三、线索二叉树
文章目录
1.原理&作用
遍历二叉树是以一定的规则将二叉树(树型)中的结点排列成一个线型序列,从而得到几种遍历序列,使得该序列中的每个结点(第一个和最后一个结点除外)都有一个直接前驱和直接后继。
普通二叉链表(二叉树)的一个结点,仅能体现一种父子关系,不能直接得到结点在遍历中的前驱或后继。它只有向下的两个孩子结点的指针,没有向上的父结点的指针。
所以每次想要确定前驱时,只能再进行一次遍历:
(动图过大)
为了解决这个问题。
首先我们要来看看这空指针域有多少个呢?对于一个有n个结点的二叉链表,每个结点有指向左右孩子的两个指针域,所以一共是2n个指针域。而n个结点的二叉树一共有 n-1 条分支线数,也就是说,其实是存在 2n - (n-1) = n+1 个空指针域。
所以利用这些空指针来存放指向其前驱或后继的指针,这样就可以像遍历单链表那样方便地遍历二叉树。引入线索二叉树正是为了加快查找结点前驱和后继的速度。
我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)。
2.存储结构
其结点结构如下所示:
lchild | ltag | data | rtag | rchild |
---|
其中,ltag, rtag初始化时,都为0。
- ltag==0时指向该结点的左孩子,为1时指向该结点的前驱。
- rtag==0时指向该结点的右孩子,为1时指向该结点的后继。
//二叉树的结点(线索链表)
typedef struct ThreadNode{
ElemType data; //数据元素
struct ThreadNode *lchild, *rchild; //左、右孩子指针
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
因此对于上图的二叉链表图可以修改为下图的样子。
创建线索二叉树
从代码层面来看,创建线索二叉树的过程可以分为两步:
-
建立二叉树:根据遍历序列创建一颗普通的二叉树。
根据遍历序列,将节点按照遍历次序连接起来的过程。在这个过程中,只关心节点之间的父子关系,而不关心前驱和后继关系。
-
线索化:遍历二叉树,根据遍历次序判断每个节点的左右孩子是否为空,并设置相应的线索标志。
在建立二叉树的基础上,为每个节点添加前驱和后继信息的过程。这个过程需要遍历二叉树两次,第一次遍历是为了确定每个节点的前驱和后继,第二次遍历是为了设置线索标志。
线索二叉树可以直接由遍历序列创建,而无需先构造一棵普通的二叉树。例如,可以使用先序遍历序列、中序遍历序列或后序遍历序列来创建线索二叉树。
线索二叉树的存储结构与普通二叉树不同。在普通二叉树中,每个节点只有左右孩子指针,而在线索二叉树中,每个节点还增加了左标志和右标志,用于表示前驱和后继信息。
线索二叉树的遍历算法与普通二叉树不同。由于线索二叉树中包含了前驱和后继信息,因此可以使用线索遍历算法来遍历线索二叉树,而无需像普通二叉树那样使用递归或迭代算法。
3.二叉树线索化
二叉树的线索化是将二叉链表中的空指针改为指向前驱或后继的线索。而前驱或后继的信息只有在遍历时才能得到,因此线索化的实质就是遍历一次二叉树,线索化的过程就是在遍历的过程中修改空指针的过程。
3.1中序线索化
设指针pre指向刚刚访问过的结点,指针p指向正在访问的结点,即pre指向p的前驱。在中序遍历的过程中,
- 检查p的**左指针
lchild
**是否为空,若为空就将它指向pre; - 检查pre的**右指针
rchild
**是否为空,若为空就将它指向p。
上图中序:BDAEC
通过中序遍历对二叉树线索化的递归算法如下:
//二叉树的结点(线索链表)
typedef struct ThreadNode{
ElemType data; //数据元素
struct ThreadNode *lchild, *rchild; //左、右孩子指针
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
//线索化:一边遍历,一边线索化
void InThread(ThreadTree p, ThreadTree &pre){
if(p != NULL){
InThread(p->lchild, pre); //递归,线索化左子树
//---
if(p->lchild == NULL){ //左子树为空,建立前驱线索
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){
pre->rchild = p; //建立前驱结点的后继线索
pre->rtag = 1;
}
pre = p; //标记当前结点成为刚刚访问过的结点
//---
InThread(p->rchild, pre); //递归,线索化右子树
}
}
会发现,除了中间的代码,和二叉树中序遍历的递归代码几乎完全一样。只不过将本是访问结点的功能改成了线索化的功能,相当于:
void InThread(ThreadTree T){
if(T!=NULL){
InThread(T->lchild);//递归线索化左子树
visit(T)
InThread(T->rchild);//递归线索化右子树
}
}
void visit(ThreadTree p){
if(p->lchild == NULL){ //左子树为空,建立前驱线索
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){
pre->rchild = p; //建立前驱结点的后继线索
pre->rtag = 1;
}
pre = p; //标记当前结点成为刚刚访问过的结点
}
❗中序线索化代码
//二叉树的结点(线索链表)
typedef struct ThreadNode{
ElemType data; //数据元素
struct ThreadNode *lchild, *rchild; //左、右孩子指针
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
// 中序线索化。一边遍历,一边线索化
// 后序线索化的代码和中序完全相同
void InThread(ThreadNode* p, ThreadNode* &pre)
{
if(p != NULL){
InThread(p->lchild, pre); //递归,线索化左子树
//---
//左子树为空,建立前驱线索
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
//右子树为空,建立前驱结点的后继线索
if(pre != NULL && pre->rchild == NULL){ // pre != NULL 排除第一个结点前驱为空的情况
pre->rchild = p;
pre->rtag = 1;
}
pre = p; //标记当前结点成为刚刚访问过的结点
//---
InThread(p->rchild, pre); //递归,线索化右子树
}
}
// 中序线索化
void CreateInThread(ThreadTree T){
ThreadNode *pre = NULL; //第一个结点没有前驱,这里的NULL会赋给第一个结点的前驱
if(T != NULL){
InThread(T, pre); //线索化二叉树
pre->rchild = NULL; //遍历结束后的最后一个结点没有后继
pre->rtag = 1;
}
}
头结点
为了方便,可以在二叉树的线索链表上也添加一个头结点,令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点。
令二叉树中序序列中的第一个结点的lchild域指针和最后一个结点的rchild域指针均指向头结点。这好比为二叉树建立了一个双向线索链表,方便从前往后或从后往前对线索二叉树进行遍历,如下图所示:
遍历的代码如下:
/*T指向头结点,头结点左链lchild指向根结点,头结点右链rchild指向中序遍
的最后一个结点。中序遍历二叉线索链表表示的二叉树T
*/
void InOrderTraverse_Thr(BiThrTree T){
BiThrTree p;
p = T->lchild; //p指向根结点
//空树或遍历结束时,p==T(最后一个结点指向根结点)
while(p != T){
//当ltag==0时循环到中序序列第一个结点
while(p->ltag == 0){
p = p->lchild; //p指向p的左子树
}
visit(p); //访问该结点
//后继线索为1且不是指向头指针
while(p->rtag == 1 && p->rchild != T){
p = p->rchild; //p指向p的后继
visit(p); //访问该节点
}
//p进至其右子树根,开始对右子树根进行遍历
p = p->rchild;
}
}
从这段代码也可以看出,它等于是一个链表的扫描,所以时间复杂度为0(n)。
由于它充分利用了空指针域的空间(这等于节省了空间),又保证了创建时的一次遍历就可以终生受用前驱后继的信息(这意味着节省了时间)。所以在实际问题中,如果所用的二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构就是非常不错的选择。
3.2先序、后序线索化
上面给出了建立中序线索二叉树的代码,建立先序线索二叉树和后序线索二叉树的代码类似,只需变动线索化改造的代码段与调用线索化左右子树递归函数的位置。
以图(a)的二叉树为例,其先序序列为ABCDF,后序序列为CDBFA,可得出其先序和后序线索二叉树分别如图(b)和©所示:
【注意】先序线索化的代码和中序基本相同,但是有一点不同,当一个结点没有左孩子时候,它的左指针lchild会指向前驱,这时候,lchild所指的并不是左子树,而是左线索,所以会出现遍历错误。
//先序遍历
void Pre0rder(BiTree T){
if(T!=NULL){
visit(T)//访问根结点,比如打印
PreOrder(T->lchild);//递归遍历左子树
Pre0rder(T->rchild);//递归遍历右子树
}
}
所以需要改为:
void PreThread(ThreadTree T){
if(T!=NULL){
visit(T) //处理
if(T->ltag == 0){ //0表示孩子(子树)
PreOrder(T->lchild);//递归遍历左子树
}
Pre0rder(T->rchild); //递归遍历右子树
}
}
❗前序线索化代码
// 前序线索化
// 先序线索化的代码和中序基本相同,但是有一点不同,当一个结点没有左孩子时候,它的左指针lchild会指向前驱,这时候,lchild所指的并不是左子树,而是左线索,所以会出现遍历错误。
void PreThread(ThreadNode* p, ThreadNode* &pre) // 前序线索化二叉树子函数
{
if(p != NULL){
//左子树为空,建立前驱线索
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
//右子树为空,建立前驱结点的后继线索
if(pre != NULL && pre->rchild == NULL){ // pre != NULL 排除第一个结点前驱为空的情况
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
// 【注意】这里在递归入口处有条件限制,左、右指针不是线索才能继续递归
if(p->ltag == 0)
PreThread(p->lchild, pre); // 递归,左子树线索化
if(p->rtag == 0)
PreThread(p->rchild, pre); // 递归,右子树线索化
}
}
void CreatePreThread(ThreadTree T){ // 前序线索化二叉树
ThreadNode *pre = NULL;
if(T != NULL){
PreThread(T, pre);
pre->rchild = NULL; // 非空二叉树,线索化
pre->rtag = 1; // 后处理中序最后一个结点
}
}
后序线索化的代码和中序完全相同。
❗后序线索化代码
// 后序线索化
// 后序线索化的代码和中序完全相同,只有顺序不同
void PostThread(ThreadNode* p, ThreadNode* &pre){ // 后序线索化二叉树子函数
if(p != NULL){
PostThread(p->lchild, pre); // 递归,左子树线索化
PostThread(p->rchild, pre); // 递归,右子树线索化
if(p->lchild == NULL){
// 建立当前节点的前驱线索
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){ // pre != NULL 排除第一个结点前驱为空的情况
// 建立当前节点的后继线索
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
}
}
// 后序线索化
void CreatePostThread(ThreadTree T){ // 前序线索化二叉树
ThreadNode *pre = NULL;
if(T != NULL){
PostThread(T, pre);
pre->rchild = NULL; // 非空二叉树,线索化
pre->rtag = 1; // 后处理中序最后一个结点
}
}
4.根据线索二叉树找前驱后继
4.1中序
因为二叉树已经被中序线索化,所以我们遍历链表,并按照中序(左->根->右)的方式访问结点即可。
在中序线索二叉树中找结点的前驱、后继:
-
如果二叉树指针被线索化(叶子节点,ltag和rtag是1),那么前驱和后继就是结点的lchild和rchild。
-
如果ltag和rtag是0,
-
后继就是结点右子树最左下角的结点;
-
前驱就是结点左子树最右下角的结点。
-
//找到以P为根的子树中,第一个被中序遍历的结点
ThreadNode *FirstNode(ThreadNode *p){
//循环找到右子树最左下结点(不一定是叶结点)
while(p->ltag == 0)
p=p->lchild;
return p;
}
//在中序线索二叉树中找到结点p的后继结点
ThreadNode *Next(ThreadNode *p){
//右子树中最左下结点
if(p->rtag == 0)
return FirstNode(p->rchild);
else
return p->rchild; //rtag==1直接返回后继线索
}
//找到以P为根的子树中,最后一个被中序遍历的结点
ThreadNode *LastNode(ThreadNode *p){
//循环找到最右下结点(不一定是叶子结点)
while(p->rtag == 0)
p = p->rchild;
return p;
}
//在中序线索二叉树中找到结点p的前驱结点
ThreadNode *Pre(ThreadNode *p){
//左子树中最右下结点
if(p->ltag == 0)
return LastNode(p->lchild);
else
return p->lchild;
}
既然可以求出每个结点的前驱后继,那么就能写出非递归中序遍历和非递归中序逆向遍历。
//对中序线索二叉树进行中序遍历(顺序)
// (利用线索实现的非递归算法)空间复杂度O(1)
void InOrder(ThreadNode *T){
for(ThreadNode *p=FirstNode(T); p!=NULL; p=Next(p))
visit(p);
}
//对中序线索二叉树进行中序遍历(倒叙)
// (利用线索实现的非递归算法)空间复杂度O(1)
void RevInOrder(ThreadNode *T){
for(ThreadNode *p=LastNode(T); p!=NULL; p=Pre(p))
visit(p);
}
4.2先序
4.2.1先序后继
- 如果为叶结点(ltag和rtag是1),则右链域直接指示了结点的后继;
- 如果有左孩子,则左孩子根就是其后继;
- 如果无左孩子但有右孩子,则右孩子根就是其后继。
4.2.2先序前驱
因为先序遍历的特点是 根-左-右,也就是左右子树都是根的后继,因为二叉链表只有两个孩子结点指针,所以找不到结点的前驱,只能用老办法再遍历一遍。
但是如果是三叉链表,增加了父结点指针:
-
如果当前结点p是左孩子,则父结点就是其前驱;
-
如果当前结点p是右孩子,并且父结点没有左孩子,则父结点就是其前驱;
-
如果当前结点p是右孩子,父结点有左孩子。则左兄弟子树最后一个被先序遍历的结点是其前驱;
-
如果当前结点p是根节点,那么前驱为空。
4.3后序
4.3.1先序前驱
-
若二叉树指针被线索化(叶子节点,ltag==1),那么前驱就是结点的lchild。
-
若ltag==0,
-
若结点有右孩子,那么右孩子的根结点就是前驱;
-
若结点没有右孩子,有左孩子,那么左孩子的根结点就是前驱。
-
4.3.2后序后继
和先序前驱同理。因为后序遍历的特点是 左-右-根,也就是左右子树都是根的前驱,因为二叉链表只有两个孩子结点指针,所以找不到结点的后继,只能用老办法再遍历一遍。
但是如果是三叉链表,增加了父结点指针:
-
若结点p是其双亲的右孩子,则父结点就是后继;
-
若结点p是其双亲的左孩子,且没有右兄弟,则父结点就是后继;
-
若结点p是其双亲的左孩子,有右兄弟,则右兄弟子树最后一个被后序遍历的结点是其前驱;
-
如果当前结点p是根节点,那么后继为空。
图©中找结点B的后继无法通过链域找到,可见在后序线索二叉树上找后继时需知道结点双亲,即需采用带标志域的三叉链表作为存储结构。
❗线索二叉树代码C
/* 二叉树
线索二叉树
C 实现(但是没有双指针,需要在c++中运行)
参考:https://blog.csdn.net/BBBling/article/details/117444965
*/
#include<stdio.h>
#include<stdlib.h>
typedef char ElemType;
typedef struct ThreadNode {
ElemType data;
struct ThreadNode *lchild, *rchild;//左右孩子指针
//默认0代表左右孩子, 1代表前驱或者后继
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
ThreadTree CreatBiTree(); //前序遍历递归法建立二叉树算法
ThreadTree CreatBiTreeByArray(ElemType data[], int &j, int length); //使用数组直接创建二叉树(前序)
// 中序线索化
void InThread(ThreadNode* p, ThreadNode* &pre);
void CreateInThread(ThreadTree T);
// 前序线索化
void PreThread(ThreadNode* p, ThreadNode* &pre);
void CreatePreThread(ThreadTree T);
// 后序线索化
void PostThread(ThreadNode* p, ThreadNode* &pre);
void CreatePostThread(ThreadTree T);
void DestroyThreadTree(ThreadTree T); //销毁二叉树
// 中序线索遍历
ThreadNode* FirstNode(ThreadNode* p);
ThreadNode* Next(ThreadNode* p);
void InOrder(ThreadNode *T); //中序遍历
ThreadNode* LastNode(ThreadNode* p);
ThreadNode* Pre(ThreadNode* p);
void RevInOrder(ThreadNode *T); //中序倒叙遍历
void PreOrder(ThreadNode *T); // 前序线索遍历
// 后序线索遍历考试几乎会不考到,故省略。
void visit(ThreadTree T); //获取元素
/*
//示例二叉树的结构
A
/ \
B C
\ /
D E
*/
int main()
{
ThreadTree root;
// printf("请输入根结点(输入#表示该结点为空):");
// root=CreatBiTree(); //创建树
// 数组创建二叉树
ElemType data[] = "ab#d##ce###";
int i=0;
int length = sizeof(data) / sizeof(ElemType);
root = CreatBiTreeByArray(data, i, length); //创建树
// 中序线索化
CreateInThread(root);
printf("前序遍历二叉树: \n");
InOrder(root);
printf("\n");
printf("后序遍历二叉树: \n");
RevInOrder(root);
// // 前序线索化
// CreatePreThread(root);
// printf("前序遍历二叉树: \n");
// PreOrder(root);
// printf("\n");
// 销毁二叉树
printf("\n销毁二叉树\n");
DestroyThreadTree(root);
return 0;
}
//-------------------------------------------------------------
// 前序遍历递归法建立二叉树算法
ThreadTree CreatBiTree(){
ThreadTree T;
ElemType data;
fflush(stdin);
// scanf("%c",&data);
data = getchar();
if(data == '#')
T = NULL;
else{
T = (ThreadTree)malloc(sizeof(ThreadNode));
T->data = data;
T->ltag = 0;
T->rtag = 0;
printf("%c的左子树:",data);
T->lchild = CreatBiTree();
printf("%c的右子树:",data);
T->rchild = CreatBiTree();
}
return T;
}
// 使用数组直接创建二叉树(前序)
// j指针指示当前到达的数组位置, 从数组0开始
ThreadTree CreatBiTreeByArray(ElemType data[], int &j, int length){
ThreadTree T;
if(j >= length || data[j] == '#'){
T = NULL;
j++;
}
else{
T = (ThreadTree)malloc(sizeof(ThreadNode));
T->data = data[j];
T->ltag = 0;
T->rtag = 0;
j++;
T->lchild = CreatBiTreeByArray(data, j, length);
T->rchild = CreatBiTreeByArray(data, j, length);
}
return T;
}
// 销毁二叉树
void DestroyThreadTree(ThreadTree T){
if(T != NULL){
if(T->ltag == 0)
DestroyThreadTree(T->lchild);
if(T->rtag == 0)
DestroyThreadTree(T->rchild);
free(T);
}
}
// 获取元素
void visit(ThreadTree T){
if(T==NULL){
printf("# ");;
}
printf("%c ",T->data);
}
// --------------------------- Thread 线索化 ----------------------------
/*
设指针pre指向刚刚访问过的结点,指针p指向正在访问的结点,即pre指向p的前驱(p先走,然后再pre)。在中序遍历的过程中,
1. 检查p的左指针`lchild`是否为空,若为空就将它指向pre;
2. 检查pre的右指针`rchild`是否为空,若为空就将它指向p。
*/
// 中序线索化。一边遍历,一边线索化
// 后序线索化的代码和中序完全相同
void InThread(ThreadNode* p, ThreadNode* &pre)
{
if(p != NULL){
InThread(p->lchild, pre); //递归,线索化左子树
//---
//左子树为空,建立前驱线索
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
//右子树为空,建立前驱结点的后继线索
if(pre != NULL && pre->rchild == NULL) {
pre->rchild = p;
pre->rtag = 1;
}
pre = p; //标记当前结点成为刚刚访问过的结点
//---
InThread(p->rchild, pre); //递归,线索化右子树
}
}
// 中序线索化
void CreateInThread(ThreadTree T){
ThreadNode *pre = NULL; //第一个结点没有前驱,这里的NULL会赋给第一个结点的前驱
if(T != NULL){
InThread(T, pre); //线索化二叉树
pre->rchild = NULL; //遍历结束后的最后一个结点没有后继
pre->rtag = 1;
}
}
// 前序线索化
// 先序线索化的代码和中序基本相同,但是有一点不同,当一个结点没有左孩子时候,它的左指针lchild会指向前驱,这时候,lchild所指的并不是左子树,而是左线索,所以会出现遍历错误。
void PreThread(ThreadNode* p, ThreadNode* &pre) // 前序线索化二叉树子函数
{
if(p != NULL){
//左子树为空,建立前驱线索
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
//右子树为空,建立前驱结点的后继线索
if(pre != NULL && pre->rchild == NULL){ // pre != NULL 排除第一个结点前驱为空的情况
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
// 【注意】这里在递归入口处有条件限制,左、右指针不是线索才能继续递归
if(p->ltag == 0)
PreThread(p->lchild, pre); // 递归,左子树线索化
if(p->rtag == 0)
PreThread(p->rchild, pre); // 递归,右子树线索化
}
}
// 前序线索化
void CreatePreThread(ThreadTree T){ // 前序线索化二叉树
ThreadNode *pre = NULL;
if(T != NULL){
PreThread(T, pre);
pre->rchild = NULL; // 非空二叉树,线索化
pre->rtag = 1; // 后处理中序最后一个结点
}
}
// 后序线索化
// 后序线索化的代码和中序完全相同,只有顺序不同
void PostThread(ThreadNode* p, ThreadNode* &pre){ // 后序线索化二叉树子函数
if(p != NULL){
PostThread(p->lchild, pre); // 递归,左子树线索化
PostThread(p->rchild, pre); // 递归,右子树线索化
if(p->lchild == NULL){
// 建立当前节点的前驱线索
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){ // pre != NULL 排除第一个结点前驱为空的情况
// 建立当前节点的后继线索
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
}
}
// 后序线索化
void CreatePostThread(ThreadTree T){ // 前序线索化二叉树
ThreadNode *pre = NULL;
if(T != NULL){
PostThread(T, pre);
pre->rchild = NULL; // 非空二叉树,线索化
pre->rtag = 1; // 后处理中序最后一个结点
}
}
// ----------------------- Traverse 遍历方法 ----------------------------
// 中序顺序遍历
//找到以P为根的子树中,第一个被中序遍历的结点
ThreadNode* FirstNode(ThreadNode* p){
//循环找到右子树最左下结点(不一定是叶结点)
while(p->ltag == 0)
p=p->lchild;
return p;
}
//在中序线索二叉树中找到结点p的后继结点
ThreadNode* Next(ThreadNode* p){
//右子树中最左下结点
if(p->rtag == 0)
return FirstNode(p->rchild);
else
return p->rchild; //rtag==1直接返回后继线索
}
//对中序线索二叉树进行中序遍历(顺序)
// (利用线索实现的非递归算法)空间复杂度O(1)
void InOrder(ThreadNode *T){
for(ThreadNode *p=FirstNode(T); p!=NULL; p=Next(p))
visit(p);
}
// 中序倒叙遍历
//找到以P为根的子树中,最后一个被中序遍历的结点
ThreadNode* LastNode(ThreadNode* p){
//循环找到最右下结点(不一定是叶子结点)
while(p->rtag == 0)
p = p->rchild;
return p;
}
//在中序线索二叉树中找到结点p的前驱结点
ThreadNode* Pre(ThreadNode* p){
//左子树中最右下结点
if(p->ltag == 0)
return LastNode(p->lchild);
else
return p->lchild;
}
//对中序线索二叉树进行中序遍历(倒叙)
// (利用线索实现的非递归算法)空间复杂度O(1)
void RevInOrder(ThreadNode *T){
for(ThreadNode *p=LastNode(T); p!=NULL; p=Pre(p))
visit(p);
}
// 前序遍历
// 因为二叉链表只能找前序后继,所以是顺序遍历(三叉链表才能找前驱)
void PreOrder(ThreadNode *T){
if(T != NULL){
ThreadNode *p = T;
while (p != NULL){
while (p->ltag == 0){ // 左指针不是线索,则边访问边左移
visit(p); // 访问结点
p = p->lchild; // 左移,访问左子树
}
visit(p); // 此时p左必为线索,但还没有被访问,则访问
p = p->rchild; // 此时p左孩子不存在,则右指针若非空,则不论是否为线索都指向其后继
}
}
}