线索二叉树
1 产生背景
现有一棵结点数目为n的二叉树,采用二叉链表的形式存储。对于每个结点均有指向左右孩子的两个指针域,而结点为n的二叉树一共有n-1条有效分支路径。那么,则二叉链表中存在2n-(n-1)=n+1个空指针域。那么,这些空指针造成了空间浪费。
例如:图1所示一棵二叉树一共有10个结点,空指针^有11个。
此外,当对二叉树进行中序遍历时可以得到二叉树的中序序列。例如:图2.1所示二叉树的中序遍历结果为HDIBJEAFCG,可以得知A的前驱结点为E,后继结点为F。但是,这种关系的获得是建立在完成遍历后得到的,那么可不可以在建立二叉树时就记录下前驱后继的关系呢,那么在后续寻找前驱结点和后继结点时将大大提升效率。
2 线索化
现将某结点的空指针域指向该结点的前驱后继,定义规则如下:
若结点的左子树为空,则该结点的左孩子指针指向其前驱结点。
若结点的右子树为空,则该结点的右孩子指针指向其后继结点。
这种指向前驱和后继的指针称为线索。将一棵普通二叉树以某种次序遍历,并添加线索的过程称为线索化。
按照中序遍历规则HDIBJEAFCG,将图1所示二叉树线索化后如图2所示:
图中黑色点画线为指向后继的线索,紫色虚线为指向前驱的线索。
可以看出通过线索化,既解决了空间浪费问题,又解决了前驱后继的记录问题。
3 线索化带来新问题
将一棵二叉树线索化为一棵线索二叉树,那么新的问题产生了。我们如何区分一个结点的lchild指针是指向左孩子还是前驱结点呢?例如:对于图2所示的结点E,如何区分其lchild的指向的结点J是其左孩子还是前驱结点呢?
为了解决这一问题,现需要添加标志位ltag,rtag。并定义规则如下:
ltag为0时,指向左孩子,为1时指向前驱
rtag为0时,指向右孩子,为1时指向后继
添加ltag和rtag属性后的结点结构如下:
图2所示线索二叉树转变为图3所示的二叉树。
先序线索二叉树:
先序序列:ABDGCEHF
中序线索二叉树:
中序序列:DGBAEHCF
后序线索二叉树:
后序序列:GDBHEFCA
4 线索二叉树结点数据结构
//#define Link 0//指针标志
5 中序遍历建立线索二叉树
实现线索化的过程就是在中序遍历同时修改结点空指针的指向。
采用中序遍历的访问顺序实现一棵二叉树的线索化过程代码如下:
//中序遍历进行中序线索化
6 加上头结点,遍历线索二叉树
加上线索的二叉树结构是一个双向链表结构,为了便于遍历线索二叉树,我们为其添加一个头结点,头结点左孩子指向原二叉树的根结点,右孩子指针指向中序遍历的最后一个结点。同时,将第一个结点左孩子指针指向头结点,最后一个结点的右孩子指针指向头结点。
图3所示线索二叉树添加头结点后如图4所示:
带有头结点的线索二叉树遍历代码如下:
//T指向头结点,头结点的lchild链域指针指向二叉树的根结点
查找中序线索二叉树前驱
对于中序线索二叉树上的任一结点,寻找其中序的前驱结点,有以下两种情况:
(1)如果该结点的左标志为1,没有有左孩子,那么其左线索所指向的结点便是它的前驱结点;
(2)如果该结点的左标志为0,有左孩子,中序遍历左子树中往右链中第一个没有右孩子的节点即为前驱节点。即沿着其左子树的右指针链向下查找,当某结点的右标志为1时,它就是所要找的前驱结点。
查找前驱的算法:
查找中序线索二叉树后继
(1)如果该结点的右标志为1,那么其右指针域所指向的结点便是它的后继结点;
(2)如果该结点的右标志为0,表明该结点有右孩子,中序遍历右子树第一个节点即为后继结点。即沿着其右子树的左指针链向下查找,当某结点的左标志为1时,它就是所要找的后继结点。
tbitree
结语
线索二叉树充分利用了指针空间,同时又便于寻找结点的前驱结点和后继结点。线索二叉树适用于经常需要遍历寻找结点前驱或者后继结点的二叉树。