线索二叉树C+Java

1、线索二叉树的原理:

为什么会有线索二叉树呢?我们观察一个普通的二叉树:

这正是我们平常见到的二叉树,可以发现A,B,C,D这四个节点它们的左孩子和有孩子都有节点,而E,F,G,H,I,J它们都有空闲的指针,这些空闲的指针不存储任何事物

白白浪费了内存的资源。

当我们中序遍历二叉树:HDIBJEAFCG .遍历完之后可以指到每个节点的前驱和后继,如D的前驱是H,后继是I。

可是这是建立在已经遍历完之后的基础上,这里需要注意的是每一种遍历的结果前驱和后继都会不一样的。

在二叉链表创建完后,我们只知道每个节点的左孩子和右孩子是谁,但是不知道每个节点的前驱和后继是谁?(当然这是针对某一种特定的遍历,这里等下以中序遍历为例)

所以要想知道每个节点的前驱和后继是谁,就必须遍历一次,那这个时候我们会考虑,为什么不在建立的时候就把前驱和后继记住,这样子将会节省大量的时间。

综合上述考虑,我们可以考虑利用那些空的地址,存放指向节点在某种遍历下的前驱节点和后继结点的地址。就好像GPS导航一样,我们在开车时,哪怕我们不知道具体的目的地

但是我们知道去往目的地的下一站地址,这就是我们要研究的。即我们把指向前驱和后继的指针称为线索,加上线索的链表称为线索链表,对应的二叉树称之为线索二叉树。

那么问题开了,我们如何区分一个节点的lchild是指向前驱还是左孩子,rchild是指向后继还是右孩子。

我们规定:修改普通节点结构,为每一个节点再增加两个标志位ltag和rtag.注意ltag和rtag只存放0和1数字的布尔变量,

其占用内存要小于lchild和rchild的指针变量。

节点结构:

(1)ltag为0时指向该结点的左孩子,为1时指向该结点的前驱;

(2)rtag为0时指向该结点的右孩子,为1时指向该结点的后继;

(3)因此对于上图的二叉链表图可以修改为下图的样子:

2、二叉树的结构实现:

 1 typedef char TElemType;
 2 /*线索二叉树的节点结构*/
 3 /*Link == 0表示指向左右孩子的指针,Thread==1表示指向前驱或后继的线索*/
 4 typedef enum{Link,Thread}PointerTag;
 5 typedef struct BiThrNode{
 6
 7     TElemType data;
 8     struct BiThrNode *lchild,*rchild; /*存储左右孩子指针*/
 9     PointerTag LTag;
10     PointerTag RTag;
11 }BiThrNode;
12 typedef struct BiThrNode *BiThrTree;  //取别名

线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。由于前驱和后继的信息只有在遍历后才能获得,所以线索化的过程就是在遍历的过程修改空指针的过程。

 1 BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */
 2 /* 中序遍历进行中序线索化 */
 3 void InThreading(BiThrTree p){
 4    if(p){
 5         InThreading(p->lchild); //递归左孩子线索化
 6
 7         //--->
 8         if(!p->lchild){         //没有左孩子
 9             p->LTag = Thread;    //线索化:前驱线索
10             p->lchild = pre;  //左孩子指向前驱
11         }
12         if(!pre->rchild){   //前驱没有右孩子
13             pre->RTag = Thread; //线索化:后继线索
14             pre->rchild = p;   //前驱右孩子指向后继(当前节点p)
15         }
16         pre = p;   //保持pre始终指向p的前驱
17
18         //<---
19         InThreading(p->rchild);     //递归右孩子线索化
20     }
21 }

代码块里面除了 //--->  -<---//里面的,其他的都和中序遍历一样。

//--->  -<---//在中序遍历里面是打印节点,而在线索二叉树里面是对节点线索化。

中间部分代码做了这样的事情:

当指针指向树的最左节点时

if(!p->lchild){ //当没有左孩子
p->LTag = Thread; //线索化:前驱线索
p->lchild = pre; //左孩子指向前驱
}

if(!pre->rchild){ //前驱没有右孩子
pre->RTag = Thread; //线索化:后继线索
pre->rchild = p; //前驱右孩子指向后继(当前节点p)
}

pre = p;   //保持pre始终指向p的前驱

动手画一画就明白了,不信你试试。

实在不懂的化可以参考:

https://www.bilibili.com/video/av15550650?t=635

https://www.bilibili.com/video/av35340088?t=227

现在我们只是将空闲的指针利用了起来,可是真的全部利用起来了吗?

很明显,在中序遍历的第一个节点和最后一个节点它们都有空闲的左孩子和右孩子,那它们要它们指向哪里呢?

其实刚开始它们是指向null的,但是为了我们遍历二叉树方便,这个时候他已经形成了双向链表,我们想链表一样引入头结点,所做的操作如下:

1、将头结点的左孩子指向根节点

2、将中序遍历的第一个节点的左孩子指向头结点

3、将中序遍历的最后一个节点指向头结点

-----这个时候就形成了双向循环链表。

4、最后再将头结点的右孩子指向最后一个节点

这样做的好处是:

我们既可以将从第一个节点起顺后继进行遍历,也可以从最后一个节点起顺后继进行遍历。

 1 /* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */
 2 Status InOrderThreading(BiThrTree* Thrt,BiThrTree T){
 3     //
 4     *Thrt = (BiThrTree)malloc(sizeof(BiThrNode));  //创建头结点
 5     if(!*Thrt){
 6         exit(0);
 7     }
 8     (*Thrt)->LTag = Link; //标识头结点有左孩子
 9     (*Thrt)->RTag = Thread; //无右孩子:线索
10     (*Thrt)->rchild = (*Thrt); //左孩子指向自己指向自己
11     if(!T){//若二叉树为空时,左指针也指向自己
12         (*Thrt)->lchild = (*Thrt); //指向自己
13     }else{
14         (*Thrt)->lchild = T; //头结点左孩子指向树的根节点
15         pre = (*Thrt);//前驱节点指向头结点
16         InThreading(T);//中序遍历进行中序线索化
17         pre ->rchild = *Thrt; //最后一个节点的右孩子指向头节点
18         pre->RTag = Thread ; //最后节点线索化
19         (*Thrt)->rchild = pre;//头结点指向最后一个节点
20
21     }
22     return OK;
23 }

插入头结点后开始遍历:

注意:此时应按如上的双向循环链表的遍历的方式进行遍历

 1 /* 中序遍历二叉线索树T(头结点)的非递归算法 */
 2 Status InOrderTraverse_Thr(BiThrTree T){
 3     //开始遍历双向链表
 4     BiThrTree p;
 5     p = T->lchild; //p指向根节点
 6     while(p!=T){        //空树或遍历结束时 p == T
 7         while(p->LTag == Link){
 8             p = p->lchild;
 9         }
10         if(!visit(p->data)){
11             return ERROR;
12         }
13         //此时p指向中序遍历序列的第一个结点(最左下的结点)
14         while(p->RTag == Thread && p->rchild !=T){ //当前节点没有右孩子,并且有孩子!=头结点
15             p = p->rchild;
16             visit(p->data);  //访问后继节点
17         }
18          //当p所指结点的rchild指向的是孩子结点而不是线索时,
19         //p的后继应该是其右子树的最左下的结点,即遍历其右子树时访问的第一个节点
20         p = p->rchild;
21     }
22     return OK;
23 }

全部代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3
  4 #define OK 1
  5 #define ERROR 0
  6 #define TRUE 1
  7 #define FALSE 0
  8
  9 typedef int Status;
 10 typedef char TElemType;
 11
 12 /*线索二叉树的节点结构*/
 13 /*Link == 0表示指向左右孩子的指针,Thread==1表示指向前驱或后继的线索*/
 14 typedef enum{Link,Thread}PointerTag;
 15 typedef struct BiThrNode{
 16
 17     TElemType data;
 18     struct BiThrNode *lchild,*rchild; /*存储左右孩子指针*/
 19     PointerTag LTag;
 20     PointerTag RTag;
 21 }BiThrNode;
 22 typedef struct BiThrNode *BiThrTree;  //取别名
 23
 24 Status visit(TElemType e){
 25     printf("%c ",e);
 26     return OK;
 27 }
 28
 29 /* 按前序输入二叉线索树中结点的值,构造二叉线索树T */
 30 /* 0(整型)/空格(字符型)表示空结点 */
 31 TElemType Nil=‘#‘; /* 字符型以空格符为空 */
 32 Status CreateBiThrTree(BiThrTree* T){
 33     TElemType h;
 34     scanf("%c",&h);
 35     if(h ==Nil){
 36         *T =NULL;
 37     }else{
 38         *T = (BiThrTree)malloc(sizeof(BiThrNode));
 39         if(!*T){
 40             exit(0);
 41         }
 42         (*T)->data = h;
 43         CreateBiThrTree(&(*T)->lchild);
 44         if((*T)->lchild){   //有左孩子
 45             (*T)->LTag = Link;
 46         }
 47         CreateBiThrTree(&(*T)->rchild);
 48         if((*T)->rchild){   //有左孩子
 49             (*T)->RTag = Link;
 50         }
 51     }
 52     return OK;
 53 }
 54
 55 BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */
 56 /* 中序遍历进行中序线索化 */
 57 void InThreading(BiThrTree p){
 58    if(p){
 59         InThreading(p->lchild); //递归左孩子线索化
 60
 61         //--->
 62         if(!p->lchild){         //没有左孩子
 63             p->LTag = Thread;    //线索化:前驱线索
 64             p->lchild = pre;  //左孩子指向前驱
 65         }
 66         if(!pre->rchild){   //前驱没有右孩子
 67             pre->RTag = Thread; //线索化:后继线索
 68             pre->rchild = p;   //前驱右孩子指向后继(当前节点p)
 69         }
 70         pre = p;   //保持pre始终指向p的前驱
 71
 72         //<---
 73         InThreading(p->rchild);     //递归右孩子线索化
 74     }
 75 }
 76
 77 /* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */
 78 Status InOrderThreading(BiThrTree* Thrt,BiThrTree T){
 79     //
 80     *Thrt = (BiThrTree)malloc(sizeof(BiThrNode));  //创建头结点
 81     if(!*Thrt){
 82         exit(0);
 83     }
 84     (*Thrt)->LTag = Link; //标识头结点有左孩子
 85     (*Thrt)->RTag = Thread; //无右孩子:线索
 86     (*Thrt)->rchild = (*Thrt); //左孩子指向自己指向自己
 87     if(!T){//若二叉树为空时,左指针也指向自己
 88         (*Thrt)->lchild = (*Thrt); //指向自己
 89     }else{
 90         (*Thrt)->lchild = T; //头结点左孩子指向树的根节点
 91         pre = (*Thrt);//前驱节点指向头结点
 92         InThreading(T);//中序遍历进行中序线索化
 93         pre ->rchild = *Thrt; //最后一个节点的右孩子指向头节点
 94         pre->RTag = Thread ; //最后节点线索化
 95         (*Thrt)->rchild = pre;//头结点指向最后一个节点
 96
 97     }
 98     return OK;
 99 }
100
101 /* 中序遍历二叉线索树T(头结点)的非递归算法 */
102 Status InOrderTraverse_Thr(BiThrTree T){
103     //开始遍历双向链表
104     BiThrTree p;
105     p = T->lchild; //p指向根节点
106     while(p!=T){        //空树或遍历结束时 p == T
107         while(p->LTag == Link){
108             p = p->lchild;
109         }
110         if(!visit(p->data)){
111             return ERROR;
112         }
113         //此时p指向中序遍历序列的第一个结点(最左下的结点)
114         while(p->RTag == Thread && p->rchild !=T){ //当前节点没有右孩子,并且有孩子!=头结点
115             p = p->rchild;
116             visit(p->data);  //访问后继节点
117         }
118          //当p所指结点的rchild指向的是孩子结点而不是线索时,
119         //p的后继应该是其右子树的最左下的结点,即遍历其右子树时访问的第一个节点
120         p = p->rchild;
121     }
122     return OK;
123 }
124
125 int main(){
126
127     BiThrTree H,T;
128     printf("请按前序输入二叉树(如:‘ABDH##I##EJ###CF##G##‘)\n");
129     CreateBiThrTree(&T); //按前序产生二叉树
130     InOrderThreading(&H,T); //中序遍历,并中序线索化二叉树、
131     printf("中序遍历(输出)二叉树:\n");
132     InOrderTraverse_Thr(H);
133
134     return 0;
135 }

小结:

线索二叉树比起普通的二叉树,结构较为复杂,多了两个标志位,用于线索化,用起来也比较灵活,主要解决了普通二叉树的指针浪费问题,

与此同时,线索二叉树便于寻找某一个节点的前驱和后继,适用于经常需要遍历某一个节点的前驱和后继。

Java代码:

1 package 线索二叉树;
2
3 public class BiTreeNode {
4     char data;
5     BiTreeNode lchild,rchild;
6     PointerTag lTag,rTag;
7 }

1 package 线索二叉树;
2
3 public enum PointerTag {
4     Link,Thread
5 }

 1 package 线索二叉树;
 2
 3 import java.io.IOException;
 4
 5
 6 public class BiThrTree {
 7
 8     //输出
 9     void visit(char c) {
10         System.out.print(c+" ");
11     }
12
13     //建树
14     int index = 0;
15     public BiTreeNode createThrTree(BiTreeNode node,String str) throws IOException {
16         char ch ;
17         if(index < str.length()) {
18             ch = str.charAt(index);
19             index++;
20         }else {
21             return null;
22         }
23         if(ch == ‘#‘) {
24             node = null;
25         }else {
26             if(node == null) {
27                 node = new BiTreeNode();
28             }
29             node.data = ch;
30             node.lchild = createThrTree(node.lchild,str);
31             if(node.lchild!=null) {
32                 node.lTag = PointerTag.Link;
33             }
34             node.rchild = createThrTree(node.rchild,str);
35             if(node.rchild!=null) {
36                 node.rTag = PointerTag.Link;
37             }
38         }
39         return node;
40     }
41
42     //线索化
43     BiTreeNode pre;
44     void inThreading(BiTreeNode node) {
45         if(node!=null) {
46             inThreading(node.lchild);
47             if(node.lchild ==null) {
48                 node.lTag = PointerTag.Thread;
49                 node.lchild = pre;  //pre == null
50             }
51             if(pre.rchild == null) {
52                 pre.rTag  =  PointerTag.Thread;
53                 pre.rchild = node;
54             }
55             pre = node ;
56             inThreading(node.rchild);
57         }
58     }
59
60     //插入头结点
61     void InOrderThreading(BiTreeNode node,BiTreeNode head) {
62         if(head == null)
63         head = new BiTreeNode();
64         head.lTag = PointerTag.Link;
65         head.rTag = PointerTag.Thread;
66         head.rchild = head;
67         if(node == null) {
68             head.lchild = head;
69         }else {
70             head.lchild = node;
71             pre = head;
72             inThreading(node);
73             pre.rchild = head;
74             pre.rTag = PointerTag.Thread;
75             head.rchild = pre;
76         }
77     }
78
79     //遍历二叉树
80     void InOrderTraverse_Thr(BiTreeNode node) {
81         BiTreeNode p;
82         p = node.lchild; //指向头结点
83
84         while(p != node) {
85             while(p.lTag == PointerTag.Link) {
86                 p = p.lchild;
87             }
88             visit(p.data);
89             //此时p指向中序遍历序列的第一个结点(最左下的结点)
90             while( p.rTag == PointerTag.Thread && p.rchild!=node) {
91                 p = p.rchild;
92                 visit(p.data);
93             }
94             p = p.rchild;
95         }
96     }
97 }

 1 package 线索二叉树;
 2
 3 import java.io.IOException;
 4 /**
 5  * 测试:ABDH##I##EJ###CF##G##
 6  * @author liuzeyu12a
 7  *
 8  */
 9 public class Test {
10
11     public static void main(String[] args) throws IOException {
12         BiTreeNode head = new BiTreeNode();
13         BiTreeNode node = new BiTreeNode();
14         BiThrTree tree = new BiThrTree();
15         System.out.println("二叉树建立:ABDH##I##EJ###CF##G##");
16         tree.createThrTree(node,"ABDH##I##EJ###CF##G##");
17
18         tree.InOrderThreading(node, head);
19
20         System.out.println("先序遍历输出:");
21         tree.InOrderTraverse_Thr(head);
22     }
23
24 }

参考资料

1、大佬那边抠到几张图:https://www.cnblogs.com/guweiwei/p/7090050.html

2、《大话数据结构》

原文地址:https://www.cnblogs.com/liuzeyu12a/p/10434737.html

时间: 2024-10-13 16:33:11

线索二叉树C+Java的相关文章

数据结构学习之线索二叉树(java/c++版)

#include <iostream> #include <windows.h> using namespace std; #pragma warning(disable:4996) //可不可以利用c++的泛化编程的思想去改边它 typedef int Tree_type; //树中的存储结构的类型(先去掉---用泛化的思想) //注意枚举和结构体一样的命名和定以方式 typedef enum PoitnterTag { link, Thread } PointerTag; //

二叉树之线索二叉树

