线索二叉树
注意:源码
- 二叉线索树的概念
二叉线索树是在传统二叉树结构的基础上,加上判断结点左右孩子是否为空的标志–LTag,RTag。
当左孩子为空的时候,lchild指向该节点的前驱结点,当右孩子为空的时候,rchild指向该节点后继结点。以此提高链表的储存密度。下面我们给出二叉线索树的结构:
typedef struct BiTNode
{
char data;//数据域
int LTag = 0;//左标签
int RTag = 0;//右标签
struct BiTNode* lchild = nullptr;//指针必须初始化,c11标准
struct BiTNode* rchild = nullptr;//左右孩子
}BiTNode, * BiTree;
-
二叉线索树相对于一般二叉树的优点
当以二叉链表作为储存结构,只能找到结点的左右孩子信息,而不能得到结点在任意序列中的前驱和后继信息,这种信息只有在遍历的动态过程中才能得到,而二叉线索树可以保存这些动态过程中的前驱和后继信息。而且由于一般二叉树有n个结点必定存在(n+1)的空链域(容易证得)。因此可以充分利用这些空链域储存前驱和后继信息,提高储存密度。 -
创建二叉树
我们以先序遍历创建二叉树,先输入根节点,再创建左子树,在创建右子树(若结点data为#,表示空树)。代码如下:
void CreateBiTree(BiTree& T,BiTree P)//先序输入
{
char ch;
cin >> ch;
if (ch == '#') T = NULL;
else
{
T = new BiTNode;//分配空间
T->data = ch;//根节点赋值
T->parent = P;
CreateBiTree(T->lchild,T);//建立左子树
CreateBiTree(T->rchild,T);//建立右子树
}
}
- 先序线索化二叉树
按照根左右的顺序进行先序线索化,具体流程为:
首先,我们先创建一个空的BiTree Thrt (一方面用于作为第一个结点的前驱结点,最后一个节点的后继结点,另一方面也可以将二叉树形成一个双向链表,既可以从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历,非常方便-^- ^-),创建一个BiTree pre 始终指向刚刚访问过的结点。
其次,我们先找到根结点,若此结点没有左孩子,我们将lchild指向它的前驱,并将此节点赋给pre。若pre没有右孩子,我们将pre的rchild指向当前节点。
最后,递归左子树,再递归右子树。
我们所要遍历的树如下所示:
先序线索化后的二叉树就是这个样子(有点丑 -^- ^- ):
下面附上代码(pre是一个BiTree类型的全局变量,如果不要带头结点的线索树,就将第一个最左面的结点的lchild置空,之后pre指向此节点):
//以结点T为根的子树先序线索化
void PreThreading(BiTree& T)
{
if (T)//T非空
{
if (!T->lchild)//左子树为空
{
T->LTag = 1;
T->lchild = pre;//指向前驱结点
}
else
{
T->LTag = 0;
}
if (!pre->rchild)
{
pre->RTag = 1;
pre->rchild = T;//指向后继结点
}
else
{
T->RTag = 0;
}
pre = T;//pre指向当前节点
if (T->LTag == 0)//这点很重要,只有结点有左孩子的时候才可以遍历左子树,否则会陷入死循环
{
PreThreading(T->lchild);//遍历左子树
}
if (T->RTag == 0)//同上
{
PreThreading(T->rchild);//遍历右子树
}
}
}
//以结点T为根的字树先序线索化
//带头节点的二叉树先序线索化
void PreOrderThreading(BiTree& Thrt, BiTree T)
{
Thrt = new BiTNode;//头节点
Thrt->LTag = 0;//左标签为零,表明有左子树
Thrt->RTag = 1;
Thrt->rchild = Thrt;//右子树指向自己
if (!T)//若为空树,右子树指向自己
{
Thrt->lchild = Thrt;
}
else
{
Thrt->lchild = T;//左指针指向T
pre = Thrt;
PreThreading(T);//进行线索化
pre->rchild = Thrt;//此时pre为最右面的那个节点,让它的右子树为Thrt
pre->RTag = 1;
Thrt->rchild = pre;//Thrt的右子树为pre,形成闭环
}
}
//带头节点的二叉树先序线索化
此时,二叉树的先序线索化就结束了。
- 中序线索化二叉树
中序线索化二叉树的步骤和先序二叉树很相似,具体步骤为:
1.遍历左子树,找到最左面的结点,让他的lchild指向pre,并将pre指向当前节点。
2.对根节点重复以上步骤。
3.在遍历右子树。
下面是中序线索化二叉树的图(图依旧很丑):
下面附上代码(代码还是那个代码,只不过换了一下顺序):
//以结点T为根的子树中序线索化
void InThreading(BiTree &T)
{
if (T)//T非空
{
InThreading(T->lchild);//遍历左子树
if (!T->lchild)//左子树为空
{
T->LTag = 1;
T->lchild = pre;
}
else
{
T->LTag = 0;
}
if (!pre->rchild)
{
pre->RTag = 1;
pre->rchild = T;
}
else
{
T->RTag = 0;
}
pre = T;
InThreading(T->rchild);//遍历右子树
}
}
//以结点T为根的字树中序线索化
//带头节点的二叉树中序线索化
void InOrderThreading(BiTree& Thrt, BiTree T)
{
Thrt = new BiTNode;
Thrt->LTag = 0;
Thrt->RTag = 1;
Thrt->rchild = Thrt;
if (!T)
{
Thrt->lchild = Thrt;
}
else
{
Thrt->lchild = T;
pre = Thrt;
InThreading(T);
pre->rchild = Thrt;
pre->RTag = 1;
Thrt->rchild = pre;
}
}
//带头节点的二叉树中序线索化
- 后序线索化二叉树
后序线索化二叉树和前面的思路一样:
1.先遍历左子树,找到最左面的结点,若其lchild为空,这lchild 指向pre,若pre的rchild为空,则pre的rchild指向当前结点。
2.遍历右子树,进行上面的步骤。
3.最后再对根节点进行如上操作。
下面是后序线索化二叉树的图(图还是很丑):
附上代码:
//以结点T为根的子树后序线索化
void PosThreading(BiTree& T)
{
if (T)//T非空
{
PosThreading(T->lchild);//遍历左子树
PosThreading(T->rchild);//遍历右子树
if (!T->lchild)//左子树为空
{
T->LTag = 1;
T->lchild = pre;
}
else
{
T->LTag = 0;
}
if (!pre->rchild)
{
pre->RTag = 1;
pre->rchild = T;
}
else
{
T->RTag = 0;
}
pre = T;
}
}
//以结点T为根的字树后序线索化
//带头节点的二叉树后序线索化
void PosOrderThreading(BiTree& Thrt, BiTree T)
{
Thrt = new BiTNode;
Thrt->LTag = 0;
Thrt->RTag = 1;
Thrt->rchild = T;
if (!T)
{
Thrt->lchild = Thrt;
}
else
{
Thrt->lchild = T;
pre = Thrt;
PosThreading(T);
}
}
//带头节点的二叉树后序线索化
看到这里,二叉线索树的线索化就算结束了,当我们的故事还没有结束(生活不止眼前的线索化,还有二叉树的遍历)。下面我们来谈一谈二叉树线索树 的遍历。
- 遍历先序线索树
先序遍历线索树时,首先定义一个BiTree p,指向头节点p 的左子树,即 p = T->child。
然后,我们要输出p的信息,如果该节点有左子树,便p指向左子树,并输出结点信息,若没有左子树,便p指向右子树,输出信息。
最后,当p指向T,循环结束。
下面是先序遍历的图例(动画做的不好):
下面附上代码(就是如此简单):
//遍历先序线索二叉树
void PreTraverse_Thr(BiTree T)
{
BiTree p = T->lchild;
while (p != T)
{
cout << p->data;
if (p->LTag == 0)
{
p = p->lchild;
}
else
{
p = p->rchild;
}
}
}
//遍历先序线索二叉树
- 遍历中序线索树
遍历中序线索树的思路就是按照左根右的思路:
首先,先找到最左面的结点p,输出节点值。
然后,若左面的结点的rchild=1,p = p->rchild,输出结点值,循环往复。
若遍历到根节点p,p = p->rchild。在循环往复,直至p为T结束。
下面是中序遍历的图例(动画依旧做的不好):
附上代码(依旧那么简单):
//遍历中序线索二叉树
void InTraverse_Thr(BiTree T)
{
BiTree p = T->lchild;//
while (p != T)
{
while (p->LTag == 0)
{
p = p->lchild;
}
cout << p->data;
while (p->RTag == 1 && p->rchild != T)
{
p = p->rchild;
cout << p->data;
}
p = p->rchild;
}
}
//遍历中序线索二叉树
- 遍历后序线索树
遍历后序线索树有点难搞呀。原先的二叉树结构需要做一下改动,我们要为每个结点添加父节点,以解决当遍历到右子树时无法访问父节点。
代码如下:
typedef struct BiTNode
{
char data;//数据域
int LTag = 0;//左标签
int RTag = 0;//右标签
struct BiTNode* lchild = nullptr;//指针必须初始化,c11标准
struct BiTNode* rchild = nullptr;//左右孩子
struct BiTNode* parent = nullptr;//双亲
}BiTNode, * BiTree;
有了上面的结构,后序遍历线索树就简单多了。
1.首先我们应该像中序遍历一样,先找到最左面的结点p,输出节点的值,若p->rchild == 1;p = p->rchild,输出节点的值,一直循环往复,直到p->lchild == 0 并且 p->rchild == 0。
2.此时p为根节点(相对来说),我们需要靠双亲结点继续遍历,并输出节点的值。直到我们找到真正的根节点p,此时p = p->rchild。
3.重复1,2步骤,直到p的双亲结点为空。
演示图列:
附上代码(这次不简单哦):
//后序遍历需要双亲节点
void PosTraverse_Thr(BiTree T)
{
BiTree root = T->lchild;//初始节点
BiTree prev = nullptr;//记录上一个节点
while (root)
{
if (root->lchild == root->rchild)//如果双亲没有左子树或者右子树
{
root = root->rchild;
}
while (root->LTag == 0 && root->lchild != prev)//找到最左边的节点
{
root = root->lchild;
}
while (root->RTag == 1)//输出并遍历节点的后继
{
cout << root->data;
prev = root;//记录上一个节点
root = root->rchild;
}
if (root == T->lchild)//判断是不是根节点
{
cout << root->data;
return;
}
while (root->rchild == prev)//不是根节点,访问当前节点的双亲节点
{
cout << root->data;
prev = root;
root = root->parent;
if (root == nullptr)//遍历到根节点,退出
{
return;
}
}
if (root->RTag == 0)//遍历右子树
{
root = root->rchild;
}
}
}
//遍历后序线索二叉树