数据结构 5-3-3 二叉树的线索化

一、概念

传统的二叉树,只表示出了二叉树的父子关系,而缺少一种线性的联系,因此引入了二叉线索树,以便加快查找前缀和后继的速度,主要是利用二叉树中没有利用到的节点指针。如果指针没有指向节点,就让左向指针指向节点的前驱节点,让右向指针指向节点的后继节点,由于做出了改动,所以需要在每个节点的结构体里面添加两个标记,用于区分指向的到底是节点还是前驱后继。

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;
}

猜你喜欢

转载自blog.csdn.net/weixin_43849505/article/details/107396769