线索化存在的必要原因:对于一颗二叉树,遍历是一个经常性的操作,但是如果我们每次遍历时,都要通过递归或者通过栈实现二叉树的遍历,在遍历时需要进行很多判断,并且一定程度上浪费了一些空间和时间,因此我们考虑是否可以记录下其遍历时其”前驱”和”后继”。
方法:1.对每个二叉树节点增加两个字段”前驱”和”后继”来分别存储遍历过程中它的前一个节点位置和后一个节点位置,这种方式固然是方便,不过也不得不花费更多的空间来存储节点地址,这样一来,可能未能达到优化效果。
2.因为二叉树每个节点的左右子树可能不存在,指向其左右子树的指针为null,这个资源是完全可以被利用的,显然我们可以将节点左右子树指针为null的地方改为指向其”前驱”或者”后继”,但是这样我们就无法区分左右子树指针指向的是”子树”还是”前驱或后继”,所以我们要花费额外的两个bit来区分。
代码及代码说明
首先生成二叉树
typedef enum PointerTag{Link,Thread} PointerTag;//定义标签
typedef struct BiThrNode{
int data;
struct BiThrNode *lchirld,*rchild;
PointerTag LTag,RTag;
}BiThrNode;
//层序生成二叉树
BiThrNode* CreateTree(){
int arr[]={1,2,3,4,5,6,7};
BiThrNode *root=(BiThrNode*)malloc(sizeof(BiThrNode));
root->data=1;
root->lchirld=root->rchild=NULL;
BiThrNode** con[7]={};
int r,l,idx;
con[0]=&(root->lchirld);
con[1]=&(root->rchild);
r=0;
l=2;
for(int i=1;i<7;++i){
BiThrNode *p=(BiThrNode*)malloc(sizeof(BiThrNode));
p->data=arr[i];
p->lchirld=p->rchild=NULL;
*con[r++]=p;
con[l++]=&(p->lchirld);
con[l++]=&(p->rchild);
}
return root;
}
线索化代码
int DoThr(BiThrNode* pre,BiThrNode* now){
/*需要前后两个变量,来测试记录前者的"后继"和后者的"前驱"*/
if(pre){
if(!pre->rchild){
pre->RTag=Thread;
pre->rchild=now;
cout<<pre->data<<" 的后继是"<<now->data<<endl;
}
else pre->RTag=Link;
}
if(now){
if(!now->lchirld){
now->LTag=Thread;
now->lchirld=pre;
if(pre)//由于在刚开始时可能出现pre是null的情况
cout<<now->data<<" 的前驱是"<<pre->data<<endl;
}
else now->LTag=Link;
}
}
前序线索化
int PreThr(BiThrNode* T){
BiThrNode *pre=NULL;//记录前一个节点
BiThrNode *p=T;//此为当前节点
BiThrNode *visited=NULL;//刚刚访问过的变量
BiThrNode* stk[10]={};
int top=0;
while(top>0||p){
if(p!=NULL){
DoThr(pre,p);//注意在前序遍历时,此函数为变化为visit
pre=p;
stk[top++]=p;
if(p->LTag==Thread)p=NULL;//此点需要尤为注意,因为线索化可能会改变p的左子树,而接下来我们判断的就是其左子树,所以不能想遍历时简单判断
else p=p->lchirld;
}
else{
//此处与前序遍历时相同
if(stk[top-1]->rchild&&stk[top-1]->rchild!=visited){
p=stk[top-1]->rchild;
}
else{
visited=stk[--top];
p=NULL;
}
}
}
}
中序线索化
int InThr(BiThrNode *T){
BiThrNode *p=T;
BiThrNode *pre=NULL;
BiThrNode* stk[10]={};
int top=0;
while(top>0||p){
if(p){
stk[top++]=p;
p=p->lchirld;
}
else{
p=stk[--top];
DoThr(pre,p);//进行线索化,
pre=p;//及时更新"前驱"
p=p->rchild;//更新"当前节点"
}
}
}
后续线索化
int PostThr(BiThrNode *T){
BiThrNode *p=T;
BiThrNode *visited=NULL;
BiThrNode *pre=NULL;
BiThrNode* stk[10]={};
int top=0;
while(top>0||p){
if(p){
//与后续遍历时相同
stk[top++]=p;
p=p->lchirld;
}
else{
if(stk[top-1]->rchild&&stk[top-1]->rchild!=visited)p=stk[top-1]->rchild;
else{
p=stk[--top];
visited=p;
DoThr(pre,p);
pre=p;
p=NULL;//注意在此将p赋为null
}
}
}
}
从上述代码中我们可以看出,线索化是在二叉树遍历的基础上进行的,因此在学习线索化之前应该先将二叉树遍历的思路理清楚,这样在学线索化就会很快的学会了。
相比于二叉树的遍历,线索化需要多记录一个pre用来作为当前节点的”前驱”,而当前节点作为”前驱”的后继,然后线索化完此节点后,在将pre和当前节点进行更新,不断重复此过程。
其次,遍历时对节点值打印的动作变为了线索化的动作(这点也不难理解,因为线索化的顺序与遍历节点的顺序是一致的),这一点可能会改变原来对当前节点左右节点存在性的判断,因此进行完线索化之后,需要进一步对其进行分析,以确定下一个节点的位置。
到此为止,线索化的思路已经分享完了,如果各位老铁有对其有着另外的思考,欢迎在文章下面留言~