
【数算-21】线索二叉树
发布日期:2021-05-07 08:58:01
浏览次数:25
分类:精选文章
本文共 11863 字,大约阅读时间需要 39 分钟。
文章目录
1、线索二叉树的引入
2、线索二叉树的概念
3、线索二叉树的应用与图解
- 问题描述
- 由于线索二叉树是通过无指向的指针来指向前驱结点与后继结点的,因此线索二叉树一定与某种特定的遍历方式有关。所以,这里以中序遍历线索二叉树为例,对线索二叉树进行图解分析
由上图的二叉树可以得出其中序遍历序列:8-3-10-1-14-6 由线索二叉树的概念可知,将无指向的左指针指向遍历序列中的前驱结点,将无指向的右指针指向遍历序列中的后继结点 对上图进行修改,可以得到下面的二叉树结构
- 具体说明
4、线索二叉树的代码实现
由于在未经线索化的二叉树结构中,结点的左右指针分别指向其左右子树;而在线索化二叉树结构中,左右指针分别指向其前驱结点和后继结点
因此需要对其左右指针所指向的内容需要进行区分 这里通过为左右指针新增对应的type属性来判断该指针指向的到底代表哪种含义Node.class
package threaded_bin_tree;/** * @author zhihua.li * @date 2021/2/15 - 23:46 **/public class Node { private Integer no; private String name; private Node left; private Node right;// 若 leftType/rightType 为 0则表示为指向其左/右子树,为1表示指向其前驱/后继结点 private int leftType; private int rightType; public int getLeftType() { return leftType; } public void setLeftType(int leftType) { this.leftType = leftType; } public int getRightType() { return rightType; } public void setRightType(int rightType) { this.rightType = rightType; } public Node(Integer no, String name) { this.no = no; this.name = name; } // 前序遍历: 根 左 右 public void preOrderIterate() { // 不用判断该节点是否为空 System.out.println(this); if (this.left != null) { this.left.preOrderIterate(); } if (this.right != null) { this.right.preOrderIterate(); } } // 中序遍历:左 根 右 public void midOrderIterate() { if (this.left != null) { this.left.midOrderIterate(); } System.out.println(this); if (this.right != null) { this.right.midOrderIterate(); } } // 后序遍历:左 右 根 public void postIterate() { if (this.left != null) { this.left.postIterate(); } if (this.right != null) { this.right.postIterate(); } System.out.println(this); } @Override public String toString() { return "binary_tree.Node{" + "no=" + no + ", name='" + name + '\'' + '}'; } public Integer getNo() { return no; } public void setNo(Integer no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Node getLeft() { return left; } public void setLeft(Node left) { this.left = left; } public Node getRight() { return right; } public void setRight(Node right) { this.right = right; }}
ThreadedBinaryTree.class
package threaded_bin_tree;/** * @author zhihua.li * @date 2021/2/16 - 19:41 **//** * @program: DataStructure * @description: 线索二叉树代码实现 * @author: zhihua li * @create: 2021-02-16 19:41 */public class ThreadedBinaryTree { private Node root; // 设置前驱结点,初始化为null private Node pre = null; public ThreadedBinaryTree(Node root) { this.root = root; } public Node getPre() { return pre; } public void setPre(Node pre) { this.pre = pre; } // 先序线索化二叉树 public void preThreadedNodes(Node node) { // 若传入结点为空,则直接返回 if (node == null) { return; }// 对当前节点进行线索化:// 如果当前节点的左子节点为空,则设置当前结点的左子节点为pre,并且设置type为1 if (node.getLeft() == null) { node.setLeft(pre); node.setLeftType(1); }// 若保存的pre结点不为空且该节点的右子节点为空,说明可以保存其后继结点到right中 if (pre != null && pre.getRight() == null) { // 给pre结点(上一次递归的结点,也为本次执行的前驱结点)设置后继结点为node pre.setRight(node); pre.setRightType(1); }// 让pre后移为当前节点 pre = node;// 对其左子节点进行递归线索化.不加判断的话会造成死循环// 比如 3-8-3-8.... if(node.getLeftType()==0) { preThreadedNodes(node.getLeft()); }// 对其右子节点进行递归线索化 if(node.getRightType()==0) { preThreadedNodes(node.getRight()); } } //中序线索化二叉树 public void midThreadedNodes(Node node) { // 当前递归的终止条件 if (node == null) { return; }// 线索化左子树 midThreadedNodes(node.getLeft());// 线索化当前结点// 1.处理当前结点的前驱结点: if (node.getLeft() == null) { // 让当前节点的左指针指向前驱结点 node.setLeft(pre);// 修改左指针类型 node.setLeftType(1); }// 2.处理当前结点的后继结点 if (pre != null && pre.getRight() == null) { pre.setRight(node); pre.setRightType(1); }// 将当前结点设置为前驱结点以便下次递归设置,pre也是动态的 pre = node;// 线索化右子树 midThreadedNodes(node.getRight()); } //后序线索化二叉树 public void postThreadedNodes(Node node) { if (node == null) { return; }// 线索化当前结点的左子结点 postThreadedNodes(node.getLeft());// 线索化当前结点的右子结点 postThreadedNodes(node.getRight());// 线索化当前结点// 处理当前结点的前驱结点 if (node.getLeft() == null) { node.setLeft(pre); node.setLeftType(1); }// 处理当前结点的后继结点 if (pre != null && pre.getRight() == null) { pre.setRight(node); pre.setRightType(1); }// 将当前结点设置为前驱结点以便下次递归设置,pre也是动态的 pre = node; } public void preThreadedList(){ if(root!=null){ root.preOrderIterate(); } System.out.println("当前根节点为空"); } public void midThreadedList(){ if(root!=null){ root.midOrderIterate(); } System.out.println("当前根节点为空"); } public void postThreadedList(){ if(root!=null){ root.postIterate(); } System.out.println("当前根节点为空"); }}
注意:先序遍历线索化二叉树时,需要对当前结点的左右结点进行判断,看其是否为前驱/后继结点,如果当前结点的左指针已经修改为其前驱结点,就不对其左子树进行递归操作了,同理,右子树也一样
5、线索二叉树的测试
设计二叉树结构为下图,分别对三种遍历方式的线索化进行测试:

1、先序线索化
ThreadedBinaryTreeTest
package threaded_bin_tree;/** * @author zhihua.li * @date 2021/2/16 - 21:16 **//** *@program: DataStructure *@description: 测试线索二叉树 *@author: zhihua li *@create: 2021-02-16 21:16 */public class ThreadedBinaryTreeTest { public static void main(String[] args) { Node root = new Node(1,"tom"); Node node2 = new Node(3,"jack"); Node node3 = new Node(6,"smith"); Node node4 = new Node(8,"mary"); Node node5 = new Node(10,"king"); Node node6 = new Node(14,"dim"); root.setLeft(node2); root.setRight(node3); node2.setLeft(node4); node2.setRight(node5); node3.setLeft(node6);// 测试线索化,以node5为例,若正常线索化,则包含前驱结点和后继结点// 由下图中的先序遍历分析可知// node5(no=10)的前驱结点为8,后继结点为6 ThreadedBinaryTree tree = new ThreadedBinaryTree(root); tree.preThreadedNodes(root); System.out.println(node5.getLeft()); System.out.println(node5.getRight()); }}
测试结果:

2、中序线索化
package threaded_bin_tree;/** * @author zhihua.li * @date 2021/2/16 - 21:16 **//** *@program: DataStructure *@description: 测试线索二叉树 *@author: zhihua li *@create: 2021-02-16 21:16 */public class ThreadedBinaryTreeTest { public static void main(String[] args) { Node root = new Node(1,"tom"); Node node2 = new Node(3,"jack"); Node node3 = new Node(6,"smith"); Node node4 = new Node(8,"mary"); Node node5 = new Node(10,"king"); Node node6 = new Node(14,"dim"); root.setLeft(node2); root.setRight(node3); node2.setLeft(node4); node2.setRight(node5); node3.setLeft(node6);// 测试线索化,以node5为例,若正常线索化,则包含前驱结点和后继结点// 由下图中的先序遍历分析可知// node5(no=10)的前驱结点为3,后继结点为1 ThreadedBinaryTree tree = new ThreadedBinaryTree(root); tree.midThreadedNodes(root); System.out.println(node5.getLeft()); System.out.println(node5.getRight()); }}
测试结果:

3、后序线索化
package threaded_bin_tree;/** * @author zhihua.li * @date 2021/2/16 - 21:16 **//** *@program: DataStructure *@description: 测试线索二叉树 *@author: zhihua li *@create: 2021-02-16 21:16 */public class ThreadedBinaryTreeTest { public static void main(String[] args) { Node root = new Node(1,"tom"); Node node2 = new Node(3,"jack"); Node node3 = new Node(6,"smith"); Node node4 = new Node(8,"mary"); Node node5 = new Node(10,"king"); Node node6 = new Node(14,"dim"); root.setLeft(node2); root.setRight(node3); node2.setLeft(node4); node2.setRight(node5); node3.setLeft(node6);// 测试线索化,以node5为例,若正常线索化,则包含前驱结点和后继结点// 由下图中的先序遍历分析可知// node5(no=10)的前驱结点为8,后继结点为3 ThreadedBinaryTree tree = new ThreadedBinaryTree(root); tree.postThreadedNodes(root); System.out.println(node5.getLeft()); System.out.println(node5.getRight()); }}
测试结果:

6、线索二叉树的遍历
1、问题描述
2、代码实现
public void midThreadedList() { // 定义变量接收当前结点的root结点 Node node = root; while (node != null) { // 循环的找到leftType为1的点,当leftType为1时,说明该节点已经历过线索化处理 while (node.getLeftType() == 0) { node = node.getLeft(); } System.out.println(node);// 循环找到rightType为1的点并打印 while (node.getRightType() == 1) { node = node.getRight(); System.out.println(node); } node = node.getRight(); } }
3、代码测试
public class ThreadedBinaryTreeTest { public static void main(String[] args) { Node root = new Node(1,"tom"); Node node2 = new Node(3,"jack"); Node node3 = new Node(6,"smith"); Node node4 = new Node(8,"mary"); Node node5 = new Node(10,"king"); Node node6 = new Node(14,"dim"); root.setLeft(node2); root.setRight(node3); node2.setLeft(node4); node2.setRight(node5); node3.setLeft(node6); ThreadedBinaryTree tree = new ThreadedBinaryTree(root); tree.midThreadedNodes(root); // 遍历顺序与线索化顺序必须一致 tree.midThreadedList();
4、扩展
1、先序遍历线索化二叉树
前序遍历代码如下:
// 遍历线索化二叉树(先序)的方法: public void preThreadedList() { Node node = root; while (node != null) { // 先序遍历线索二叉树// 先打印当前二叉树的根节点 System.out.println(node); while (node.getLeftType() == 0) { // 遍历其非线索化的左节点,并逐个打印出节点 node = node.getLeft(); System.out.println(node); } while (node.getRightType() == 1) { node = node.getRight(); System.out.println(node); } node = node.getRight(); } }
2、后序遍历线索化二叉树【推导较难】
后序遍历遇到的问题:
在本例中,由所给二叉树后序遍历序列为:8-10-3-14-6-1 由后序线索化可得下图中的线索二叉树
在遍历的过程中会遇到这样的情况:
-
1-3-8-10的过程省略。。。
当遍历到10结点时,其后继结点为3,而3的右指针指向的仅仅为右子树而并不代表后继结点,所以在这里会造成一直访问3-8-10-3…而无法直接跳转到右子树中的14结点 -
因此我想到了需要对左右子树分别遍历,左子树中断后跳转到右子树对右子树进行遍历,最后遍历根结点
-
其次对于一直3-8-10-3-8-…的循环,为了打破这个死循环,我能想到的只有为遍历过的结点添加一个标志,来避免一直循环该结点
具体代码实现
// 遍历线索化二叉树(后序)的方法: public void postThreadedList() { Node node = root; //为了避免死循环,为Node.class新增hasList属性 while(node.getLeftType()==0&&node.hasList == false){ node.hasList = true; node = node.getLeft(); } System.out.println(node); while (node.getRightType()==1) { node = node.getRight(); System.out.println(node); } //左子树遍历结束,开始遍历右子树 node = root.getRight(); //对右子树进行相同遍历操作 while(node.getLeftType()==0&&node.hasList == false){ node.hasList = true; node = node.getLeft(); } System.out.println(node); while (node.getRightType()==1) { node = node.getRight(); //注意这里不需要对根结点再次输出,因为6结点的后缀结点就为1结点,因此直接循环即可打印出root结点 System.out.println(node); } }
发表评论
最新留言
哈哈,博客排版真的漂亮呢~
[***.90.31.176]2025年04月07日 11时37分02秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Java Swing JList:列表框组件
2021-05-07
jQuery中的动画
2021-05-07
1.2.3 项目、项目集、项目组合以及运营管理之间的关系
2021-05-07
【△重点△】LeetCode - 4. 寻找两个正序数组的中位数——二分查找
2021-05-07
LeetCode - 5. 最长回文子串——字符串、动态规划
2021-05-07
全局锁和表锁 :给表加个字段怎么有这么多阻碍?
2021-05-07
事务到底是隔离的还是不隔离的?
2021-05-07
二分查找与插入排序的结合使用
2021-05-07
892 三维形体的表面积(分析)
2021-05-07
16 最接近的三数之和(排序、双指针)
2021-05-07
279 完全平方数(bfs)
2021-05-07
875 爱吃香蕉的珂珂(二分查找)
2021-05-07
桌面图标的自动排列图标
2021-05-07
第十一届蓝桥杯python组第二场省赛-数字三角形
2021-05-07
Jquery使用需要下载的文件
2021-05-07
BST中某一层的所有节点(宽度优先搜索)
2021-05-07
广度优先搜索
2021-05-07
Dijkstra算法的总结
2021-05-07
SpringCloud和SprinBoot之间的关系
2021-05-07