If you can not explain it simply,you do not understand it well enouth!
不能简明的解释一件事,说明你对它懂得不多! --爱因斯坦
二叉树的线索化
一、 背景
在二叉链表上,我们只能知道每个结点指向其左右孩子结点的地址,而不知道某个结点的前驱是谁,后继是谁?
• 在n个结点的二叉树中,必定有n+1个空链域(叶子结点的左右子树空间浪费了)
• 二叉树的遍历,无论是递归还是非递归算法,效率都不算高。
那我们能不能利用原本浪费掉的空间,来解决这些问题呢?答案是可以的那就是线索化。
二、线索化
现将某结点的空指针域指向该结点的前驱或者后继,定义规则如下:
a.若结点的左子树为空,则该结点的左孩子指针指向其前驱结点
b.若结点的右子树为空,则该结点的右孩子指针指向其后继结点
这种指向前驱和后继的指针称为线索,将一棵普通的二叉树以某种次序遍历,并添加线索的过程称为线索化。
三.线索化带来的问题与解决
此时新的问题又产生了:我们如何区分一个lchild指针是指向左孩子还是前驱结点呢?
为了解决这个问题,我们需要添加标志位ltag和rtag,并定义以下规则
有的同学可能会觉得:这不是增加了两个字段嘛,空间相比较之前的不是变大了吗?是的,的确变大了,但是这两个标记带来的效率是非常高的。
(1)ltag为0时指向该结点的左孩子,为1时指向该结点的前驱;
(2)rtag为0时指向该结点的右孩子,为1时指向该结点的后继;
(3)可将上述图中的二叉链表修改为如下的图;
四、知识点扩充
4.1聚合类
聚合类(aggregate class)使用户可以直接访问其成员,并且具有特殊的初始化语法。当一个类满足以下条件时,我们说它是聚合的:
a.无自定义构造函数;
b.非静态数据成员没有大括号或等号初始化器,即类内没有初始值;
c.无私有或保护的非静态成员;
d.无基类和虚函数。
聚合类的的主要特性是可以使用{}符号像数组一样进行初始化.
4.2 NULL和nullptr
void fun(int a)
{
cout << "f(int)" << endl;
}
void fun(int *p)
{
cout << "f(int*)" << endl;
}
int main()
{
fun(0);
fun(NULL);
fun(nullptr);
system("pause");
return 0;
}
五、二叉线索化代码实现
typedef enum { Link, Thread } PointerTag; //link = 0表示指向左右孩子指针
//Thread = 1表示指向前驱或后继的线索
typedef struct BitNode
{
char data; //结点数据
struct BitNode* lchild; //左右孩子指针
struct BitNode* rchild;
PointerTag ltag{ Link }; //左右标志
PointerTag rtag{ Thread };
}BitNode, * BiTree;
BiTree pre; //全局变量,始终指向刚刚访问过的结点
void InPrint(BiTree pTree)
{
if (pTree)
{
InPrint(pTree->lchild);
printf("%c ",pTree->data);
InPrint(pTree->rchild);
}
}
void InThreading(BiTree pTree)
{
if (pTree)
{
InThreading(pTree->lchild);
if (NULL == pTree->lchild)
{
pTree->lchild = pre;
pTree->ltag = Thread;
}
if (pre && NULL == pre->rchild)
{
pre->rchild = pTree;
pre->rtag = Thread;
}
pre = pTree;
InThreading(pTree->rchild);
}
}
BiTree GetThreadedTree(BiTree pTree)
{
if (NULL == pTree)
{
return NULL;
}
BiTree pHead = (BiTree)malloc(sizeof(BitNode));
pHead->ltag = Link;
pHead->rtag = Thread;
pHead->lchild = pTree;//前驱
pHead->rchild = pHead;//后继
pre = pHead;
InThreading(pTree);
pre->rchild = pHead;
pre->rtag = Thread;
pHead->rchild = pre;
return pHead;
}
void InOrderThraverse_Thr(BiTree t)
{
BiTree p;
p = t->lchild; //p指向根结点
while (p != t)
{
while (p->ltag == Link) //当ltag = 0时循环到中序序列的第一个结点
{
p = p->lchild;
}
printf("%c ", p->data); //显示结点数据,可以更改为其他对结点的操作
while (p->rtag == Thread && p->rchild != t)
{
p = p->rchild;
printf("%c ", p->data);
}
p = p->rchild; //p进入其右子树
}
}
int main(int argc, char** argv)
{
//1.创建一棵树,用于线索化
BitNode nodeF{ 'F',NULL,NULL,Link,Link };
BitNode nodeG{ 'G',NULL,NULL,Link,Link };
BitNode nodeC{ 'C',&nodeF,&nodeG,Link,Link };
BitNode nodeH{ 'H',NULL,NULL,Link,Link };
BitNode nodeI{ 'I',NULL,NULL,Link,Link };
BitNode nodeD{ 'D',&nodeH,&nodeI,Link,Link };
BitNode nodeJ{ 'J',NULL,NULL,Link,Link };
BitNode nodeE{ 'E',&nodeJ,NULL,Link,Link };
BitNode nodeB{ 'B',&nodeD,&nodeE,Link,Link };
BitNode root{ 'A',&nodeB,&nodeC,Link,Link }; BiTree Tree = &root;
cout << "中序递归遍历:";
InPrint(Tree);
cout << endl;
cout << "线索化 遍历:";
BiTree ThreadTree = GetThreadedTree(Tree);
InOrderThraverse_Thr(ThreadTree);
cout << endl;
system("pause");
return 0;
}
C++98可用如下的代码:
int main(int argc, char** argv)
{
BitNode root, nodeB, nodeC, nodeD, nodeE, nodeF, nodeG, nodeH, nodeI, nodeJ;
nodeH.data = 'H';
nodeH.lchild = NULL;
nodeH.rchild = NULL;
nodeH.ltag = Link;
nodeH.rtag = Link;
nodeI.data = 'I';
nodeI.lchild = NULL;
nodeI.rchild = NULL;
nodeI.ltag = Link;
nodeI.rtag = Link;
nodeJ.data = 'J';
nodeJ.lchild = NULL;
nodeJ.rchild = NULL;
nodeJ.ltag = Link;
nodeJ.rtag = Link;
nodeF.data = 'F';
nodeF.lchild = NULL;
nodeF.rchild = NULL;
nodeF.ltag = Link;
nodeF.rtag = Link;
nodeG.data = 'G';
nodeG.lchild = NULL;
nodeG.rchild = NULL;
nodeG.ltag = Link;
nodeG.rtag = Link;
nodeE.data = 'E';
nodeE.lchild = &nodeJ;
nodeE.rchild = NULL;
nodeE.ltag = Link;
nodeE.rtag = Link;
nodeD.data = 'D';
nodeD.lchild = &nodeH;
nodeD.rchild = &nodeI;
nodeD.ltag = Link;
nodeD.rtag = Link;
nodeB.data = 'B';
nodeB.lchild = &nodeD;
nodeB.rchild = &nodeE;
nodeB.ltag = Link;
nodeB.rtag = Link;
nodeC.data = 'C';
nodeC.lchild = &nodeF;
nodeC.rchild = &nodeG;
nodeC.ltag = Link;
nodeC.rtag = Link;
root.data = 'A';
root.lchild = &nodeB;
root.rchild = &nodeC;
root.ltag = Link;
root.rtag = Link;
BiTree Tree = &root;
//中
InPrint(Tree);
//已经线索化之后的树
BiTree pThTree = GetThreadedTree(Tree);
//打印
cout << endl;
InOrderThraverse_Thr(pThTree);
system("pause");
return 0;
}
六、总结
线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索,由于前驱和后继的信息只有在遍历该二叉树时才能得到,所以线索化的过程就是在遍历的过程中修改空指针的过程。
有了线索二叉树后,我们对它遍历时发现,其实就等于是操作一个双向链表!