线索化二叉树

亦凉 2024-04-18 21:44 173阅读 0赞

一、问题

将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1=7
在这里插入图片描述
问题分析:

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

二、线索二叉树基本介绍

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

三、线索二叉树应用案例

应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}
在这里插入图片描述
思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}
在这里插入图片描述
说明: 当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:

  1. left 指向的是左子树,也可能是指向的前驱节点. 比如 1 节点 left 指向的左子树, 而 10 节点的 left 指向的就是前驱节点.
  2. right 指向的是右子树,也可能是指向后继节点,比如 1 节点 right 指向的是右子树,而10 节点的 right 指向的是后继节点.

四、遍历线索化二叉树

  1. 说明:对前面的中序线索化的二叉树, 进行遍历
  2. 分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历 线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次 序应当和中序遍历保持一致。

五、代码实现:

  1. package tree.threadedbinarytree;
  2. /**
  3. * @program: text
  4. * @description: 线索二叉树
  5. * @author: min
  6. * @create: 2019-09-12 11:14
  7. **/
  8. public class ThreadedBinaryTreeDemo {
  9. public static void main(String[] args) {
  10. HeroNode root = new HeroNode(1, "tom");
  11. HeroNode node2 = new HeroNode(3, "jack");
  12. HeroNode node3 = new HeroNode(6, "smith");
  13. HeroNode node4 = new HeroNode(8, "mary");
  14. HeroNode node5 = new HeroNode(10, "king");
  15. HeroNode node6 = new HeroNode(14, "dim");
  16. //二叉树,后面我们要递归创建, 现在简单处理使用手动创建
  17. root.setLeft(node2);
  18. root.setRight(node3);
  19. node2.setLeft(node4);
  20. node2.setRight(node5);
  21. node3.setLeft(node6);
  22. //测试中序线索化
  23. ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
  24. threadedBinaryTree.setRoot(root);
  25. threadedBinaryTree.threadedNodes();
  26. //测试: 以 10 号节点测试
  27. HeroNode leftNode = node5.getLeft();
  28. HeroNode rightNode = node5.getRight();
  29. System.out.println("10 号结点的前驱结点是 =" + leftNode); //3
  30. System.out.println("10 号结点的后继结点是=" + rightNode); //1
  31. //当线索化二叉树后,能在使用原来的遍历方法
  32. // threadedBinaryTree.infixOrder();
  33. System.out.println("使用线索化的方式遍历 线索化二叉树");
  34. threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6
  35. }
  36. }
  37. //定义 ThreadedBinaryTree 实现了线索化功能的二叉树
  38. class ThreadedBinaryTree {
  39. private HeroNode root;
  40. //为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
  41. // 在递归进行线索化时,pre 总是保留前一个结点
  42. private HeroNode pre = null;
  43. public void setRoot(HeroNode root) {
  44. this.root = root;
  45. }
  46. //重载一把 threadedNodes 方法
  47. public void threadedNodes() {
  48. this.threadedNodes(root);
  49. }
  50. //遍历线索化二叉树的方法
  51. public void threadedList() {
  52. //定义一个变量,存储当前遍历的结点,从 root 开始
  53. HeroNode node = root;
  54. while (node != null) {
  55. //循环的找到 leftType == 1 的结点,第一个找到就是 8 结点
  56. // 后面随着遍历而变化,因为当 leftType==1 时,说明该结点是按照线索化
  57. // 处理后的有效结点
  58. while (node.getLeftType() == 0) {
  59. node = node.getLeft();
  60. }
  61. //打印当前这个结点
  62. System.out.println(node);
  63. //如果当前结点的右指针指向的是后继结点,就一直输出
  64. while (node.getRightType() == 1) {
  65. //获取到当前结点的后继结点
  66. node = node.getRight();
  67. System.out.println(node);
  68. } //替换这个遍历的结点
  69. node = node.getRight();
  70. }
  71. }
  72. // 二叉树进行中序线索化
  73. public void threadedNodes(HeroNode node) {
  74. //如果 node==null, 不能线索化
  75. if (node == null) {
  76. return;
  77. }
  78. //(一)先线索化左子树
  79. threadedNodes(node.getLeft());
  80. //(二)线索化当前结点[有难度]
  81. //处理当前结点的前驱结点
  82. //以 8 结点来理解
  83. //8 结点的.left = null , 8 结点的.leftType = 1
  84. if (node.getLeft() == null) {
  85. //让当前结点的左指针指向前驱结点
  86. node.setLeft(pre);
  87. //修改当前结点的左指针的类型,指向前驱结点
  88. node.setLeftType(1);
  89. }
  90. //处理后继结点
  91. if (pre != null && pre.getRight() == null) {
  92. //让前驱结点的右指针指向当前结点
  93. pre.setRight(node);
  94. //修改前驱结点的右指针类型
  95. pre.setRightType(1);
  96. }
  97. //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
  98. pre = node;
  99. //(三)在线索化右子树
  100. threadedNodes(node.getRight());
  101. }
  102. //删除结点
  103. public void delNode(int no) {
  104. if (root != null) {
  105. //如果只有一个 root 结点, 这里立即判断 root 是不是就是要删除结点
  106. if (root.getNo() == no) {
  107. root = null;
  108. } else {
  109. //递归删除
  110. root.delNode(no);
  111. }
  112. } else {
  113. System.out.println("空树,不能删除~");
  114. }
  115. }
  116. //前序遍历
  117. public void preOrder() {
  118. if (this.root != null) {
  119. this.root.preOrder();
  120. } else {
  121. System.out.println("二叉树为空,无法遍历");
  122. }
  123. }
  124. //中序遍历
  125. public void infixOrder() {
  126. if (this.root != null) {
  127. this.root.infixOrder();
  128. } else {
  129. System.out.println("二叉树为空,无法遍历");
  130. }
  131. }
  132. //后序遍历
  133. public void postOrder() {
  134. if (this.root != null) {
  135. this.root.postOrder();
  136. } else {
  137. System.out.println("二叉树为空,无法遍历");
  138. }
  139. }
  140. //前序遍历
  141. public HeroNode preOrderSearch(int no) {
  142. if (root != null) {
  143. return root.preOrderSearch(no);
  144. } else {
  145. return null;
  146. }
  147. }
  148. //中序遍历
  149. public HeroNode infixOrderSearch(int no) {
  150. if (root != null) {
  151. return root.infixOrderSearch(no);
  152. } else {
  153. return null;
  154. }
  155. }
  156. //后序遍历
  157. public HeroNode postOrderSearch(int no) {
  158. if (root != null) {
  159. return this.root.postOrderSearch(no);
  160. } else {
  161. return null;
  162. }
  163. }
  164. }
  165. //先创建 HeroNode 结点
  166. class HeroNode {
  167. private int no;
  168. private String name;
  169. private HeroNode left; //默认 null
  170. private HeroNode right; //默认 null
  171. //说明
  172. //1. 如果 leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
  173. //2. 如果 rightType == 0 表示指向是右子树, 如果 1 表示指向后继结点
  174. private int leftType;
  175. private int rightType;
  176. public int getLeftType() {
  177. return leftType;
  178. }
  179. public void setLeftType(int leftType) {
  180. this.leftType = leftType;
  181. }
  182. public int getRightType() {
  183. return rightType;
  184. }
  185. public void setRightType(int rightType) {
  186. this.rightType = rightType;
  187. }
  188. public HeroNode(int no, String name) {
  189. this.no = no;
  190. this.name = name;
  191. }
  192. public int getNo() {
  193. return no;
  194. }
  195. public void setNo(int no) {
  196. this.no = no;
  197. }
  198. public String getName() {
  199. return name;
  200. }
  201. public void setName(String name) {
  202. this.name = name;
  203. }
  204. public HeroNode getLeft() {
  205. return left;
  206. }
  207. public void setLeft(HeroNode left) {
  208. this.left = left;
  209. }
  210. public HeroNode getRight() {
  211. return right;
  212. }
  213. public void setRight(HeroNode right) {
  214. this.right = right;
  215. }
  216. @Override
  217. public String toString() {
  218. return "HeroNode [no=" + no + ", name=" + name + "]";
  219. }
  220. //递归删除结点
  221. //1.如果删除的节点是叶子节点,则删除该节点
  222. //2.如果删除的节点是非叶子节点,则删除该子树
  223. public void delNode(int no) {
  224. //思路
  225. /** 1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断 当前这个结点是不是需要删除结点.
  226. 2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回 (结束递归删除)
  227. 3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回 (结束递归删除)
  228. 4. 如果第 2 和第 3 步没有删除结点,那么我们就需要向左子树进行递归删除
  229. 5. 如果第 4 步也没有删除结点,则应当向右子树进行递归删除.*/
  230. //2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回(结束递归删除)
  231. if (this.left != null && this.left.no == no) {
  232. this.left = null;
  233. return;
  234. }
  235. //3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回(结 束递归删除)
  236. if (this.right != null && this.right.no == no) {
  237. this.right = null;
  238. return;
  239. }
  240. //4.我们就需要向左子树进行递归删除
  241. if (this.left != null) {
  242. this.left.delNode(no);
  243. }
  244. //5.则应当向右子树进行递归删除
  245. if (this.right != null) {
  246. this.right.delNode(no);
  247. }
  248. }
  249. //编写前序遍历的方法
  250. public void preOrder() {
  251. System.out.println(this); //先输出父结点 //递归向左子树前序遍历
  252. if (this.left != null) {
  253. this.left.preOrder();
  254. }
  255. //递归向右子树前序遍历
  256. if (this.right != null) {
  257. this.right.preOrder();
  258. }
  259. }
  260. //中序遍历
  261. public void infixOrder() {
  262. //递归向左子树中序遍历
  263. if (this.left != null) {
  264. this.left.infixOrder();
  265. }
  266. //输出父结点
  267. System.out.println(this);
  268. //递归向右子树中序遍历
  269. if (this.right != null) {
  270. this.right.infixOrder();
  271. }
  272. }
  273. //后序遍历
  274. public void postOrder() {
  275. if (this.left != null) {
  276. this.left.postOrder();
  277. }
  278. if (this.right != null) {
  279. this.right.postOrder();
  280. }
  281. System.out.println(this);
  282. }
  283. /**
  284. * @param no 查找 no
  285. * @return 如果找到就返回该 Node ,如果没有找到返回 null
  286. */
  287. public HeroNode preOrderSearch(int no) {
  288. System.out.println("进入前序查找"); //比较当前结点是不是
  289. if (this.no == no) {
  290. return this;
  291. }
  292. //1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
  293. //2.如果左递归前序查找,找到结点,则返回
  294. HeroNode resNode = null;
  295. if (this.left != null) {
  296. resNode = this.left.preOrderSearch(no);
  297. }
  298. if (resNode != null) {//说明我们左子树找到
  299. return resNode;
  300. }
  301. //1.左递归前序查找,找到结点,则返回,否继续判断,
  302. //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
  303. if (this.right != null) {
  304. resNode = this.right.preOrderSearch(no);
  305. }
  306. return resNode;
  307. }
  308. //中序遍历查找
  309. public HeroNode infixOrderSearch(int no) {
  310. //判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
  311. HeroNode resNode = null;
  312. if (this.left != null) {
  313. resNode = this.left.infixOrderSearch(no);
  314. }
  315. if (resNode != null) {
  316. return resNode;
  317. }
  318. System.out.println("进入中序查找");
  319. //如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
  320. if (this.no == no) {
  321. return this;
  322. }
  323. //否则继续进行右递归的中序查找
  324. if (this.right != null) {
  325. resNode = this.right.infixOrderSearch(no);
  326. }
  327. return resNode;
  328. }
  329. //后序遍历查找
  330. public HeroNode postOrderSearch(int no) {
  331. //判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
  332. HeroNode resNode = null;
  333. if (this.left != null) {
  334. resNode = this.left.postOrderSearch(no);
  335. }
  336. if (resNode != null) {//说明在左子树找到
  337. return resNode;
  338. }
  339. //如果左子树没有找到,则向右子树递归进行后序遍历查找
  340. if (this.right != null) {
  341. resNode = this.right.postOrderSearch(no);
  342. }
  343. if (resNode != null) {
  344. return resNode;
  345. }
  346. System.out.println("进入后序查找");
  347. //如果左右子树都没有找到,就比较当前结点是不是
  348. if (this.no == no) {
  349. return this;
  350. }
  351. return resNode;
  352. }
  353. }

转载至:尚硅谷_韩顺平_图解Java数据结构和算法.pdf

发表评论

表情:
评论列表 (有 0 条评论,173人围观)

还没有评论,来说两句吧...

相关阅读

    相关 线索

    1,线索化二叉树基本介绍 线索化二叉树是对普通二叉树的扩展,对于普通二叉树而言,一个节点会包含一个数据域以及指向左右子节点的位置索引;但是对于叶子节点来讲,左右子节

    相关 线索算法

    二叉树线索化 简述 为什么需要线索二叉树? 1. 对于普通的二叉树来说,如果随便给出二叉树中的一个结点,让你从这个结点遍历整个二叉树,这是做不到的(其实对于普通

    相关 线索

    二叉树是一种非线性结构,遍历二叉树几乎都是通过递归或者用栈辅助实现非递归的遍历。用二叉树作为存储结构时,取到一个节点,只 能获取节点的左孩子和右孩子,不能直接得到节点的任一遍历