返回目录:
Chilan Yu:《数据结构》目录链接zhuanlan.zhihu.com6.3.4 线索二叉树
1. 基本概念
在有n个结点的二叉链表中共有2n个链域,但只有n-1个有用非空链域,其余n+1个链域是空的。我们可以利用剩下的n+1个空链域来存放遍历过程中结点的前驱和后继信息。 现作如下规定:
若结点有左子树,则其LChild域指向其左孩子,否则LChild域指向其前驱结点;若结点有右子树,则其RChild域指向其右孩子,否则RChild域指向其后继结点。
为了区分孩子结点和前驱、后继结点,为结点结构增设两个标志域,如下图所示:
其中:
注意!!!在后续的程序代码编写过程中,发现除非使用枚举型固定Ltag或Rtag=0,否则是无法确定这两个空间里放的数字是多少的!所以一切有关的判断都只能跟1有关,而不能跟0有关。
typedef char DataType;
/*二叉树的二叉链表结点结构定义*/
typedef struct Node{//结点结构
DataType data;//结点数据
struct Node * LChild;//左孩子指针
struct Node * RChild;//右孩子指针
int Ltag;//左标记位
int Rtag;//右标记位
}BiTNode, * BiTree;
- 线索:在这种存储结构中,指向前驱和后继结点的指针叫做线索。
- 线索链表:以这种结构组成的二叉链表作为二叉树的存储结构,叫做线索链表。
- 线索化:对二叉树以某种次序进行遍历并且加上线索的过程叫做线索化。
- 线索二叉树:线索化了的二叉树称为线索二叉树。
2. 二叉树的线索化
线索化实质上是将二叉链表中的空指针域填上相应结点的遍历前驱或后继结点的地址,而前驱和后继的地址只能在动态的遍历过程中才能得到。因此线索化的过程是在遍历过程中修改空指针域的过程。 对二叉树按照不同的遍历次序进行线索化,可以得到不同的线索二叉树 (先序线索二叉树、中序线索二叉树和后序线索二叉树)。
【算法思想】:
- (1)中序线索化采用中序递归遍历算法框架
- (2)加线索操作就是访问结点操作
- (3)加线索操作需要利用刚访问过结点与当前结点的关系,因此设置一个指针pre,始终记录刚访问过的结点,其操作如下:
a. 如果当前遍历结点root的左子域为空,则让左子域指向pre
b. 如果前驱pre的右子域为空,则让右子域指向当前遍历结点root
c. 为下次做准备,当前访问结点root作为下一个访问结点的前驱pre
BiTree pre = NULL;//pre始终指向刚访问过的结点,其初值为NULL
/*建立中序线索树*/
void Inthread(BiTree root){//对root所指的二叉树进行中序线索化,其中pre始终指向刚访问过的结点,其初值为NULL
if(root!=NULL){
Inthread(root->LChild);//线索化左子树
/*加线索操作*/
if(root->LChild==NULL){//置前驱线索
root->Ltag = 1;
root->LChild = pre;
}
if(pre!=NULL && pre->RChild==NULL){//置后继线索
pre->RChild = root;
pre->Rtag = 1;
}
pre = root;//当前访问结点为下一个访问结点的前驱
Inthread(root->RChild);//线索化右子树
}
}
3. 在线索二叉树中找前驱、后继结点
(1)找结点的中序前驱结点
根据线索二叉树的基本概念和存储结构可知,对于结点p,当p->Ltag==1
时,p->LChild
指向p的前驱。
当p->Ltag!=1
时,p->LChild
指向p的左孩子。由中序遍历的规律可知,作为根p的前驱结点,它是中序遍历p的左子树时访问的最后一个结点,即左子树的“最右下端”结点。
/*在中序线索树中找结点前驱*/
BiTree InPre(BiTree p){//在中序线索二叉树中查找p的中序前驱,并用pre指针返回结果
BiTree Pre;
if(p->Ltag==1) Pre = p->LChild;//直接利用线索
else{//在p的左子树中查找“最右下端”结点
for(Pre=p->LChild;Pre->Rtag!=1;Pre=Pre->RChild)//不能写Pre->Rtag==0
;
}
return Pre;
}
(2)在中序线索树中找结点后继
对于结点p,若要找其后继结点,当p->Rtag==1
时,p->RChild
即为p的后继结点;当p->Rtag!=1
时,说明p有右子树,此时p的中序后继结点即为其右子树的“最左下端”的结点。
/*在中序线索树中找结点后继*/
BiTree InNext(BiTree p){//在中序线索二叉树中查找p的中序后继结点,并用Next指针返回结果
BiTree Next;
if(p->Rtag==1 || p->RChild==NULL)//直接利用线索(注意:一定要加上p->RChild==NULL,不然在调用TInOrder(T)时,程序会停不下来)
Next = p->RChild;
else{//在p的右子树中查找“最左下端”结点
for(Next=p->RChild;Next->Ltag!=1;Next=Next->LChild)//不能写Next->Ltag==0
;
}
return Next;
}
4. 遍历中序线索树
遍历线索树的问题可以分解为两步,第一步是求出某种遍历次序下第一个被访问结点;然后连续求出刚访问结点的后继结点,直至所有的结点均被访问。
(1)在中序线索树上求出中序遍历的第一个结点
/*在中序线索树上求中序遍历的第一个结点*/
BiTree InFirst(BiTree Bt){
BiTree p = Bt;
if(!p) return NULL;
while(p->Ltag!=1) p = p->LChild;
return p;
}
(2)遍历中序二叉线索树
/*遍历中序二叉线索树*/
void TInOrder(BiTree Bt){
BiTree p;
p = InFirst(Bt);
while(p){
printf("%c",p->data);
p = InNext(p);
}
}
ADDITION:
以上函数的辅助函数:
/*用扩展先序遍历序列创建二叉链表*/
void CreateBiTree(BiTree * bt){//这边的bt是指针的指针
char ch;
ch = getchar();
if(ch=='#') *bt = NULL;
else{
*bt = (BiTree)malloc(sizeof(BiTNode));//用(*bt)指针开辟结点空间
(*bt)->data = ch;
CreateBiTree( &( (*bt)->LChild ) );
CreateBiTree( &( (*bt)->RChild ) );
}
}
调用以上函数的主函数:
int main()
{
BiTree T;//开辟一个指向树的T指针
CreateBiTree(&T);//传址建树
Inthread(T);//建立中序线索树
printf("%cn",InFirst(T)->data);//在中序线索树上求中序遍历的第一个结点
printf("%cn",InPre(T)->data);//在中序线索树中找结点前驱
printf("%cn",InNext(T)->data);//在中序线索树中找结点后继
TInOrder(T);//遍历中序二叉线索树
return 0;
}