二叉树线索化及其实现
1.为什么要有线索二叉树
为了解决无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题我们在有n个结点的二叉链表中,每个结点有指向左右2个孩子的指针域,所以有2n个指针域,而n个结点的二叉树一共有n-1条分支线,也就是说,其实存在2n-(n-1) = n+1 个空指针域。空间十分浪费。
在另一方面,我们对二叉树做中序遍历时,我只知道每个树结点的左右孩子是谁,却不知道该树结点的前驱和后继是谁。要想知道必须重新遍历一遍。
为什么不考虑在创建时就记住这些前驱和后继呢,那将会省去很多时间。仔细想想,如果我们的树结点左右孩子都不为NULL ,如果采用中序遍历,那么该结点的前驱结点是不是左孩子,后继结点是不是右孩子?这就是为什么我们要采用中序遍历的形式来对二叉树进行线索化,那么我需要将孩子结点为NULL的树结点 空间利用起来!因为字节点虽然为NULL,但是都有前驱和后继结点,那么我们可以考虑将 lchild 放树节点的前驱结点,rchild放树结点的后继结点。那么这里我们是否要考虑区分lchild,rchild 是原有的子节点还是我们后续线索化添加的前驱后继结点呢?所以要在线索二叉树结点的数据结构都要体现呗。我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的的二叉树就称为线索二叉树(Threaded Binary Tree)
2.实现线索二叉树
要想利用空指针域来存放前驱、后继的地址,还需要将前驱、后继和左、右子树根结点区分开。因此我们新增两个标志域:LTag和RTag(整型),1表示有子树,0表示无子树。
1)当LTag = 0时,lchild域指示结点的左子树根结点;当LTag = 1时,lchild域指示结点在某个遍历序列中的前驱。
2)当RTag = 0时,rchild域指示结点的右子树根结点;当RTag = 1时,rchild域指示结点在某个遍历序列中的后继。
那么数据结构就有所改变如下:
请看下面前驱、后继的关系
如下这个是线索的结果图:
3.线索二叉树代码的实现
就以下面的树为例子:
#include<iostream>
#include<stack>
using namespace std;
typedef enum { Link, Thread } PointerTag; /* Link==0表示指向左右孩子指针, */
typedef struct BiTNode
{
int data;
struct BiTNode *lchild, *rchild; //左孩子 右孩子
PointerTag LTag = Link;
PointerTag RTag = Link; /* 左右标志 */
}BiTNode;
//递归中序遍历
void InOrder(BiTNode *T)
{
if (T != NULL)
{
InOrder(T->lchild);
printf("%d ", T->data);
InOrder(T->rchild);
}
}
BiTNode *pre; /* 全局变量,始终指向刚刚访问过的结点 */
/* 中序遍历进行中序线索化 */
void InThreading(BiTNode *p)
{
if (p)
{
InThreading(p->lchild); /* 递归左子树线索化 */
if (!p->lchild) /* 没有左孩子 */
{
p->LTag = Thread; /* 前驱线索 */ p->lchild = pre; /* 左孩子指针指向前驱 */
}
if (!pre->rchild) /* 前驱没有右孩子 */
{
pre->RTag = Thread; /* 后继线索 */
pre->rchild = p; /* 前驱右孩子指针指向后继(当前结点p) */
}
pre = p; /* 保持pre指向p的前驱 */
InThreading(p->rchild); /* 递归右子树线索化 */
}
}
/* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */
int InOrderThreading(BiTNode *&Thrt, BiTNode *T)
{
Thrt = (BiTNode *)malloc(sizeof(BiTNode));
if (!Thrt)
return 0;
(Thrt)->LTag = Link; /* 建头结点 */
(Thrt)->RTag = Thread;
(Thrt)->rchild = (Thrt); /* 右指针回指 */
if (!T) /* 若二叉树空,则左指针回指 */
(Thrt)->lchild = Thrt;
else
{
(Thrt)->lchild = T;
pre = (Thrt);
InThreading(T); /* 中序遍历进行中序线索化 */
pre->rchild = Thrt;
pre->RTag = Thread; /* 最后一个结点线索化 */
(Thrt)->rchild = pre;
}
return 0;
}
/* 中序遍历二叉线索树T(头结点)的非递归算法 */
int InOrderTraverse_Thr(BiTNode *T)
{
BiTNode *p;
p = T->lchild; /* p指向根结点 */
while (p != T)
{ /* 空树或遍历结束时,p==T */
while (p->LTag == Link)
p = p->lchild;
printf("%d ", p->data); /* 访问其左子树为空的结点 */
while (p->RTag == Thread&&p->rchild != T)
{
p = p->rchild;
printf("%d ", p->data); /* 访问后继结点 */
}
p = p->rchild;
}
return 0;
}
void main()
{
BiTNode b1, b2, b3, b4, b5;
memset(&b1, 0, sizeof(BiTNode));
memset(&b2, 0, sizeof(BiTNode));
memset(&b3, 0, sizeof(BiTNode));
memset(&b4, 0, sizeof(BiTNode));
memset(&b5, 0, sizeof(BiTNode));
b1.data = 1;
b2.data = 2;
b3.data = 3;
b4.data = 4;
b5.data = 5;
/*
1
2 3
4 5
*/
//构建树关系
b1.lchild = &b2;
b1.rchild = &b3;
b2.lchild = &b4;
b3.lchild = &b5;
printf("\n递归中序遍历:\n");
InOrder(&b1);
cout << "-------线索化二叉树-------\n";
BiTNode *h = NULL;
InOrderThreading(h, &b1);
cout << "-------线索化后的二叉树中序遍历-------\n";
InOrderTraverse_Thr(h);
cout << endl;
system("pause");
}
结果: