【数据结构·考研】中序线索二叉树

中序线索二叉树

在 n 个结点的二叉链表中,有(n+1)个空指针域,利用这些空指针域来存放当前结点的直接前驱和后继的线索,可以加快查找速度。

普通二叉树只能找到结点的左右孩子信息,而结点的直接前驱和直接后继只能在遍历中获得,若将遍历后对应的前驱和后继预存起来,从第一个结点开始就可以顺藤摸瓜而遍历整棵树,树实际变成了线性序列。

例如中序遍历的结果实际上就是把二叉树转化成了线性排列。

线索二叉树的结构声明

typedef struct node{
	struct node* left;
	int ltag = 0;
	char val;
	int rtag = 0;
	struct node* right;
}TreeNode,*Tree; 

通过中序遍历建立中序线索二叉树

一个 pre 指针一直指向 t 遍历的前一个结点,每次遍历到一个结点,如果可以做线索的话,刚好做的是 t 的左线索和 pre 的右线索。这样的话当 t 遍历完时候 pre 的右线索还没有处理,此时 pre 指向遍历完之后的最后一个结点,我们给 pre 的右线索置 1 收尾。

void InThread(Tree& t,TreeNode* &pre){
	//pre指针指向t的中序前驱,在主函数中预设为NULL
	if(t != NULL){
		InThread(t->left,pre); //左子树线索化 
		if(t->left == NULL){ //建立当前结点的前驱线索 
			t->left = pre;
			t->ltag = 1;
		}
		if(pre && pre->right == NULL){ //建立前驱结点pre的后继线索 
			pre->right = t;
			pre->rtag = 1;
		}
		pre = t;
		InThread(t->right,pre); //右子树线索化 
	} 
} 

//通过中序遍历建立中序线索二叉树
void CreateThread(Tree& t){
	TreeNode* pre = NULL; //前驱指针
	if(t != NULL){
		InThread(t,pre);
		pre->rtag = 1; //最后一个结点右标志改为1
	}	
}

中序序列下的后继结点

中序遍历的过程需要不停寻找下一个结点,给定一个结点 p ,如果p的 rtag == 1,那么p的右孩子就是p的后继结点,如果 p 有右孩子,即 rtag == 0,那么p的后继结点就是以 p->right 为根的二叉树的中序序列下的第一个结点,也就是最左边的结点。

//返回结点p在线索二叉树中的中序序列下的后继结点
TreeNode* successor(TreeNode* p){
	if(p->rtag == 1) return p->right;
	p = p->right;
	while(p->ltag == 0) p = p->left;
	return p; //最左下结点 
} 

中序遍历中序线索二叉树

现在我们可以开始中序遍历中序线索二叉树,先找到第一个结点,再一直寻找它的后继结点。

//中序线索二叉树的中序遍历 
void Inorder(Tree& t){
	TreeNode* p = t;
	while(p->ltag == 0) p = p->left;
	for(p;p != NULL;p = successor(p))
		cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<"  ";
}

中序序列下的前驱结点

给定一个结点 p ,如果寻找 p 的前驱结点呢,如果 p->ltag == 1,那么直接返回 p->left ,如果 p 有左孩子,那么 p 的前驱就是p的左子树最右边的结点。

//返回结点p在线索二叉树中的中序序列下的前驱结点
TreeNode* predecessor(TreeNode* p){
	if(p->ltag == 1) return p->left;
	p = p->left; 
	while(p->rtag == 0) p = p->right; //找第一个没有右孩子的结点 
	return p;
} 

中序线索二叉树的层次遍历

因为线索二叉树的叶子节点几乎没有了空指针,所以进队的条件应该做修改,用 ltag、rtag 来判断。

//层次遍历 
void levelOrderTraverse(Tree& t){
	if(t == NULL) return;
	queue<TreeNode*> q;
	TreeNode* p;
	q.push(t);
	while(!q.empty()){
		int width = q.size();
		for(int i = 0;i < width;i ++){
			p = q.front();
			q.pop();
			cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<"  ";
			if(p->ltag == 0) q.push(p->left);
			if(p->rtag == 0) q.push(p->right);
		}
		cout<<endl;
	}
} 

树还是那棵老树,完整代码如下:

#include<iostream>
#include<queue>
using namespace std;

typedef struct node{
	struct node* left;
	int ltag = 0;
	char val;
	int rtag = 0;
	struct node* right;
}TreeNode,*Tree; 


