线索二叉树

合集下载

线索二叉树

线索二叉树

6.4 线索化二叉树从前面的讨论可知,遍历二叉树就是将非线性结构的二叉树线性化,即按一定规则将二叉树中的结点排列成一个线性序列依次访问。

如图6.20(a)所示的二叉树,经中序遍历得到线性序列:BADEC,经前序遍历得到线性序列:ABCDE,经后序遍历得到线性序列:BEDCA。

在这些线性序列中,二叉树中的每个结点(除第一个和最后一个外)有且仅有唯一的一个前趋和唯一的一个后继,很容易找到各个结点的直接前驱和直接后继。

但当以二叉链表作为二叉树的存储结构时,只能找到结点的左、右孩子,而不能直接找到前驱和后继,只有在遍历的动态过程中得到这些信息。

如果将这些信息在第一次遍历时保存起来,在需要再次对二叉树进行“遍历”时就可以将二叉树视为线性结构进行访问,从而简化遍历操作。

那么,如何存储遍历中得到的结点前驱和后继的信息呢?一个简单的办法是在每个结点上增加两个指针域fwd和bkwd,分别指向存储遍历中得到的结点前驱和后继。

fwd L child data R child bkwd这是采用多重链表来表示二叉树。

这种方法虽简单易行,但这种结构的存储密度将大大降低,浪费存储空间。

另一种方法,是利用原有链域L child 和R child的空链域。

在n个结点的二叉链表中有2n个孩子链域,其中仅有n-1个链域是用来指示结点的左右孩子,而另外n+1个链域是空链域。

现在把这些空链域利用起来,使其指向结点的前驱或后继;对那些原来就不为空的链域,则仍然指向左或右孩子。

如果把指向前驱和后继的指针称为线索(Thread),那么,如何区分指向左、右孩子的指针和指向前驱、后继的线索呢?在原结点结构上增加标志域定义为:0 Lchild为左指针,指向左孩子0 Rchild为右指针,指向右孩子ltag=rtag=1 Lchild为左线索,指向前驱 1 Rchild为右线索,指向后继以这种结点构成的二叉链表作为二叉树的存储结构,叫做线索链表,其C语言类型说明如下:Typedef struct ThreadTNode{enum{0,1} ltag, rtag;Elem Type data;Struct ThreadTNode *Lchild, *Rchild;}ThreadTNode, *ThreadTree;为了节省内存空间,我们用C语言的位段方法将结点中的左标志域和右标志域与数据域合并在一个存储单元中(即各用一位表示左标志和右标志,其余各位表示结点值)。

线索二叉树

线索二叉树

0 A0 0 B1
0 C0
1 D0
1 E1
1F1
1 G1
(b) root
0
1
ห้องสมุดไป่ตู้
0 A0
0 B1
0 C0
0 A0
0 B1
0 C0
1 D0
1 E1
1F1
1 D0
1 E1
1F1
1 G1
1 G1
(c)
(d)
线索二叉树 b—中序 c—前序 d—后序
一旦建立了某种方式的线索二叉树后,用户程序就可以 像操作双向链表一样操作该线索二叉树。
if(tree->current == tree->root) tree->nextComplete = 1;
}
int EndOfNext(ThreadBiTree *tree) //判断是否已到中序线索二叉树的最后一个结点 { return tree->nextComplete; }
例8-3 编写一个程序,首先建立如图8-10(a)所示的不带头结点的二叉树, 然后中序线索化该二叉树,最后用循环结构输出该中序线索化二叉树各结 点的序列信息。
这种算法设计要求分别设计三个函数: First():定位在第一个结点位置; Next():移动到下一个结点位置; End():是否已经到最后下一个结点位置; 当然,还需要一个根据二叉树构造线索二叉树的函数。
typedef struct { ThreadBiNode *root;
ThreadBiNode *current; int nextComplete; }ThreadBiTree;
规定:当某结点的左指针为空时,令该指针指向按某种方法遍历二叉树时 得到的该结点的前驱结点;当某结点的右指针为空时,令该指针指向按某种 方法遍历二叉树时得到的该结点的后继结点。仅仅这样做会使我们不能区分 左指针指向的结点到底是左孩子结点还是前驱结点,右指针指向的结点到底 是右孩子结点还是后继结点。因此我们再在结点中增加两个线索标志位来区 分这两种情况。

莫里斯算法

莫里斯算法

莫里斯算法莫里斯算法(Morris Traversal)是一种二叉树遍历的算法,通过只使用一个指针和利用线索二叉树的思想,实现对二叉树的中序遍历,时间复杂度为O(n),空间复杂度为O(1)。

1. 线索二叉树线索二叉树是指将一颗二叉树中所有的空指针域指向该节点的直接前驱节点或直接后继节点的二叉树。

线索化后,原本空闲的指针域存储前驱或后继的指针,可以方便地实现对二叉树的遍历。

在递归遍历的过程中,需要记录每个节点是否已经被遍历。

线索二叉树分为前序线索二叉树、中序线索二叉树、后序线索二叉树。

其中,利用中序线索二叉树可以实现对二叉树的中序遍历。

2. Morris遍历算法Morris遍历算法是一种利用线索二叉树对二叉树进行中序遍历的算法,它的核心思想是在遍历节点的过程中,利用空闲的指针域存储前驱或后继的指针,从而实现对二叉树的遍历。