相对于顺序存储结构而言,利用链式存储结构的二叉树已经有了很高的存储效率,单是还是有空间上未利用到的地方,比如说叶子结点的左右孩子是空的,指向左右孩子的指针就是空闲的,没有被利用到:而且,有时候给定一个结点,我们需要查找该结点的前驱结点和后继结点,如果按照中序遍历的做法去查找的话,对于一个非叶子结点,其前驱和后继结点查找可以以下算法: 1.preNode=node.left;//前去结点就是该结点的左孩子 2.subNode=search(node.right)://后继结点是该结点的右子树的最左

数据结构与算法(八)-二叉树(斜二叉树、满二叉树、完全二叉树、线索二叉树)

前言:前面了解了树的概念和基本的存储结构类型及树的分类,而在树中应用最广泛的种类是二叉树 一.简介 在树型结构中,如果每个父节点只有两个子节点,那么这样的树被称为二叉树(Binary tree).其中,一个父结点的两个字节点分别叫做“左子节点”和“右子节点”.不过也不是所有父节点都有两个子节点,只有左子节点或者只有右子节点的情况也存在.另外,也会存在叶子结点,也就是一个子节点都没有的节点,唯一的限制就是每一个节点的子节点不能超过两个. 之前谈过的单向链表,是一种通过“指向下一个元素的指针”来连接

【数据结构】之二叉树的java实现

二叉树的定义: 二叉树是树形结构的一个重要类型.许多实际问题抽象出来的数据结构往往是二叉树的形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要. 二叉树(BinaryTree)是n(n≥0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵互不相交的.分别称作这个根的左子树和右子树的二叉树组成. 这个定义是递归的.由于左.右子树也是二叉树, 因此子树也可为空树.下图中展现了五种不同基本形态的二叉树. 其中 (a) 为空树, (b

数据结构之---C语言实现线索二叉树

//线索二叉树,这里在二叉树的基础上增加了线索化 //杨鑫 #include <stdio.h> #include <stdlib.h> typedef char ElemType; typedef enum {Link,Thread} childTag; //Link表示结点.Thread表示线索 typedef struct bitNode { ElemType data; struct bitNode *lchild, *rchild; int ltag, rtag; } b

一步一步写数据结构(线索二叉树)

线索二叉树,在原始二叉树的基础上对节点进行“扩容”,使之变成了一颗节点信息更加丰富,利用率更高的二叉树.具体来说增加了两个指示标签,ltag和rtag,每个标签有两个值,1和0,0代表存在孩子,指针指向相应孩子,1代表没有对应的孩子,指针表示线索,指向其前驱或后继.这样虽然节点多占用了空间(其实很少,只是两个枚举常量而已),换来的却是让原来结构中存在的大量空指针利用起来,变成线索,指示前驱后继,从而使得空间利用效率大大提高, 并且有了线索以后,对后续的查找等操作提高很多效率. 下面是代码,本来以

一步两步学算法之中序遍历线索二叉树

1 typedef enum 2 { 3 SubTree, //子树 4 Thread //线索 5 }NodeFlag; 6 7 typedef struct ThreadTree 8 { 9 DATA data; 10 NodeFlag lflag; 11 NodeFlag rflag; 12 struct ThreadTree *left; 13 struct ThreadTree *right; 14 }ThreadBinTree; 15 16 ThreadBinTree *Previo

线索二叉树的实现

<span style="font-size:18px;">/* 1.二叉树遍历算法提供了二叉树的一次性遍历,可是二叉树遍历算法无法实现用户程序像分步 遍历单链表那样分步遍历二叉树.线索二叉树就是专门为实现分步遍历二叉树而设计的.线索二叉树能够实现像双向 链表那样,既能够从前向后分步遍历二叉树,又能够从后向前分步遍历二叉树 2.当按某种规则遍历二叉树时,保存遍历时得到的节点的后继节点信息和前驱节点信息的最经常使用的方法是建立线索二叉树 3.线索二叉树的规定:当某节点的左指针

javascript实现数据结构:线索二叉树

遍历二叉树是按一定的规则将树中的结点排列成一个线性序列,即是对非线性结构的线性化操作.如何找到遍历过程中动态得到的每个结点的直接前驱和直接后继(第一个和最后一个除外)?如何保存这些信息? 设一棵二叉树有n个结点,则有n-1条边(指针连线) , 而n个结点共有2n个指针域(Lchild和Rchild) ,显然有n+1个空闲指针域未用.则可以利用这些空闲的指针域来存放结点的直接前驱和直接后继信息. 对结点的指针域做如下规定: 1.若结点有左子树,则其leftChild域指示其左孩子,否则令leftC