void InThread(Tree& t,TreeNode* &pre){
	//pre指针指向t的中序前驱,在主函数中预设为NULL
	if(t != NULL){
		InThread(t->left,pre); //左子树线索化 
		if(t->left == NULL){ //建立当前结点的前驱线索 
			t->left = pre;
			t->ltag = 1;
		}
		if(pre && pre->right == NULL){ //建立前驱结点pre的后继线索 
			pre->right = t;
			pre->rtag = 1;
		}
		pre = t;
		InThread(t->right,pre); //右子树线索化 
	} 
} 

//通过中序遍历建立中序线索二叉树
void CreateThread(Tree& t){
	TreeNode* pre = NULL; //前驱指针
	if(t != NULL){
		InThread(t,pre);
		pre->rtag = 1; //最后一个结点右标志改为1
	}	
}

//返回结点p在线索二叉树中的中序序列下的后继结点
TreeNode* successor(TreeNode* p){
	if(p->rtag == 1) return p->right;
	p = p->right;
	while(p->ltag == 0) p = p->left;
	return p; //最左下结点 
} 

//中序线索二叉树的中序遍历 
void Inorder(Tree& t){
	TreeNode* p = t;
	while(p->ltag == 0) p = p->left;
	for(p;p != NULL;p = successor(p))
		cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<"  ";
}

//返回结点p在线索二叉树中的中序序列下的前驱结点
TreeNode* predecessor(TreeNode* p){
	if(p->ltag == 1) return p->left;
	p = p->left; 
	while(p->rtag == 0) p = p->right; //找第一个没有右孩子的结点 
	return p;
} 

//层次遍历 
void levelOrderTraverse(Tree& t){
	if(t == NULL) return;
	queue<TreeNode*> q;
	TreeNode* p;
	q.push(t);
	while(!q.empty()){
		int width = q.size();
		for(int i = 0;i < width;i ++){
			p = q.front();
			q.pop();
			cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<"  ";
			if(p->ltag == 0) q.push(p->left);
			if(p->rtag == 0) q.push(p->right);
		}
		cout<<endl;
	}
} 

//先序构造二叉树 
void CreateTree(Tree& t){
	char x;
	cin>>x;
	if(x == '#') t = NULL; 
	else{
		t = new TreeNode; 
		t->val = x;  
		CreateTree(t->left); 
		CreateTree(t->right); 
	}
} 

int main(){
	Tree t;
	CreateTree(t);
	/*
	   a b d # # e # # c f # # #
	*/
	CreateThread(t); 
	cout<<"层次遍历:"<<endl;
	levelOrderTraverse(t);
	cout<<"中序遍历:"<<endl; 
	Inorder(t);
	cout<<endl;
	TreeNode*p;
	cout<<"t的后继结点:"<<endl; 
	p = successor(t);
	cout<<p->val<<endl; 
	cout<<"t的前驱结点:"<<endl;
	p = predecessor(t);
	cout<<p->val<<endl; 
}

运行结果:

前序线索二叉树和后序线索二叉树

二叉树前序线索化和后序线索化的方式与中序线索化相似,只是区别在线索化的时机。

前序线索化需要对最后一个结点进行收尾处理,标志它为线索,而后序线索二叉树不需要,因为它的最后一个遍历结点是根。

后序线索二叉树不能有效解决寻找后继

在后序线索二叉树中查找结点 *p 的前驱:若结点 *p 无左子树,则 p->left 指向其前驱;否则,若结点 *p 有左子树,当其右子树为空时,其左子树的根(即 p->left )为其后序前驱。当其右子树非空时,其右子树的根(即 p->right )为其后序前驱。

在后序线索二叉树中查找结点 *p 的后继:若结点 *p 为根,则无后继;若结点 *p 为其双亲的右孩子,则其后继为其双亲;若结点*p 为其双亲的左孩子,且双亲无右子女,则其后继为其双亲;若结点 *p 为其双亲的左孩子,且双亲有右子女,则结点 *p 的后继是其双亲的右子树中按后序遍历的第一个结点。所以,求后序线索二叉树中结点的后继要知道其双亲的信息,要使用栈,所以说后序线索二叉树是不完善的。

先序线索二叉树不能有效解决寻找前驱

在先序线索二叉树中查找结点的后继较容易,而查找前驱要知道其双亲的信息,要使用栈,所以说先序线索二叉树也是不完善的。

猜你喜欢

转载自blog.csdn.net/cjw838982809/article/details/108416784