数据结构-线索化二叉树

一、先看一个问题

将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1=7

线索化二叉树 - 图1

问题分析:

  1. 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
  2. 但是 6, 8, 10, 14 这几个节点的 左右指针,并没有完全的利用上.
  3. 如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?

解决方案-线索二叉树

二、基本介绍

  1. n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向 该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为”线索”)
  2. 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质 的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
  3. 一个结点的前一个结点,称为前驱结点
  4. 一个结点的后一个结点,称为后继结点

三、线索二叉树应用案例

应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}

线索化二叉树 - 图2

思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}

线索化二叉树 - 图3

根据中序遍历的结构 {8, 3, 10, 1, 14, 6}

8 节点 没有前序节点,后继节点为 3 节点

3 节点已经存在左右节点,所以没有前驱节点、后继节点

10 节点的没有左右节点,前驱节点为 3 节点,后继节点为 1 节点

1 节点已经存在左右节点,所以没有前驱节点、后继节点

14 节点没有左右节点,有前驱节点 1 节点,后继节点 6 节点

6 节点有左节点 14 节点,没有右节点,没有前驱节点、后继节点

说明:

当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:

left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的 就是前驱节点.

right 指向的是右子树,也可能是指向后继节点,比如 ① 节点 right 指向的是右子树,而⑩ 节点的 right 指向 的是后继节点

代码实现:

  1. public class ThreadedBinaryTreeDemo {
  2. public static void main(String[] args) {
  3. //测试一把中序线索二叉树的功能
  4. HeroNode root = new HeroNode(1, "tom");
  5. HeroNode node2 = new HeroNode(3, "jack");
  6. HeroNode node3 = new HeroNode(6, "smith");
  7. HeroNode node4 = new HeroNode(8, "mary");
  8. HeroNode node5 = new HeroNode(10, "king");
  9. HeroNode node6 = new HeroNode(14, "dim");
  10. //二叉树,后面我们要递归创建, 现在简单处理使用手动创建
  11. root.setLeft(node2);
  12. root.setRight(node3);
  13. node2.setLeft(node4);
  14. node2.setRight(node5);
  15. node3.setLeft(node6);
  16. //测试中序线索化
  17. ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
  18. threadedBinaryTree.setRoot(root);
  19. threadedBinaryTree.threadedNodes();
  20. //测试: 以 10 号节点测试
  21. HeroNode leftNode = node5.getLeft();
  22. HeroNode rightNode = node5.getRight();
  23. System.out.println("10 号结点的前驱结点是 =" + leftNode); //3
  24. System.out.println("10 号结点的后继结点是=" + rightNode); //1
  25. }
  26. }
  27. //定义 ThreadedBinaryTree 实现了线索化功能的二叉树
  28. class ThreadedBinaryTree {
  29. private HeroNode root;
  30. //为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
  31. //在递归进行线索化时,pre 总是保留前一个结点
  32. private HeroNode pre = null;
  33. public void setRoot(HeroNode root) {
  34. this.root = root;
  35. }
  36. //重载一把 threadedNodes 方法
  37. public void threadedNodes() {
  38. this.threadedNodes(root);
  39. }
  40. //编写对二叉树进行中序线索化的方法
  41. /**
  42. *
  43. * @param node 就是当前需要线索化的结点
  44. */
  45. public void threadedNodes(HeroNode node) {
  46. //如果 node==null, 不能线索化
  47. if(node == null) {
  48. return;
  49. }
  50. //(一)先线索化左子树
  51. threadedNodes(node.getLeft());
  52. //(二)线索化当前结点[有难度]
  53. //处理当前结点的前驱结点
  54. //以 8 结点来理解
  55. //8 结点的.left = null , 8 结点的.leftType = 1
  56. if(node.getLeft() == null) {
  57. //让当前结点的左指针指向前驱结点
  58. node.setLeft(pre);
  59. //修改当前结点的左指针的类型,指向前驱结点
  60. node.setLeftType(1);
  61. }
  62. //处理后继结点
  63. if (pre != null && pre.getRight() == null) {
  64. //让前驱结点的右指针指向当前结点
  65. pre.setRight(node);
  66. //修改前驱结点的右指针类型
  67. pre.setRightType(1);
  68. }
  69. //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
  70. pre = node;
  71. //(三)在线索化右子树
  72. threadedNodes(node.getRight());
  73. }
  74. }
  75. //先创建 HeroNode 结点
  76. class HeroNode {
  77. private int no;
  78. private String name;
  79. private HeroNode left; //默认 null
  80. private HeroNode right; //默认 null
  81. //说明
  82. //1. 如果 leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
  83. //2. 如果 rightType == 0 表示指向是右子树, 如果 1 表示指向后继结点
  84. private int leftType;
  85. private int rightType;
  86. public int getLeftType() {
  87. return leftType;
  88. }
  89. public void setLeftType(int leftType) {
  90. this.leftType = leftType;
  91. }
  92. public int getRightType() {
  93. return rightType;
  94. }
  95. public void setRightType(int rightType) {
  96. this.rightType = rightType;
  97. }
  98. public HeroNode(int no, String name) {
  99. this.no = no;
  100. this.name = name;
  101. }
  102. public int getNo() {
  103. return no;
  104. }
  105. public void setNo(int no) {
  106. this.no = no;
  107. }
  108. public String getName() {
  109. return name;
  110. }
  111. public void setName(String name) {
  112. this.name = name;
  113. }
  114. public HeroNode getLeft() {
  115. return left;
  116. }
  117. public void setLeft(HeroNode left) {
  118. this.left = left;
  119. }
  120. public HeroNode getRight() {
  121. return right;
  122. }
  123. public void setRight(HeroNode right) {
  124. this.right = right;
  125. }
  126. @Override
  127. public String toString() {
  128. return "HeroNode [no=" + no + ", name=" + name + "]";
  129. }
  130. }