第7章树和二叉树:线索二叉树
7.7 线索二叉树
1. 线索二叉树的基本概念
遍历一棵二叉树,实质上就是将非线性结构进行线性化操作,即可以将二叉树中的结点排列成一个线性序列,对于线性序列中的每个结点,有且只有一个直接前驱和一个直接后继。但是,如果以二叉链表作为二叉树的存储结构,在线性序列中的结点,不能直接得到其前驱和后继,只能在遍历的动态过程中才能得到。
在二叉链表所表示的二叉树中,结点会有空链域,那么可以充分利用这些空链域来存放结点的前驱和后继信息。为此做如下规定:
- 若结点有左子树,则其
lchild域指示其左孩子,否则令lchild域指示其前驱; - 若结点有右子树,则其
rchild域指示其右孩子,否则令rchild域指示其后继。
为了避免混淆,还需改变结点结构,增加两个标志域,其结点形式如图 7.7.1 所示。

其中:
LTag={0lchild域指示结点的左孩子1lchild域指示结点的前驱RTag={0rchild域指示结点的右孩子1rchild域指示结点的后继\begin{split} \text{LTag}=\begin{cases}0~\text{lchild}域指示结点的左孩子\\1~\text{lchild}域指示结点的前驱\end{cases} \\\text{RTag}=\begin{cases}0~\text{rchild}域指示结点的右孩子\\1~\text{rchild}域指示结点的后继\end{cases} \end{split} LTag={0 lchild域指示结点的左孩子1 lchild域指示结点的前驱RTag={0 rchild域指示结点的右孩子1 rchild域指示结点的后继
据此,可以修改二叉链表的结点数据结构为:
typedef struct BiTNode{TElemType data; //结点数据域struct BiTNode *lchild, *rchild; //左右孩子指针 int LTag, RTag; //左右标志
}BiThrTNode, *BiThrTree;
以这种结点结构构成的二叉链表作为二叉树存储结构,叫做线索链表。
- 指向结点前驱和后继的指针,叫做线索。
- 加上线索的二叉树称为线索二叉树(threaded binary tree)。
- 对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
2. 构造线索二叉树
由于线索二叉树构造的实质是将二叉链表中的空指针域改为指向前驱或后继的线索,而前驱或后继的信息只有在遍历时才能得到,因此线索化的过程即为在遍历的过程中修改空指针的过程。显然,这个过程可用递归算法。
对二叉树按照不同的遍历次序进行线索化,可以得到不同的线索二叉树:
- 先序遍历:先序线索二叉树
- 中序遍历:中序线索二叉树
- 后序遍历:后序线索二叉树
下面重点介绍中序线索化的算法。
为了记下遍历过程中访问结点的先后关系,附设一个指针 pre 始终指向刚刚访问过的结点,而指针 p 指向当前访问的结点,由此记录下遍历过程中访问结点的先后关系,如图 7.7.2 所示:
- 当
pre指向的结点没有右孩子的时候,那么给该结点加上右线索,即RTag = 1,让pre的右孩子指针指向后继结点p。 - 当
p指向的结点没有左孩子的时候,那么给该结点加上左线索,即LTag = 1,让p的左孩子指针指向前驱结点pre。

(1)以树中任意一个结点 p 为根的子树中序线索化的过程
【算法步骤】
- 如果
p非空,左子树递归线索化。 - 如果
p的左孩子为空,则给p加上左线索,将其LTag置为 1,让p的左孩子指针指向pre(前驱);否则将p的LTag置为 0。 - 如果
pre的右孩子为空,则给pre加上右线索,将其RTag置为 1,让pre的右孩子指针指向p(后继);否则将pre的RTag置为 0 。 - 将
pre指向刚访问过的结点p,即pre = p。 - 右子树递归线索化。
【算法描述】
void InThreading(BiThrTree p){//pre 是全局变量,初始化时其右孩子指针为空,便于在树的最左点开始建线索if(p){InThreading(p->lchild); //左孩子递归线索化if(!p->lchild){//p 的左孩子为空p->LTag = 1; //给 p 加上左线索p->lchild = pre; //p 的左孩子指针指向 pre(前驱)}else{p->LTag = 0; }if(!pre->rchild){//pre 的右孩子为空pre->RTag = 1; //给 pre 加上右线索pre->rchild = p; //pre 的右孩子指针指向 p(后继)}else{p->RTag = 0}pre = p; //保持 pre 指向 p 的前驱InThreading(p->rchild); //右子树递归线索化}
}
(2)完成整个二叉树的中序线索化
将二叉树线索化后,所得到的是本质上是一个链表,为此,也可以仿照链表的存储结构,在二叉树的线索链表上也添加一个头结点,并令:
- 其
lchild域的指针指向二叉树的根结点; - 其
rchild域的指针指向中序遍历时访问的最后一个结点; - 令二叉树中序序列中第一个结点(最左的结点)的
lchild域指针和最后一个结点(最右的结点)rchild域的指针均指向头结点(如图 7.7.3)。
这好比为二叉树建立了一个双向线索链表,既可从第一个结点起顺后继进行遍历,也可从最后一个结点起顺前驱进行遍历。

带头结点的二叉树中序线索化算法如下:
【算法描述】
void InOrderThreading(BiThrTree &Thrt, BitThrTree T){//中序遍历二叉树 T,并将其中序线索化,Thrt 指向头结点Thrt = new BitThrNode; //建头结点Thrt->LTag = 0; //头结点有左孩子,若树非空,则其左孩子为树根Thrt->RTag = 1; //头结点的右孩子指针为右线索Thrt->rchild = Thrt; //初始化时头结点右孩子指针指向自己if(!T){Thrt->lchild = Thrt; //若树为空,则头结点左孩子指针指向自己} else {Thrt->lchild = T;//头结点的左孩子指向根pre = Thrt; //pre 初始值指向头结点InThreading(T); //对 T 为根的二叉树进行中序线索化//上一行算法结束后,pre 为最右结点,pre 的右线索指向头结点pre->rchild = Thrt; pre->RTag = 1;Thrt->rchild = pre; //头结点的右线索指向 pre}
}
3. 遍历线索二叉树
分 3 种情况讨论在线索二叉树中如何查找结点的前驱和后继。
(1)在中序线索二叉树中查找
以图 7.7.3 为例:
① 查找 p 指针所指结点的前驱:
-
若
p->LTag为 1,则p的左链指示其前驱; -
若
p->LTag为 0,则说明p有左子树,结点的前驱是遍历左子树时最后访问的一个结点(左子树中最右下的结点)。
② 查找 p 指针所指结点的后继:
- 若
p->RTag为 1,则p的右链指示其后继,以图 7.7.3 所示的中序线索树为例,结点b的后继为结点*; - 若
p->RTag为 0,则说明p有右子树。根据中序遍历的规律可知,结点的后继应是遍历其右子树时访问的第一个结点,即右子树中最左下的结点。例如在找结点*的后继时首先沿右指针找到其右子树的根结点-,然后顺其左指针往下直至其左标志为 1 的结点,即为结点*的后继,在图 7.7.3 中是结点c。
(2)在先序线索二叉树中查找

① 查找 p 指针所指结点的前驱:
- 若
p->LTag为 1,则p的左链指示其前驱; - 若
p->LTag为 0,则说明p有左子树。此时p的前驱有两种情况:- 若
*p是其双亲的左孩子,则其前驱为其双亲结点; - 否则应是其双亲的左子树上先序遍历最后访问到的结点。
- 若
② 查找 p 指针所指结点的后继:
- 若
p->RTag为 1,则p的右链指示其后继; - 若
p->RTag为 0,则说明p有右子树。按先序遍历的规则可知,*p的后继必为其左子树根(若存在)或右子树根。
(3)在后序线索二叉树中查找

① 查找 p 指针所指结点的前驱:
- 若
p->LTag为 1,则p的左链指示其前驱; - 若
p->LTag为 0,- 当
p->RTag也为 0 时,则p的右链指示其前驱; - 当
p->RTag为 1 时,则p的左链指示其前驱。
- 当
② 查找 p 指针所指结点的后继情况比较复杂,分以下情况讨论:
- 若
*p是二叉树的根,则其后继为空; - 若
*p是其双亲的右孩子,则其后继为双亲结点; - 若
*p是其双亲的左孩子,且*p没有右兄弟,则其后继为双亲结点; - 若
*p是其双亲的左孩子,且*p有右兄弟,则其后继为双亲的右子树上按后序遍历列出的第一个结点(即右子树中“最左下”的叶结点)。
由于有了结点的前驱和后继信息,线索二叉树的遍历操作无需使用栈,避免了频繁的进栈、出栈,因此在时间和空间上都较便利二叉树节省。
如果遍历某种次序的线索二叉树,则只要从该次序下的根结点出发,反复查找在该次序下的后继,直到叶子结点。
以遍历中序线索二叉树为例:
【算法步骤】
-
指针
p指向根结点。 -
p为非空树或遍历未结束时,循环执行以下操作:-
沿左孩子向下,到达最左下结点
*p,它是中序的第一个结点; -
访问
*p; -
沿右线索反复查找当前结点
*p的后继结点并访问后继结点,直至右线索为 0 或者遍历结束; -
转向
p的右子树。
-
【算法描述】
void InOrderTraverse_Thr(BiThrTree T){//T 指向头结点,头结点的左链 lchild 指向根结点//中序遍历二叉线索树 T 的非递归算法,对每个数据元素直接输出p = T->lchild; //p 指向根结点while(p != T){//空树活遍历结束时,p==Twhile(p->LTag == 0){p = p->lchild; //沿左孩子向下}cout << p->data; //访问其左子树为空的结点while(p->RTag == 1 && p->rchild != T){p = p->rchild; //沿右线索访问后继结点count << p->data;}p = p->rchild; //转向 p 的右子树}
}
【算法分析】
- 时间复杂度 O(n)O(n)O(n)
- 空间复杂度 O(1)O(1)O(1),线索二叉树的遍历不需要使用栈来实现递归操作。
例 7.7.1 判断线索二叉树中 *p 结点有右孩子的条件是( )。
A. p != NULL\qquad B. p->rchild != NULL\qquad C. p->rtag == 0\qquad D. p->tag == 1
【解】
线索二叉树中用 rtag 标识结点的右指针是否为线索。
本题答案:C
例 7.7.2 若 x 是中序线索二叉树中一个有左孩子的结点,且 x 不为根结点,则 x 的前趋结点为( )
A. x 的双亲结点
B. x 的右子树中最左下结点
C. x 的左子树中最右下结点
D. x 的左子树中最右下结点
【解】
设 x 的左孩子为 y,则 x 的前趋结点为以 y 为根结点的最后一个结点,而一棵二叉树的中序序列最后一个结点为其最右下结点。
本题答案:C
