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

之前我们介绍了中序线索二叉树的构造、遍历、寻找前趋和寻找后继。中序线索二叉树既可以寻找前趋结点也可以寻找后继结点。

今天我们再来看一下先序线索二叉树的构造:

首先是结构声明:

我们将左线索和右线索初始化为0。

/*
结构声明 
*/ 
typedef struct node{
	struct node* left;
	int ltag = 0;
	char val;
	int rtag = 0;
	struct node* right;
}TreeNode,*Tree; 

先序构造线索二叉树的递归代码:

这里和中序线索二叉树做对比,不仅仅是把对线索的处理移到了遍历左右子树之前,并且在遍历左右子树的时候加上了tag==0的判断,这是因为当先序遍历时处理完孩子结点之后又会返回到根节点,如果不判断它是否具有线索的话会再次回到孩子结点。

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

继续包装一下递归函数,因为最后一个pre结点也需要收尾(与中序线索二叉树的收尾相同)。

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

先序线索二叉树访问后继:

/*
返回结点p在线索二叉树中的先序序列下的后继结点
如果有左孩子,则后继是左孩子,如果没有左孩子,则后继是右孩子。 
*/
TreeNode* successor(TreeNode* p){
	if(p->ltag == 0) return p->left; 
	else return p->right; 
} 

先序线索二叉树不能很好的解决寻找前驱,例如不能通过b找到a。 

建立好先序线索二叉树之后,我们就可以根据线索和左右孩子指针以线性的时间来先序遍历先序线索二叉树了。

/*
先序线索二叉树的先序遍历
先找到第一个结点,再一直寻找它的后继结点。
*/ 
void PreOrder(Tree& t){
	TreeNode* p = t;
	for(p;p != NULL;p = successor(p))
		cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<"  ";
}

 我们可以层次遍历一下,看看树打上标记后的样子。

/*
层次遍历
因为线索二叉树的叶子节点几乎没有了空指针,所以进队的条件应该做修改,用 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 PreThread(Tree& t,TreeNode* &pre){
	//pre指针指向t的中序前驱,在主函数中预设为NULL
	if(t != NULL){
		if(t->left == NULL){ //建立当前结点的前驱线索 
			t->left = pre;
			t->ltag = 1;
		}
		if(pre && pre->right == NULL){ //建立前驱结点pre的后继线索 
			pre->right = t;
			pre->rtag = 1;
		}
		pre = t;
		if(t->ltag == 0)
			PreThread(t->left,pre); //左子树线索化 
		if(t->rtag == 0)
			PreThread(t->right,pre); //右子树线索化 
	} 
} 
 
/*
通过先序遍历建立先序线索二叉树
*/ 
void CreateThread(Tree& t){
	TreeNode* pre = NULL; //前驱指针
	if(t != NULL){
		PreThread(t,pre);
		pre->rtag = 1; //最后一个结点右标志改为1
	}	
}
 
/*
返回结点p在线索二叉树中的先序序列下的后继结点
如果有左孩子,则后继是左孩子,如果没有左孩子,则后继是右孩子。 
*/
TreeNode* successor(TreeNode* p){
	if(p->ltag == 0) return p->left; 
	else return p->right; 
} 
 
/*
先序线索二叉树不能很好的解决寻找前驱,例如不能通过b找到a。 
*/ 

/*
先序线索二叉树的先序遍历
先找到第一个结点,再一直寻找它的后继结点。
*/ 
void PreOrder(Tree& t){
	TreeNode* p = t;
	for(p;p != NULL;p = successor(p))
		cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<"  ";
}
 
/*
层次遍历
因为线索二叉树的叶子节点几乎没有了空指针,所以进队的条件应该做修改,用 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;
	}
} 
 
/*
先序构造二叉树
*/ 
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; 
	PreOrder(t);
	cout<<endl;
	cout<<"层次遍历:"<<endl;
	levelOrderTraverse(t);
}

程序运行结果:

 更多代码请参考:手撕考研数据结构(代码汇总)

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jiawen9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值