有以下场景

如果使用中序遍历,那么得到的顺序是:HDIBEAFCG,可以得知A的前驱结点为E,后继结点为F。但是,这种关系的获得是建立在完成遍历后得到的。如果我们每次想得到某个节点的前驱或者后继,都要先遍历整棵树,未免太过于麻烦了!我们为何不在生成该树的时候,就直接将结点的前驱后后继写入结点的属性中,提高在后续寻找前驱结点和后继结点的效率?这就是线索二叉树的由来。
首先,得先明确以下概念:

1.n个结点的二叉链表中含有n+1个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结
占的指针(这种附加的指针称为"线索")
2.这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉(ThreadedBinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
3.一个结点的前一个结点,称为前驱结点
4.一个结点的后一个结点,称为后继结点
5.二叉树线索化时,通常规定,若左子树为空,令其左子节点=其前驱结点,若右子树为空,令其右子结点==其后继结点。
6.指向结点得前驱后者后继指针,便称为线索。
7.将二叉树以某种次序遍历使其变为线索二叉树的过程成为线索化


图中的虚线为线索

出现问题:


上面的双向链表,与线索化之后的二叉树相比,比如节点D与后继节点I,在完成线索化之后,并没有直接线索指针,而是存在父子节点的指针;节点A与节点F,在线索化完成之后,节点A并没有直接指向后继节点F的线索指针,而是通过父子节点遍历可以找到最终的节点F,前驱节点也存在同样的问题,正因为很多节点之间不存在直接的线索,所以我将此双向链表称做“特殊的双向链表”,再使用过程中根据指针是线索指针还是子节点指针来分别处理,所以在每个节点需要标明当前的左右指针是线索指针还是子节点指针,这就需要修改节点的数据结构,增加标志位。

isLeftThread为false时,指向左孩子,为ture时指向前驱
isRightThread为false时,指向右孩子,为ture时指向后继

添加后结点结构如下:

节点类:

//节点存储结构
static class Node {String data;        //数据域Node left;          //左指针域Node right;         //右指针域Node parent;        //父节点的指针(为了后序线索化使用)boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:后继Node(String data) {this.data = data;}
}

添加标志位后的二叉树:

线索树代码:

/*** @Title: 二叉树相关操作* @Description:* @Author: Uncle Ming* @Date:2017年1月6日 下午2:49:14* @Version V1.0*/
public class ThreadBinaryTree {private Node preNode;   //线索化时记录前一个节点//节点存储结构static class Node {String data;        //数据域Node left;          //左指针域Node right;         //右指针域boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱或后继线索boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:前驱或后继线索Node(String data) {this.data = data;}}/*** 通过数组构造一个二叉树(完全二叉树)* @param array* @param index* @return*/static Node createBinaryTree(String[] array, int index) {Node node = null;if(index < array.length) {node = new Node(array[index]);node.left = createBinaryTree(array, index * 2 + 1);node.right = createBinaryTree(array, index * 2 + 2);}return node;}/*** 中序线索化二叉树* @param node  节点*/void inThreadOrder(Node node) {if(node == null) {return;}//处理左子树inThreadOrder(node.left);//左指针为空,将左指针指向前驱节点if(node.left == null) {node.left = preNode;node.isLeftThread = true;}//前一个节点的后继节点指向当前节点if(preNode != null && preNode.right == null) {preNode.right = node;preNode.isRightThread = true;}preNode = node;//处理右子树inThreadOrder(node.right);}/*** 中序遍历线索二叉树,按照后继方式遍历(思路:找到最左子节点开始)* @param node*/void inThreadList(Node node) {//1、找中序遍历方式开始的节点while(node != null && !node.isLeftThread) {node = node.left;}while(node != null) {System.out.print(node.data + ", ");//如果右指针是线索if(node.isRightThread) {node = node.right;} else {    //如果右指针不是线索,找到右子树开始的节点node = node.right;while(node != null && !node.isLeftThread) {node = node.left;}}}}/*** 中序遍历线索二叉树,按照前驱方式遍历(思路:找到最右子节点开始倒序遍历)* @param node*/void inPreThreadList(Node node) {//1、找最后一个节点while(node.right != null && !node.isRightThread) {node = node.right;}while(node != null) {System.out.print(node.data + ", ");//如果左指针是线索if(node.isLeftThread) {node = node.left;} else {    //如果左指针不是线索,找到左子树开始的节点node = node.left;while(node.right != null && !node.isRightThread) {node = node.right;}}}}/*** 前序线索化二叉树* @param node*/void preThreadOrder(Node node) {if(node == null) {return;}//左指针为空,将左指针指向前驱节点if(node.left == null) {node.left = preNode;node.isLeftThread = true;}//前一个节点的后继节点指向当前节点if(preNode != null && preNode.right == null) {preNode.right = node;preNode.isRightThread = true;}preNode = node;//处理左子树if(!node.isLeftThread) {preThreadOrder(node.left);}//处理右子树if(!node.isRightThread) {preThreadOrder(node.right);}}/*** 前序遍历线索二叉树(按照后继线索遍历)* @param node*/void preThreadList(Node node) {while(node != null) {while(!node.isLeftThread) {System.out.print(node.data + ", ");node = node.left;}System.out.print(node.data + ", ");node = node.right;}}public static void main(String[] args) {String[] array = {"A", "B", "C", "D", "E", "F", "G", "H"};Node root = createBinaryTree(array, 0);ThreadBinaryTree tree = new ThreadBinaryTree();tree.inThreadOrder(root);System.out.println("中序按后继节点遍历线索二叉树结果:");tree.inThreadList(root);System.out.println("\n中序按后继节点遍历线索二叉树结果:");tree.inPreThreadList(root);Node root2 = createBinaryTree(array, 0);ThreadBinaryTree tree2 = new ThreadBinaryTree();tree2.preThreadOrder(root2);tree2.preNode = null;System.out.println("\n前序按后继节点遍历线索二叉树结果:");tree.preThreadList(root2);}
}

加上头结点

有时为了方便,模仿线性表的存储结构为了便于遍历线索二叉树,我们为其添加一个头结点,头结点左孩子指向原二叉树的根结点,右孩子指针指向中序遍历的最后一个结点。同时,将第一个结点左孩子指针指向头结点,最后一个结点的右孩子指针指向头结点。这好比为二叉树建立了一个双向线索链表,既可以从第一个结点起顺后继进行遍历,又可以从最后一个结点起顺前驱进行遍历。

小结

  1. 线索化的实质就是将二叉链表中的空指针改为指向前驱节点或后继节点的线索;
  2. 线索化的过程就是修改二叉链表中空指针的过程,可以按照前序、中序、后序的方式进行遍历,分别生成不同的线索二叉树;
  3. 有了线索二叉树之后,我们再次遍历时,就相当于操作一个双向链表。
  4. 使用场景:如果我们在使用二叉树过程中经常需要遍历二叉树或者查找节点的前驱节点和后继节点,可以考虑采用线索二叉树存储结构。

本文转载于

线索二叉树(基于链表存储树结点)相关推荐

  1. 顺序二叉树(基于数组存储树结点)

    从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组,看下面的示意图. 顺序二叉树有两个要求: 1.上图的二叉树结点,以数组的方式来存放 2.要求在遍历数组时 ...

  2. 二叉树的链表存储与遍历

    终于进入了二叉树的学习章节,二叉树的存储结构包括数组存储和链表存储,我主要介绍链表存储,数组存储也是类似的. #include<stdio.h> #include<stdlib.h& ...

  3. 【练习】2021下半年数据结构刷题笔记和总结 (二) 树、查找-- 不同的排序算法、二叉排序树 平衡二叉树、哈希表查找、线索二叉树、

    记录自己下半年写题目的记录.题目来自书或者网站. 练习(一)的地址: https://blog.csdn.net/qq_41358574/article/details/117098620?ops_r ...

  4. 【数据结构Note5】- 树和二叉树(知识点超细大全-涵盖常见算法 排序二叉树 线索二叉树 平衡二叉树 哈夫曼树)

    文章目录 5.1 树和二叉树引入 5.1.1 树的概念 5.1.2 树的表示 5.1.3 树中基本术语 5.2 二叉树 5.2.1 概念 5.2.2 二叉树的性质 5.2.3 特殊的二叉树 5.2.4 ...

  5. 二叉树的链式存储结构(线索二叉树)

    一.链式存储结构 由于顺序存储二叉树的空间利用率较低,因此二叉树一般都采用链式存储结构,用链表结点来存储二叉树中的每个结点.在二叉树中,结点结构通过包括若干数据域和若干指针域,二叉链表至少包含3个域: ...

  6. 线索二叉树(C语言实现)——后续线索链表

    #include<stdio.h> #include<stdlib.h> #include<stdbool.h> typedef char DataType;// ...

  7. 我这么讲线索二叉树,我三岁大的表弟笑了笑

    目录: 1.线索二叉树的由来 2.线索二叉树的概念和结构 3.二叉树的线索化 4.中序线索二叉树的代码实现 5.遍历线索二叉树 1.线索二叉树的由来 我问表弟下面的一个问题: 在n个结点一个二叉链表中 ...

  8. VC++2012编程演练数据结构《25》线索二叉树

    线索二叉树 按照某种遍历方式对二叉树进行遍历,可以把二叉树中所有结点排序为一个线性序列.在该序列中,除第一个结点外每个结点有且仅有一个直接前驱结点:除最后一个结点外每一个结点有且仅有一个直接后继结点. ...

  9. 8.遍历二叉树、线索二叉树、森林

    思考 一.什么遍历二叉树.线索二叉树.森林?(What) 0.二叉树遍历一共(4种) 1.二叉树先序遍历 2.二叉树中序遍历 3.二叉树后序遍历 4.遍历分析 5.遍历二叉树 6.二叉树层次遍历 7. ...

最新文章

  1. 51单片机实现对24C02进行页写、顺序读取并显示验证
  2. PyramidBox笔记
  3. ABP官方文档翻译 0.0 ABP官方文档翻译目录
  4. POJ2296二分2sat
  5. navicat mysql两张表建立联系_初识MySQL
  6. HTML/CSS——子元素相对于父元素固定定位解决方案
  7. Symbian学习笔记(4)——在GUI应用中使用图像
  8. SpringBoot 迭代输出
  9. 数据结构(复习)--------关于平衡二叉树(转载)
  10. 实时数据交换平台 - BottledWater-pg with confluent
  11. Problem M. Mediocre String Problem(Z 函数 + PAM)
  12. 六、pink老师学习笔记——CSS三角形之美 margin负值之美文字围绕浮动元素行内块元素布局的巧妙运用
  13. 12种NumpyPandas高效技巧
  14. Michael Jordan:当下的AI其实都是伪“AI” 1
  15. python项目总结与展望_我做知识星球一周年总结与未来展望
  16. Android View.onMeasure方法的理解
  17. python数字转字符串固定位数_python-将String转换为64位整数映射字符以自定...
  18. 如何解决mysql数据倾斜_什么是数据倾斜?如何解决数据倾斜?
  19. 1个开发如何撑起一个过亿用户的小程序
  20. devtmpfs文件系统创建设备节点

热门文章

  1. 【题解】Luogu SP1435 PT07X - Vertex Cover
  2. 【UML】UML类图关系(泛化 、继承、实现、依赖、关联、聚合、组合)
  3. Anywhere 随启随用的静态文件服务器
  4. 2016.6.23 随笔———— AJAX
  5. Order附近语法错误
  6. SQL 取字符串列表
  7. 阐述Linux动态库的显式调用
  8. mqtt android封装,Android之MQTT封装使用
  9. 云炬随笔20210819
  10. 云炬随笔20190701