一、概念
传统的二叉树,只表示出了二叉树的父子关系,而缺少一种线性的联系,因此引入了二叉线索树,以便加快查找前缀和后继的速度,主要是利用二叉树中没有利用到的节点指针。如果指针没有指向节点,就让左向指针指向节点的前驱节点,让右向指针指向节点的后继节点,由于做出了改动,所以需要在每个节点的结构体里面添加两个标记,用于区分指向的到底是节点还是前驱后继。
struct TNode{
int data;
int ltag,rtag;
struct TNode *left;
struct TNode *right;
};
根据遍历方式的不同,节点的前驱后继也会不一样,所以延伸出三种构建二叉线索树的方法,本质上构建二叉线索树的过程就是遍历一遍二叉树的过程,在遍历过程中记录前一个节点,以此来实现前驱后继的指向。
线索化的过程一定是基于前驱后缀的,理顺好前驱后缀的关系,把没有指向的空指针位置做好调整,就能得到线索化的二叉树。先画出图再以图为基础构建代码。
二、中序线索化
先从中序线索化开始,维护一个pre用于指向上一个节点,初始值为NULL,从中序遍历的过程可以看出,先访问的节点应该是整个二叉树最左下的节点,所以先采用递归的方式,一直向左下方向移动,当没有左孩子的时候遍历到了最左下的节点,此时最左下节点的左孩子指针根据定义应该指向该节点的前驱,即pre指针所指的节点。由于当前pre指向的是NULL,所以不对其做操作,只有当pre指向的节点不为空且其右孩子指针空,才修改pre指针指向的节点的右孩子指针让其指向当前节点,即找到了pre指针所指结点的后继节点。将pre移动到当前节点的位置,再遍历节点的右子树部分。重复这个过程直到全部节点遍历一次,最后将中序遍历的最后一个节点即最右下的节点的后继指向空。这样完成的只是线索化,但是输出时如果要利用添加的线索,就需要改一下代码。
void InThread(struct TNode * &p,struct TNode * &pre)
{
if(p!=NULL){
InThread(p->left,pre);
if(p->left==NULL)
{
p->left=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->right==NULL)
{
pre->right=p;
pre->rtag=1;
}
pre=p;
InThread(p->right,pre);
}
}
void creatInThread()
{
struct TNode *pre;
pre=NULL;
if(t.root!=NULL)
{
InThread(t.root,pre);
pre->right=NULL;
pre->rtag=1;
}
}
输出时,根据中序的规则,先找到最左下的节点,之后如果节点右孩子指针指向的是后继元素,就直接移向后继节点,如果右孩子指针指向的是右孩子节点,那么就找右子树的最左边的节点,重复这个过程直到为空。其实在输出的时候,前驱是没什么卵用的,不可能根据前驱去找一个已经访问过的节点,一般是利用后继,如果有后继即直接跳转,没有就找右子树的最左下节点,相当于笨办法去找后继。
struct TNode * FirstNode(struct TNode *p)
{
while(p->ltag==0)
p=p->left;
return p;
}
struct TNode * NextNode(struct TNode *p)
{
if(p->rtag==0)
return FirstNode(p->right);
else
return p->right;
}
void InOrder()
{
for(struct TNode *p=FirstNode(t.root);p!=NULL;p=NextNode(p))
printf("%d ",p->data);
}
完整版代码如下,层序建树后中序线索化:
#include<bits/stdc++.h>
using namespace std;
struct Tree{
struct TNode* root;
int size;
};
struct Tree t;
struct TNode{
int data;
int ltag,rtag;
struct TNode *left;
struct TNode *right;
};
int num[1005];
int n;
void creat()
{
queue<TNode *> q;
struct TNode *temp;
temp=(struct TNode*)malloc(sizeof(struct TNode));
temp->data=num[0];
temp->left=NULL;
temp->ltag=0;
temp->right=NULL;
temp->rtag=0;
t.root=temp;
q.push(temp);
int i=1;
while(i<n)
{
temp=q.front();
q.pop();
temp->ltag=0;
struct TNode *l;
l=(struct TNode*)malloc(sizeof(struct TNode));
l->data=num[i];
l->left=NULL;
l->ltag=0;
l->right=NULL;
l->rtag=0;
i++;
temp->left=l;
q.push(l);
if(i==n)
break;
temp->rtag=0;
struct TNode *r;
r=(struct TNode*)malloc(sizeof(struct TNode));
r->data=num[i];
r->left=NULL;
r->ltag=0;
r->right=NULL;
r->rtag=0;
i++;
temp->right=r;
q.push(r);
}
}
void InThread(struct TNode * &p,struct TNode * &pre)
{
if(p!=NULL){
InThread(p->left,pre);
if(p->left==NULL)
{
p->left=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->right==NULL)
{
pre->right=p;
pre->rtag=1;
}
pre=p;
InThread(p->right,pre);
}
}
void creatInThread()
{
struct TNode *pre;
pre=NULL;
if(t.root!=NULL)
{
InThread(t.root,pre);
pre->right=NULL;
pre->rtag=1;
}
}
struct TNode * FirstNode(struct TNode *p)
{
while(p->ltag==0)
p=p->left;
return p;
}
struct TNode * NextNode(struct TNode *p)
{
if(p->rtag==0)
return FirstNode(p->right);
else
return p->right;
}
void InOrder()
{
for(struct TNode *p=FirstNode(t.root);p!=NULL;p=NextNode(p))
printf("%d ",p->data);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&num[i]);
creat();
creatInThread();
InOrder();
return 0;
}
三、前序线索化
根据上一篇的总结,前序中序最好改了,换换顺序就可以,线索化也是这样,还是先从前序遍历的过程开始找,第一个访问的节点肯定是根节点,每次都先访问到达的节点的数据,所以不能像中序那样子直接上来就暴力移动,移动应该放在对当前节点的操作之后,顺序为:判断当前节点的有没有左孩子,没有则让其指向前驱,然后判断当前节点是不是谁的后继,之后将pre调整为当前节点,之后先左移递归再右移递归。这里一定要加一个判断,只有当左右孩子为节点时才移动,否则会出现死循环的现象。重复过程直到全部节点遍历一边,最后让最右下的节点指向空即可。
void InThread(struct TNode * &p,struct TNode * &pre)
{
if(p!=NULL){
if(p->left==NULL)
{
p->left=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->right==NULL)
{
pre->right=p;
pre->rtag=1;
}
pre=p;
if(pre->ltag==0)
InThread(p->left,pre);
if(pre->rtag==0)
InThread(p->right,pre);
}
}
void creatInThread()
{
struct TNode *pre;
pre=NULL;
if(t.root!=NULL)
{
InThread(t.root,pre);
pre->right=NULL;
pre->rtag=1;
}
}
输出时,就比较简单了,从根节点开始,如果有左孩子节点就移向左孩子,没有就直接移向后继,因为前序遍历时左孩子的后继直接就是右孩子,不管右指针指向的是右孩子还是后继元素,都可以直接移向有指针的位置。
struct TNode * NextNode(struct TNode *p)
{
if(p->ltag==0)
return p->left;
else
return p->right;
}
void InOrder()
{
for(struct TNode *p=t.root;p!=NULL;p=NextNode(p))
printf("%d ",p->data);
}
完整代码如下,层序建树之后前序线索化:
#include<bits/stdc++.h>
using namespace std;
struct Tree{
struct TNode* root;
int size;
};
struct Tree t;
struct TNode{
int data;
int ltag,rtag;
struct TNode *left;
struct TNode *right;
};
int num[1005];
int n;
void creat()
{
queue<TNode *> q;
struct TNode *temp;
temp=(struct TNode*)malloc(sizeof(struct TNode));
temp->data=num[0];
temp->left=NULL;
temp->ltag=0;
temp->right=NULL;
temp->rtag=0;
t.root=temp;
q.push(temp);
int i=1;
while(i<n)
{
temp=q.front();
q.pop();
temp->ltag=0;
struct TNode *l;
l=(struct TNode*)malloc(sizeof(struct TNode));
l->data=num[i];
l->left=NULL;
l->ltag=0;
l->right=NULL;
l->rtag=0;
i++;
temp->left=l;
q.push(l);
if(i==n)
break;
temp->rtag=0;
struct TNode *r;
r=(struct TNode*)malloc(sizeof(struct TNode));
r->data=num[i];
r->left=NULL;
r->ltag=0;
r->right=NULL;
r->rtag=0;
i++;
temp->right=r;
q.push(r);
}
}
void InThread(struct TNode * &p,struct TNode * &pre)
{
if(p!=NULL){
if(p->left==NULL)
{
p->left=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->right==NULL)
{
pre->right=p;
pre->rtag=1;
}
pre=p;
if(pre->ltag==0)
InThread(p->left,pre);
if(pre->rtag==0)
InThread(p->right,pre);
}
}
void creatInThread()
{
struct TNode *pre;
pre=NULL;
if(t.root!=NULL)
{
InThread(t.root,pre);
pre->right=NULL;
pre->rtag=1;
}
}
struct TNode * NextNode(struct TNode *p)
{
if(p->ltag==0)
return p->left;
else
return p->right;
}
void InOrder()
{
for(struct TNode *p=t.root;p!=NULL;p=NextNode(p))
printf("%d ",p->data);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&num[i]);
creat();
creatInThread();
InOrder();
return 0;
}
四、后续线索化
后续线索化是最难实现的,这个不是很好表达,最好的办法就是画图来表示。
从图不难发现,和前序中序不一样的是,当访问完标号为2的节点的时候,想要找到其后继6是很难的,2号节点有左右孩子,必然不能通过前驱后继指针来直接移动,那么想找到6号节点,唯一的办法就是回到上一层,在转向右子树,但是找上一层又是个麻烦事,因为没有直接指向父节点的指针,只能从根节点从头开始找,这必然是个很费时间的过程,现有的知识解决不了,查了查别人的代码,发现是用带标志域的三叉链表作为存储结构,枉费了一个小时尝试返回上一层的代码。这里放一下改了很久的代码,可以实现三层的线索化,但是超过三层就炸了。
层序建树,不超过三层情况下后序线索化:
#include<bits/stdc++.h>
using namespace std;
struct Tree{
struct TNode* root;
int size;
};
struct Tree t;
struct TNode{
int data;
int ltag,rtag;
struct TNode *left;
struct TNode *right;
};
int num[1005];
int n;
void creat()
{
queue<TNode *> q;
struct TNode *temp;
temp=(struct TNode*)malloc(sizeof(struct TNode));
temp->data=num[0];
temp->left=NULL;
temp->ltag=0;
temp->right=NULL;
temp->rtag=0;
t.root=temp;
q.push(temp);
int i=1;
while(i<n)
{
temp=q.front();
q.pop();
temp->ltag=0;
struct TNode *l;
l=(struct TNode*)malloc(sizeof(struct TNode));
l->data=num[i];
l->left=NULL;
l->ltag=0;
l->right=NULL;
l->rtag=0;
i++;
temp->left=l;
q.push(l);
if(i==n)
break;
temp->rtag=0;
struct TNode *r;
r=(struct TNode*)malloc(sizeof(struct TNode));
r->data=num[i];
r->left=NULL;
r->ltag=0;
r->right=NULL;
r->rtag=0;
i++;
temp->right=r;
q.push(r);
}
}
void InThread(struct TNode * &p,struct TNode * &pre)
{
if(p!=NULL){
InThread(p->left,pre);
InThread(p->right,pre);
if(p->left==NULL)
{
p->left=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->right==NULL)
{
pre->right=p;
pre->rtag=1;
}
pre=p;
}
}
void creatInThread()
{
struct TNode *pre;
pre=NULL;
if(t.root!=NULL)
InThread(t.root,pre);
}
struct TNode * FirstNode(struct TNode *p)
{
while(p->ltag==0)
p=p->left;
return p;
}
struct TNode * Findup(struct TNode *p)
{
struct TNode *s=t.root;
while(s->ltag==0)
{
if(s->left==p)
return FirstNode(s->right);
s=s->left;
}
s=t.root;
if(s->right==p)
return s;
while(s->rtag==0)
{
if(s->right==p)
return FirstNode(s->right);
s=s->right;
}
}
struct TNode * NextNode(struct TNode *p)
{
if(p->rtag==0)
return Findup(p);
else
return p->right;
}
void InOrder()
{
for(struct TNode *p=FirstNode(t.root);;p=NextNode(p))
{
printf("%d ",p->data);
if(p==t.root)
break;
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&num[i]);
creat();
creatInThread();
InOrder();
return 0;
}