Morris遍历算法对于空间的要求比较严格,它只需要使用一个指针来实现对二叉树的遍历,因此空间复杂度为O(1),非常适合解决空间限制的问题。

在时间复杂度方面,Morris遍历算法需要在遍历每个节点和寻找其前驱节点时,都需要遍历部分节点,因此时间复杂度为O(n)。

具体而言,Morris遍历算法利用线索化指针判断节点是否被访问过,从而实现对二叉树的遍历。

Morris遍历算法的具体流程如下:1)初始化当前节点为根节点2)重复以下操作,直到当前节点为空a. 如果当前节点没有左孩子节点,则输出当前节点,将其右孩子节点作为下一次要访问的节点,更新当前节点为右孩子节点b. 如果当前节点存在左孩子节点,则寻找当前节点的左子树中的最右侧节点,该节点为当前节点的直接前驱节点i. 如果该节点的右孩子指向当前节点,则说明当前节点是第二次访问该节点,将其右孩子设为null,输出该节点,更新当前节点为右孩子节点ii. 如果该节点的右孩子指向null,则说明当前节点为第一次访问该节点,将其右孩子设为当前节点,更新当前节点为左孩子节点Morris遍历算法的特点是利用空闲的指针域存储前驱或后继的指针,实现对二叉树的遍历。

遍历二叉树与线索二叉树PPT

遍历二叉树与线索二叉树PPT
A B C X Y D E
作业:P217-218
后序列:DGJHEBIFCA, 中序列:DBGEHJACIF, 求:1、画出该二叉树; 2、先序; 3、画出该二叉树对应的森林。
由此可以看出:
(1)遍历操作实际上是将非线性结构线性化的过程, 其结果为线性序列; (2)遍历操作是一个递归的过程,因此,这三种遍历 操作的算法可以用递归函数实现。 先序遍历递归算法: DLR ( BiTree T ) { if (T) //非空二叉树 { printf(“%d”,T->data); //访问根结点D DLR(T->lchild); //递归遍历左子树 DLR(T->rchild); //递归遍历右子树 } return(0); }
这就是线索二叉树(Threaded Binary Tree)
如何预存这类信息?有两种解决方法: 缺点:空间效 ① 每个结点增加两个域:fwd和bwd; 率太低! fwd lchild data rchild bwd ② 与原有的左右孩子指针域“复用”,充分利用那n+1 个空链域。 lchild data rchild 如何判断是孩 子指针还是线 规 定: 索指针? 1)若结点有左子树,则lchild指向其左 孩子;否则,lchild指向其直接前驱(即 线索); 如何区 别? 2)若结点有右子树,则rchild指向其右 孩子;否则,rchild指向其直接后继(即线索) 。
中序遍历递归算法: LDR(BiTree T) { if(T) { LDR(T->lchild); printf(“%d”,T->data); LDR(T->rchild); } return(0); }
后序遍历递归算法 LRD (BiTree T) { if(T) { LRD(T->lchild); LRD(T->rchild); printf(“%d”,T->data); } return(0);}

线索二叉树

线索二叉树

6·4 线索二叉树1、线索二叉树的结点结构二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。

对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。

为了容易找到前驱和后继,有两种方法。

一是在结点结构中增加向前和向后的指针fwd和bkd,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。

现将二叉树的结点结构重新定义如下:其中:ltag=0 时ltag=1 时lchild指向前驱;rtag=0 时rchild指向左子女;rtag=1 时rchild指向后继;以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,指向前驱和后继的指针叫线索,加上线索的二叉树叫线索二叉树,对二叉树进行某种形式遍历使其变为线索二叉树的过程叫线索化。

学习线索化时,有三点必须注意:一是何种“序”的线索化,是先序、中序还是后序;二是要“前驱”线索化、“后继”线索化还是“全”线索化(前驱后继都要);三是只有空指针处才能加线索。

2、对二叉树进行中序线索化的算法bithptr *pre; /* 全程变量*/void INTHREAD(bithptr *p){if(p!=NULL){ INTHREAD(p->lchild); /* 左子树线索化*/if(p->lchild==NULL) { p->ltag=1;p->lchild=pre;}if(p->rchild==NULL) p->rtag=1;if(pre!=NULL && pre->rtag==1) pre->rchild=p;pre=p; /* 前驱指向当前结点*/INTHREAD(p->rchild); /* 右子树线索化*/}3、在线索二叉树上查找前驱和后继(1)中序线索二叉树:若结点的ltag=1,lchild指向其前驱;否则,该结点的前驱是以该结点为根的左子树上按中序遍历的最后一个结点。

线索二叉树

线索二叉树

void InThreading(BiThrTree p) { if ( p ) { InThreading ( p -> lchild ); //左子树中序线索化 左子树中序线索化 if ( p->lchild = = NULL ) { p->LTag=Thread; p->lchild= pre; } //左线索为 ; 左线索为pre 左线索为 if ( pre->rchild == NULL ) { pre->RTag=Thread; pre->rchild= p ;} //后继线索 后继线索 pre = p; //保持 指向 的前驱 保持pre指向 保持 指向p的前驱 InThreading(p -> rchild ); //右子树中序线索化 右子树中序线索化 } }//InThreading
指向该线性序列中的“前驱”和 “后继” 的指针 指针,称作“线索” 线索” 指针 线索
A B C D E F G H K
^B
C^ E^
^D^
包含 “线索” 的存储 结构,称作 “线索链 线索链 表” 与其相应的二叉树, 线索二叉树” 称作 “线索二叉树 线索二叉树中序线索二叉树源自A0 1B
C
NULL
Status InOrderThreading (BiThrTree &Thrt , BiThrTree T ) { //将二叉树 改变为其中序线索二叉树 将二叉树T改变为其中序线索二叉树 if ( !Thrt = (BiThrTree ) malloc (sizeof(BiThrNode))) exit ( OVERFLOW ); Thrt-> LTag = Link ; Thrt ->RTag = Thread; Thrt -> rchild = Thrt; if ( !T ) Thrt -> lchild = Thrt; //空树 空树 else { Thrt -> lchild = T ; pre = Thrt ; InTreading( T ); //中序遍历进行中序线索化 中序遍历进行中序线索化 pre-> rchild = Thrt; pre->RTag= Thread; Thrt -> rchild = pre; } return OK; }

简述二叉树的五种形态

简述二叉树的五种形态

简述二叉树的五种形态二叉树是一种常用的数据结构,它由节点组成,每个节点最多有两个子节点。

根据节点的分布情况,二叉树可以分为五种形态,分别是满二叉树、完全二叉树、平衡二叉树、搜索二叉树和线索二叉树。

一、满二叉树满二叉树是指除了叶子节点外,每个节点都有两个子节点的二叉树。

也就是说,满二叉树的所有层都是满的,并且最后一层的叶子节点都靠左排列。

满二叉树的节点数可以通过公式计算得到,假设树的高度为h,则节点数为2^h - 1。

满二叉树的特点是结构简单,查找速度快。

在满二叉树中,任意两个节点的路径长度都相同。

二、完全二叉树完全二叉树是指除了最后一层之外,其他层都是满的,并且最后一层的叶子节点都靠左排列的二叉树。

完全二叉树的特点是节点数较少,结构相对简单。

完全二叉树通常用数组来表示,因为它的节点之间的关系可以通过数组的下标来表示。

在完全二叉树中,任意一个节点的左子节点的下标为2i,右子节点的下标为2i+1。

三、平衡二叉树平衡二叉树是指左右子树的高度差不超过1的二叉树。

平衡二叉树的特点是查找、插入和删除的时间复杂度都为O(logn),其中n是节点的数量。

平衡二叉树的高度可以通过节点的平衡因子来计算,平衡因子定义为左子树的高度减去右子树的高度。

平衡因子的取值范围为-1、0和1,当平衡因子的绝对值大于1时,需要通过旋转操作来调整树的平衡性。

四、搜索二叉树搜索二叉树,也称为二叉搜索树或排序二叉树,是一种特殊的二叉树。

它的特点是对于树中的任意一个节点,其左子树中的所有节点都小于它,右子树中的所有节点都大于它。

搜索二叉树的中序遍历结果是一个递增的有序序列。

搜索二叉树的特点是可以快速地查找某个节点,时间复杂度为O(logn),其中n是节点的数量。

但是,如果搜索二叉树不平衡,即左子树或右子树过深,则会导致查找的时间复杂度退化为O(n)。

五、线索二叉树线索二叉树是对二叉树进行了优化的数据结构,它通过添加指向前驱和后继节点的线索,使得遍历操作更加高效。

线索二叉树是一种

线索二叉树是一种

线索二叉树(threaded binary tree)是一种特殊的二叉树,它在普通的二叉树的基础上增加了指向前驱和后继的指针。

线索二叉树的每个节点都有两个指针域,一个指向左儿子,一个指向右儿子。

如果左儿子或右儿子为空,则将该指针指向相应节点的前驱或后继。

线索二叉树有两种:中序线索二叉树和前序线索二叉树。

中序线索二叉树是指在二叉树的中序遍历序列上建立的线索二叉树,前序线索二叉树是指在二叉树的前序遍历序列上建立的线索二叉树。

线索二叉树的优点是可以直接访问二叉树的任意一个节点的前驱或后继,可以方便地遍历二叉树。

它的缺点是增加了空间开销,因为每个节点都需要额外的两个指针域,而且插入和删除节点时也需要维护线索二叉树的正确性。

线索二叉树是在普通的二叉树的基础上增加了指向前驱和后继的指针,使得遍历二叉树变得更加方便。

在线索二叉树中,每个节点都有两个指针域,一个指向左儿子,一个指向右儿子。

如果左儿子或右儿子为空,则将该指针指向相应节点的前驱或后继。

线索二叉树有两种:中序线索二叉树和前序线索二叉树。

中序线索二叉树是指在二叉树的中序遍历序列上建立的线索二叉树,前序线索二叉树是指在二叉树的前序遍历序列上建立的线索二叉树。

线索二叉树的建立需要对二叉树进行遍历,在遍历的过程中,如果当前节点的左儿子为空,则将左指针指向前驱;如果当前节点的右儿子为空,则将右指针指向后继。

这样就可以在遍历完整棵二叉树之后得到一颗线索二叉树。

在线索二叉树中,每个节点都有两个指针域,一个指向左儿子,一个指向右儿子。

如果左儿子或右儿子为空,则将该指针指向相应节点的前驱或后继。

这样就可以直接访问每个节点的前驱或后继,方便遍历整棵二叉树。

线索二叉树的优点是可以方便地遍历整棵二叉树,而且可以直接访问每个节点的前驱或后继。

缺点是增加了空间开销,因为每个节点都需要额外的两个指针域,而且插入和删除节点时也需要维护线索二叉树的正确性。

线索二叉树常用于二叉树的遍历,也可以用于实现其他基于二叉树的数据结构,如二叉搜索树、平衡二叉树等。

6-3线索二叉树

6-3线索二叉树

第四节线索二叉树如何快捷地找出结点的孩子?如何快捷地找出结点的前驱、后继?①遍历二叉树,形成一个线性序列,再在序列中查找。

笨!②每个结点增加前驱域、后继域。

③利用结点的空链域存储前驱域和后继域。

思想:若结点有左子树,则其lchild域指示其左子树的位置若结点无左子树,则其lchild域指示其前驱结点的位置若结点有右子树,则其rchild域指示其右子树的位置若结点无右子树,则其rchild域指示其后继结点的位置、二叉树的线索化:以某种次序遍历二叉树,遍历过程中对其线索化。

(即将其空链域填上值)试一试:手工实现“二叉树的线索化”程序实现:一、建立线索树1、算法思路:①利用非递归算法:p遍历各结点,pre指向上一个访问的结点。

②每次指针变化前,线索化*p的前驱,*pre的后继。

2、程序typedef enum {START,LEFT,RIGHT} TravFlag;typedef struct{ //栈中元素的类型BiThrNode *p; //指向树中结点TravFlag flag; //*p结点的遍历状态}StackElem;void BiThrTree_PostOrder(BiThrNode *root) //后序线索树{Stack S; StackElem e;BiThrNode *pre=NULL;Stack_Init(S);e.p=root; e.flag=START; Stack_Push(S,e);while(!Stack_Empty(S)){ e=Stack_Gettop(S);switch (e.flag){case START: //第一次{ e.flag=NextDirect(e.flag); Stack_Settop(S,e);if(e.p->lch){e.p=e.p->lch; e.flag=START; Stack_Push(S,e);}break;}case LEFT: //从左子树回来{ e.flag=NextDirect(e.flag); Stack_Settop(S,e);if(e_p->rch){e.p=e.p->rch; e.flag=START; Stack_Push(S,e);}break;}case RIGHT: //从右子树回来。

线索二叉树的应用场景

线索二叉树的应用场景

线索二叉树的应用场景
线索二叉树是一种特殊类型的二叉树,其主要特点是在二叉树的空闲指针中存储指向前驱节点和后继节点的线索,从而可以方便地访问任意节点的前驱和后继。

这种数据结构在实际应用中具有多种使用场景,尤其是在需要频繁遍历二叉树或快速查找节点前驱和后继的情况下。

遍历优化:线索二叉树可以大大提高二叉树的遍历效率。

在传统的二叉树遍历中,如果需要访问某个节点的前驱或后继节点,通常需要重新从根节点开始遍历。

而线索二叉树则可以直接通过线索找到前驱或后继节点,无需重新遍历,从而大大提高了遍历效率。

路径总和问题:在解决路径总和问题时,线索二叉树可以提供一种高效的解决方案。

通过存储前驱和后继节点的线索,可以快速地回溯到之前的节点,从而方便地计算路径总和。

数据压缩与存储:在某些需要压缩存储数据的情况下,线索二叉树也可以发挥作用。

由于线索二叉树充分利用了空闲指针,因此可以在不增加额外存储空间的情况下,存储更多的信息。

图形渲染与优化:在计算机图形学中,线索二叉树也被广泛应用于场景图、渲染树等数据结构的优化。

通过利用线索二叉树的特性,可以更加高效地遍历和渲染场景中的对象。

总的来说,线索二叉树是一种非常实用的数据结构,特别适用于需要频繁遍历二叉树或快速查找节点前驱和后继的情况。

在实际应用中,可以根据具体需求选择合适的遍历方法和存储策略,以实现最佳的性能和效率。

线索化二叉树详解

线索化二叉树详解

线索化⼆叉树详解线索化⼆叉树详解说明1. 线索化⼆叉树,由字⾯意思,就是将⼆叉树的节点拿线索连接起来2. 实质上,也就是将⼆叉树的叶⼦节点左右指针域彼此连接⼀个节点3. ⼆叉树的⾮叶⼦节点的左右指针域都各⾃连接了⼀个节点,但是叶⼦节点的左右指针域是空的,因此考虑将叶⼦节点的左右指针域按照某种遍历次序连接起来4. 按照⼆叉树的遍历⽅式,有前序中序后续三种遍历⽅式,因此可以形成三种链式结构5. 每个叶⼦节点前⼀个节点称为前驱节点,后⼀个节点称为后继节点,如果当前节点没有前驱或者后继节点,则直接置为空6. 以中序线索化⼆叉树为例,编写中序线索化⼆叉树的⽅法7. 先判断当前节点是否为空,如果为空,则直接返回8. 否则先向左递归线索化⼆叉树的左⼦树9. 然后再线索化当前节点,定义属性pre保存当前节点的前⼀个节点,因此当前节点的前⼀个节点置为pre10. 注意当前节点的后⼀个节点,需要⽤pre保存当前节点,然后遍历到后⼀个节点,然后⽤pre指向11. 注意第⼀个节点和最后⼀个节点12. 中序线索化如下,前序和后续类似源码及分析节点类//创建节点class HeroNode{//编号private int no;//姓名private String name;//左⼦树private HeroNode left;//右⼦树private HeroNode right;//线索化的前驱节点类型,是节点还是树,假定 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 HeroNode(int no, String name) {this.no = no; = name;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public String getName() {return name;}public void setName(String name) { = name;}public HeroNode getLeft() {return left;}public void setLeft(HeroNode left) {this.left = left;}public HeroNode getRight() {return right;}public void setRight(HeroNode right) {this.right = right;}@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +'}';}//删除节点/**** @param no 要删除的节点编号*/public void delNode(int no){//判断当前节点的左⼦树是否为空,如果不为空,再判断是否为要删除的节点if (this.left != null && this.left.no == no){this.left = null;}//判断当前节点的右⼦树是否为空,如果不为空,再判断是否为要删除的节点if (this.right != null && this.right.no == no){this.right = null;}//否则向左向右递归if (this.left != null){this.left.delNode(no);}if (this.right != null){this.right.delNode(no);}}//前序中序后序遍历主要区别在于⽗节点的输出位置不同,/*** 前序遍历先输出⽗节点信息,然后判断左⼦树是否为空,如果不为空,则递归前序遍历 * 然后再判断右⼦树是否为空,如果不为空,则递归遍历前序遍历*///前序遍历public void preOrder(){//先输⼊当前节点信息System.out.println(this);//然后判断当前节点的左⼦树是否为空if (this.left != null){this.left.preOrder();}//再判断当前节点的右⼦树是否为空if (this.right != null){this.right.preOrder();}}//中序遍历public void infixOrder(){//先判断当前节点的左⼦树是否为空if (this.left != null){this.left.infixOrder();}//再输出当前节点的信息System.out.println(this);//然后再判断当前节点的右⼦树是否为空if (this.right != null){this.right.infixOrder();}}//后序遍历public void postOrder(){//先判断当前节点的左⼦树是否为空if (this.left != null){this.left.postOrder();}//再判断当前节点的右⼦树是否为空if (this.right != null){this.right.postOrder();}//最后输出当前节点的信息System.out.println(this);}//前序查找/*** 前序遍历查找* @param no 要查找的节点编号* @return 返回查找的结果*/public HeroNode preOrderSearch(int no){//先判断当前节点是不是要查找的节点if (this.no == no){return this;}//如果当前节点不是要查找的节点,则判断左⼦树是否为空,若不为空,则递归前序查找 HeroNode resNode = null;if (this.left != null){resNode = this.left.preOrderSearch(no);}//如果在左⼦树找到,则直接返回if (resNode != null){return resNode;}//如果左⼦树也没有找到,则判断右⼦树是否为空,并递归if (this.right != null){resNode = this.right.preOrderSearch(no);}return resNode;}//中序查找/*** 中序遍历查找* @param no 要查找的节点编号* @return 返回查找的结果*/public HeroNode infixOrderSearch(int no){//先判断当前节点左⼦树是否为空,如果不为空则递归中序查找//定义变量保存查找的结果HeroNode resNode = null;if (this.left != null){resNode = this.left.preOrderSearch(no);}//如果查找到,则直接返回if (resNode != null){return resNode;}//如果没有找到,判断当前节点是不是要查找的节点if (this.no == no){return this;}//如果还没有找到,则判断右⼦树是否为空,不为空则递归中序查找if (this.right != null){resNode = this.right.infixOrderSearch(no);}return resNode;}//后序查找/*** 后续遍历查找* @param no 要查找的节点编号* @return 返回查找的结果*/public HeroNode postOrderSearch(int no){//判断当前节点的左⼦树是否为空,如果不为空,则递归后续查找HeroNode resNode = null;if (this.left != null){resNode = this.left.postOrderSearch(no);}if (resNode != null){return resNode;}if (this.right != null){resNode = this.right.postOrderSearch(no);}if (resNode != null){return resNode;}if (this.no == no){return this;}return resNode;}}线索化⼆叉树类//创建⼀颗线索化⼆叉树class ThreaderBinaryTree{//⼆叉树必有根节点private HeroNode root;//定义变量指向前驱节点,默认为空private HeroNode pre = null;public void setRoot(HeroNode root) {this.root = root;}//编写中序线索化⼆叉树的⽅法/**** @param node node为当前要中序线索化的节点*/public void infixThreadedBinaryTree(HeroNode node){//先判断当前节点是否为空if (node == null){return;}//如果不为空,先线索化左⼦树infixThreadedBinaryTree(node.getLeft());//再线索化当前节点//当前节点的前驱节点为pre,后继节点需要在下⼀个节点连通,因为是单向的 //设置当前节点的前驱节点,并设置前驱节点类型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;//最后再线索化右⼦树infixThreadedBinaryTree(node.getRight());}//重载线索化的⽅法public void infixThreadedBinaryTree(){this.infixThreadedBinaryTree(root);}//删除节点/**** @param no 要删除的节点编号*/public void delNode(int no){//先判断⼆叉树是否为空if (this.root != null){//再判断当前root节点是不是要删除的节点if (this.root.getNo() == no){root = null;}else {this.root.delNode(no);}}else {System.out.println("⼆叉树为空,不能删除...");}}//前序遍历public void preOrder(){if (this.root != null){this.root.preOrder();}else {System.out.println("⼆叉树为空...");}}//中序遍历public void infixOrder(){if (this.root != null){this.root.infixOrder();}else {System.out.println("⼆叉树为空...");}}//后续遍历public void postOrder(){if (this.root != null){this.root.postOrder();}else {System.out.println("⼆叉树为空...");}}//前序查找public HeroNode preOrderSearch(int no){if (this.root != null){return this.root.preOrderSearch(no);}else {return null;}}//中序查找public HeroNode infixOrderSearch(int no){if (this.root != null){return this.root.infixOrderSearch(no);}else {return null;}}//后续查找public HeroNode postOrderSearch(int no){if (this.root != null){return this.root.postOrderSearch(no);}else {return null;}}}测试类public static void main(String[] args) {ThreaderBinaryTree threaderBinaryTree = new ThreaderBinaryTree(); HeroNode root = new HeroNode(1,"tom");HeroNode node2 = new HeroNode(3,"jack");HeroNode node3 = new HeroNode(6,"smith");HeroNode node4 = new HeroNode(8,"king");HeroNode node5 = new HeroNode(10,"mary");HeroNode node6 = new HeroNode(14,"dop");root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);threaderBinaryTree.setRoot(root);//进⾏线索化threaderBinaryTree.infixThreadedBinaryTree();//测试线索化的结果System.out.println("node5的前⼀个节点 = " + node5.getLeft());System.out.println("node5的后⼀个节点 = " + node5.getRight());}。

线索二叉树(图)

线索二叉树(图)

线索二叉树:遍历二叉树:实际上是对二叉树(非线性结构)进行的线性化操作,即以一定规则将二叉树中的结点排列成一个线性序列(先序序列、中序序列和后序序列)。

举例:图6.9所示的二叉树中的结点,按中序遍历可得到中序序列:a+b*c-d-e/f,其中‘c’的前驱为‘*’,后继为‘-’。

当以二叉链表作为二叉树的存储结构时,只能找到结点的左右孩子信息,而不能直接得到结点在任一线性序列中的前驱和后继信息,因为这种信息只有在遍历的动态过程中才能得到。

如何保存这种在遍历过程中得到的结点的前驱和后继信息呢?方法一:在二叉链表的每个结点上增加两个指针域fwd和bkwd,分别指向在依任一次序遍历时得到的前驱和后继信息。

(大大影响存储密度)方法二:利用二叉链表中的空链域来存放结点的前驱和后继信息。

(在有n个结点的二叉链表中必定存在n+1个空链域!)(不影响存储密度)为此,可以将二叉链表中的结点结构作如下修改:lchild LTag data RTag rchild其中:Ltag = 0 lchild域指示结点的左孩子1 lchild域指示结点的前驱Rtag = 0 rchild域指示结点的右孩子1 rchild域指示结点的后继我们把如此修改后的二叉链表称为二叉树的线索链表,其中指向结点前驱和后继的指针称为线索。

相应地,把添加线索后的二叉树称为线索二叉树(Threaded Binary Tree)。

对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。

举例:图6.11(a)所示为中序线索二叉树,与其对应的中序线索链表如图 6.11(b)所示。

其中实线为指针(指向左、右子树),虚线为线索(指向前驱和后继)。

在线索树上进行遍历,只要找到序列中的第一个结点,然后依次找结点的后继直到其后继为空时而停止。

关键是如何在线索树中找结点的后继?二叉树的二叉线索存储表示:(p133-134)线索二叉树的遍历:(以中序线索二叉树为例,即中序遍历二叉线索树)算法6.5二叉树的线索化:(以中序线索化为例,即通过中序遍历建立中序线索链表)算法6.6,算法6.7。

拓展阅读6——线索二叉树的操作

拓展阅读6——线索二叉树的操作

线索二叉树的操作*线索二叉树的主要操作是建立线索二叉树和遍历线索二叉树。

下面以中序线索二叉树为例介绍线索二叉树的建立和遍历,其他两种线索二叉树与此类似。

1.中序线索二叉树的建立线索二叉树的建立是基于对二叉树进行遍历的基础上完成的,与前面讲的二叉树遍历的应用所不同的是,在对二叉树进行遍历时所做的操作是将结点的空指针改为指向其前驱或后继结点。

线索二叉树的建立分成两部分来完成:一部分是建立线索链表的头结点,另一部分是对已有的二叉链表进行线索化。

对二叉链表线索化也是采用递归的形式,先对左子树进行线索化,然后对根结点线索化,最后对右子树进行线索化。

在对根结点进行线索化时,需要两个指针p和pre,其中p 指向当前根结点,pre指向其前驱结点。

若当前根结点的lchild指针为空,则将其指向其前驱结点,同时其标志值改为1,否则不做修改。

若其前驱结点的rchild指针为空,则将其指向其后继结点,即指针p所指的结点,同时其标志值改为1,否则不做修改。

对二叉链表线索化的算法如下:【算法1】:void InOrderThread(PTBNode p,PTBNode *pre)/*二叉树的中序线索化*/{if(p!=NULL){InOrderThread(p->lchild,pre); /*中序线索化左子树*/if(p->lchild==NULL) /*当前结点没有左子树,将其lchild指针指向前驱结点*/{p->lchild=*pre;p->ltag=1;}if((*pre)->rchild==NULL)/*前驱结点没有右子树,将其rchild指针指向后继结点*/{(*pre)->rtag=1;(*pre)->rchild=p;}*pre=p;InOrderThread(p->rchild,pre); /* 中序线索化右子树*/}}建立线索链表的头结点时,首先创建头结点,然后让头结点的lchild指针指向二叉链表的根结点,置ltag为0;让头结点的rchild指针指向遍历序列中最后一个结点,置rtag为1。

彻底理解线索二叉树

彻底理解线索二叉树

彻底理解线索⼆叉树⼀、线索⼆叉树的原理通过考察各种⼆叉链表,不管⼉叉树的形态如何,空链域的个数总是多过⾮空链域的个数。

准确的说,n各结点的⼆叉链表共有2n个链域,⾮空链域为n-1个,但其中的空链域却有n+1个。

如下图所⽰。

因此,提出了⼀种⽅法,利⽤原来的空链域存放指针,指向树中其他结点。

这种指针称为线索。

记ptr指向⼆叉链表中的⼀个结点,以下是建⽴线索的规则:(1)如果ptr->lchild为空,则存放指向中序遍历序列中该结点的前驱结点。

这个结点称为ptr的中序前驱;(2)如果ptr->rchild为空,则存放指向中序遍历序列中该结点的后继结点。

这个结点称为ptr的中序后继;显然,在决定lchild是指向左孩⼦还是前驱,rchild是指向右孩⼦还是后继,需要⼀个区分标志的。

因此,我们在每个结点再增设两个标志域ltag和rtag,注意ltag和rtag只是区分0或1数字的布尔型变量,其占⽤内存空间要⼩于像lchild和rchild的指针变量。

结点结构如下所⽰。

其中:(1)ltag为0时指向该结点的左孩⼦,为1时指向该结点的前驱;(2)rtag为0时指向该结点的右孩⼦,为1时指向该结点的后继;(3)因此对于上图的⼆叉链表图可以修改为下图的养⼦。

⼆、线索⼆叉树结构实现⼆叉线索树存储结构定义如下:线索化的实质就是将⼆叉链表中的空指针改为指向前驱或后继的线索。

由于前驱和后继信息只有在遍历该⼆叉树时才能得到,所以,线索化的过程就是在遍历的过程中修改空指针的过程。

中序遍历线索化的递归函数代码如下:上述代码除了//===之间的代码以外,和⼆叉树中序遍历的递归代码机会完全⼀样。

只不过将打印结点的功能改成了线索化的功能。

中间部分代码做了这样的事情:因为此时p结点的后继还没有访问到,因此只能对它的前驱结点pre的右指针rchild做判断,if(!pre->rchild)表⽰如果为空,则p就是pre的后继,于是pre->rchild = p,并且设置pre->rtag = Thread,完成后继结点的线索化。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

例3:二叉树的中序线索化算法
BiThrTree pre = NULL ; //设置前驱为全局 void InOrderThread(BiThrTree T){ if(T){ InOrderThread( T->lchild ); //左子树中序线索化 if( !T->lchild ){ //左孩子为空,添加前驱线索 T->ltag = 1 ; T->lchild = pre ; //修改前驱线索为pre } if( pre && pre->rtag==1 ) //前驱结点没有右孩子 pre->rchild = T ; //前驱结点的后继为当前结点 if( !T->rchild ) T->rtag = 1; //置右标记,为后继线索做准备 pre = T ; InOrderThread( T->rchild ); //右子树中序线索化 } 2013-7-7 滨州学院计算机科学技术系 }
A B C D E F G H K
E^ C^
^B
^D^
•包含 “线索” 的存储 结构,称作 “线索链 表” •与其相应的二叉树, 称作 “线索二叉树”
滨州学院计算机科学技术系
2013-7-7
中序线索二叉树
0 1
A 0 A 0
0 B 0 NULL
1 C 0
NUL13-7-7
滨州学院计算机科学技术系
一、 何谓线索二叉树?
遍历二叉树的结果是,求得结点的一 线性序列,对非线性结构进行线性化操作。
A
B
C
D
2013-7-7
例如: 先序序列: ABCDEFGHK E 中序序列: F B D CAH G K F E K
滨州学院计算机科学技术系
G H
后序序列: DCBHKGFEA
•若将指向该线性序列中的“前 驱”和 “后继” 的指针,称作 “线索”
数据结构
第六章
树和二叉树
线索二叉树
学习资料邮箱:ds_study@ 密码:ds2013 系内数据结构学习网站:http://10.2.4.200:8080/ds
2013-7-7
滨州学院计算机科学技术系
本讲内容
一、何谓线索二叉树?
二、线索链表的遍历算法 三、二叉树的线索化
2013-7-7
2013-7-7 滨州学院计算机科学技术系
例4:求给定值x的后继结点 例4:求出线索二叉树中给定值x的后继结点
BiThrTree InOrder(BiThrTree T, ElemType x){ p = T->lchild ; //p指向中序线索二叉树的根结点 while(p!=T){ while( p->ltag==0 && p->data!=x ) p = p->lchild ; //在左子树中往左下方向查找x if(p->data==x) return (p); //找到返回p while( p->rtag==1 && p->rchild!=T ){ p = p->rchild ; //后继结点 if(p->data==x) return (p); } p = p->rchild ; //转向右子树寻找 } } 2013-7-7 滨州学院计算机科学技术系
2013-7-7
中序线索化
滨州学院计算机科学技术系
后序线索化
三、二叉树的线索化
例1:画出下面二叉树的 中序线索二叉树 A
方法:在遍历过程 中修改空指针
B NULL D E F C
NULL
G
H
中序遍历序列:D B E G A F H C
2013-7-7 滨州学院计算机科学技术系
例2:二叉树的先序线索化
2013-7-7 滨州学院计算机科学技术系
2. 线索链表的类型定义:
typedef struct BiThrNod { TElemType data; struct BiThrNode *lchild, *rchild; // 左右指针 int LTag, RTag; // 左右标志 } BiThrNode, *BiThrTree;
// Link==0:指针,Thread==1:线索
typedef struct BiThrNod { TElemType data; struct BiThrNode *lchild, *rchild; // 左右指针 PointerThr LTag, RTag; // 左右标志 } BiThrNode, *BiThrTree;
thrt 0 1
0A0 0B0 1D1 1E0 1G1
2013-7-7 滨州学院计算机科学技术系
0C1 1F0 1H1
二、 线索链表的遍历算法 由于在线索链表中添加了遍历中 得到的“前驱”和“后继”的信息, 从而简化了遍历的算法。
2013-7-7
滨州学院计算机科学技术系
例如:遍历中序线索化链表的算法
若该结点的右子树不空, 则rchild域的指针指向其右子树, 且右标志域的值为 0 “指针” ; 否则,rchild域的指针指向其“后继”,
且右标志的值为1 “线索”。
如此定义的二叉链表称作“线索链表”。
2013-7-7
滨州学院计算机科学技术系
2. 线索链表的类型定义:
typedef enum { Link, Thread } PointerThr;
例4:求出线索二叉树中给定值x的后继结点
算法思想:
假设在中序线索二叉树进行操作,采用 带头结点的线索链表作为存储结构。首先, 在中序线索二叉树中查找给定值为x的结点, 由p指向;然后,根据指针p在中序线索二叉 树中所指结点的后继结点的特征进行判断。 特征:若p的右标志为1,p的rchild指向其 后继结点;否则,结点p的右子树中最左边 的结点是p的中序后继结点。
BiThrTree AfterXNode(BiThrTree T){ BiThrTree p =InOrder(T,x);
//在T树上查找给定值x的结点,由p指向
if( p->rtag==1 ) return (p->rchild) ;
//右标志为1,p的rchild指向其后继结点
else{ q = p->rchild ; //右标志为0,进入右子树 while( q->ltag==0 ) q = q ->lchild ; //右子树中最左下的结点为所求后继 return (q);
2013-7-7 滨州学院计算机科学技术系
算法思想: 对二叉树进行先序遍历,在“访问结点” 时根据有无左右孩子判断决定进行加线索的 改造。没有左孩子添加前驱线索,没有右孩 子添加后继线索。 算法要点: 需要设一个指针pre始终指向当前访问结 点的前驱
2013-7-7 滨州学院计算机科学技术系
二叉树的先序线索化算法
BiThrTree pre = NULL ; //设置前驱为全局变量 void PreOrderThread(BiThrTree T){ if(T){ if( !T->lchild ){ //左孩子为空,添加前驱线索 T->ltag = 1 ; T->lchild = pre ; //修改前驱线索为pre } if( pre && pre->rtag==1 ) //前驱结点没有右孩子 pre->rchild = T ; //前驱结点的后继为当前结点 if( !T->rchild ) T->rtag = 1; //置右标记,为后继线索做准备 pre = T ; if( T->ltag==0 ) PreOrderThread( T->lchild ); //左子树前序线索化 PreOrderThread( T->rchild ); //右子树前序线索化 2013-7-7 } 滨州学院计算机科学技术系 }
滨州学院计算机科学技术系
1.对线索链表中结点的约定:
在二叉链表的结点中增加两个标志域, 并作如下规定: 若该结点的左子树不空, 则Lchild域的指针指向其左子树, 且左标志域的值为0 “指针”; 否则,Lchild域的指针指向其“前驱”, 且左标志的值为1
2013-7-7
“线索” 。
滨州学院计算机科学技术系
2013-7-7
滨州学院计算机科学技术系
为方便起见,仿照线性表的存储结构,在二叉树的线索链表上也 添加一个头结点,并令其lchild域的指针指向二叉树的根结点, 其rchild域的指针指向遍历时访问的最后一个结点;反之,令二 叉树遍历序列的第一个结点的lchild域的指针和最后一个结点的 rchild域的指针均指向头结点。
} }
2013-7-7 滨州学院计算机科学技术系
例5:求后序线索树中给定结点的直接前驱 算法思想 二叉树的后序遍历是“左-右-根”,因此, 在后序线索二叉树中,若结点有右孩子,则右 孩子是其后序前驱;否则,左指针(或左线索) 就是其后序前驱。
BiThrTree PostFront(BiThrTree T,BiThrTree p){ if( p->rtag==0 ) //若p有右孩子,则右孩子为其前驱 return (p->rchild) ; else{//若p无右孩子,则左孩子或左线索为其直接前驱 return (p->lchild); } }
※ 中序遍历的第一个结点 ?
左子树上处于“最左下”(没有左子树) 的结点。
※ 在中序线索化链表中结点的后继 ?
若无右子树,则为后继线索所指结点; 否则为对其右子树进行中序遍历时访问 的第一个结点。
2013-7-7 滨州学院计算机科学技术系
void InOrderTraverse_Thr (BiThrTree T) { p = T->lchild; // p指向根结点 while (p != T) { // 空树或遍历结束时,p= =T while (p->LTag==0) p = p->lchild; // 找到第一个结点 print(p->data)//访问结点 while (p->RTag==1&& p->rchild!=T) { //有右线索时找其右线索 p = p->rchild; print (p->data); // 访问结点 } p = p->rchild; // p进至其右子树根 } } // InOrder
相关文档
最新文档