5.2 二叉树

5.2 二叉树

比起紫书上二叉树的基础知识,黑书拓展了一些特殊的树形结构。


5.2.1 二叉树的存储

二叉树的基本概念就不再多做介绍,对一些用语不太明白的详见紫书6.3 树和二叉树

关于二叉树的存储结构,一般用指针来实现:

struct node{
   int value; node *l,*r;}

l和r分别指向当前结点的左孩子和右孩子,当新建一个node时用new来申请内存,使用完毕时,用delete进行删除。


二叉树的遍历

主要就是两种遍历方式,宽度优先遍历和深度优先遍历。

宽度优先遍历

紫书中的遍历代码,当时对BFS还不够熟悉,写的反而应该是最详细的。

二叉树的宽度优先遍历和图的宽度优先遍历没有区别,甚至更加简单,因为图的BFS,状态图的BFS还需要判重防止重复的走到某个节点,二叉树按照顺序加入结点则不会出现这样的情况。

struct Tree{
   //树结点 
	bool value_exist;//表示这个结点是否被赋值了
	int value;//结点值
	Tree *left,*right;//指向左边的子节点,指向右边的子节点 
	Tree(){
   value_exist=NULL;left=NULL;right=NULL;}//构造函数 
}*root;//定义一个根节点
//建立树 
bool bfs(vector<int>&ans){
   //ans即为我们需要得到的答案 
	ans.clear();
	queue<Tree*>q; q.push(root); //建立树,初始只有一个根节点 
	while (!q.empty()){
   
		Tree*u=q.front(); q.pop();
		if (!u->value_exist) return false; 
		//如果我们现在遍历到的指针没有对应的值,那么代表在结点的建立过程中
		//当前指针在建立的过程中被"顺带"地建立了出来,但是没有被赋值 
		//但是在一个二叉树中,一个结点存在的条件,是它必须要被赋值
		//一个没有被赋值过的结点是不能被称为结点的 
		ans.push_back(u->value);//把队列的值放入到ans里
		//因为是按照层序遍历的,所以是按照一层一层的顺序将输入存入队列的 
		if (u->left!=NULL) q.push(u->left); //先左后右 
		if (u->right!=NULL) q.push(u->right); 
	}
	return true;
	//输入正确,此时每一个被建立出来的结点,都有它对应的值 
}
深度优先遍历

又称递归遍历,主要分为先序遍历,中序遍历和后序遍历。

先序遍历:首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树,如果二叉树为空则返回。

递归公式:RreOrder(T)=T的根节点+RreOrder(T的左子树)+RreOrder(T的右子树)

中序遍历:首先遍历左子树,然后访问根结点,最后遍历右子树,若二叉树为空则结束返回。

递归公式:InOrder(T)=InOrder(T的左子树)+T的根节点+InOrder(T的右子树)

后序遍历:首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点,若二叉树为空则结束返回。

递归公式:PostOrder(T)=PostOrder(T的左子树)+PostOrder(T的右子树)+T的根节点

以先序遍历为例,伪代码如下:

void preorder(node *root){
   
	cout<<root->value;//输出根节点的值
	preorder(root->1); preorder(root->2);//递归遍历左子树和右子树
}

对于三种遍历方式而言,如果我们知道中序遍历和另一种遍历的结果,那么我们可以得到另一种遍历方式的结果。

hdu 1710 “Binary Tree Traversals”

输入二叉树的先序遍历结果和中序遍历结果,输出后序遍历。

分析:这个问题紫书上也做过了,那为什么我说一定需要中序遍历呢?因为先序遍历子树的第一个结点一定是子树的根节点,然后我们可以根据这个根节点在中序遍历中找到左子树和右子树。

#include<cstdio>
int pre[1010],in[1010],post[1010],k;//先序,中序,后序序列,k表示求序列时遍历到的位置,也用于记录结点个数 
struct node{
   int value; node *l,*r;//左右子节点 
	node(int v=0,node *l=NULL,node *r=NULL):value(v),l(l),r(r){
   };//构造函数 
};//根据两个序列构建树,l和r为建树序列的左右边界,t为先序遍历中该子树根节点的位置,root为当前树的根 
void buildtree(int l,int r,int &t,node *&root){
   int flag=-1;//flag记录中序遍历中根结点的位置 
	for (int i=l;i<=r;i++) if (in[i]==pre[t]){
   flag=i; break;} if (flag==-1) return;//建树结束 
	root=new node(in[flag]); t++;//新建根节点,以根节点为基准切割出左子树和右子树进行递归遍历 
	if (flag>l) buildtree(l,flag-1,t,root->l);//当前结点的左子结点为左子树的根,递归建立左子树  
	if (flag<r) buildtree(flag+1,r,t,root->r);//当前结点的左子结点为右子树的根,递归建立右子树
}//根据树求先序序列,先赋值根节点,再遍历左子树右子树 
void preorder(node *root){
   
	if (root!=NULL){
   post[k++]=root->value;//赋值 
		preorder(root->l); preorder(root->r);
	} 
}//根据树求中序序列,先遍历左子树,再赋值根节点,再遍历右子树 
void inorder(node *root){
   
	if (root!=NULL){
   inorder(root->l); post[k++]=root->value;//赋值 
		inorder(root->r);
	} 
}//根据树求后序序列,先遍历左子树和右子树,再赋值根节点 
void postorder(node *root){
   
	if (root!=NULL){
   postorder(root->l); postorder(root->r);
		post[k++]=root->value;//赋值
	} 
}//要记得给建好的树释放空间,否则空间会爆炸
void remove_tree(node *root){
   if (root==NULL) return;
	remove_tree(root->l); remove_tree(root->r); delete root;//递归释放树的空间	
}
int main(){
   int n;
	while(scanf("%d",&n)){
   node* root; int t=1;
		for (int i=1;i<=n;i++) scanf("%d",&pre[i]); for (int i=1;i<=n;i++) scanf("%d",&in[i]);
		buildtree(1,n,t,root);//根节点的位置一定在先序序列的第一个		
		k=0; postorder(root);//根据建好的二叉树求后序遍历 
		for (int i=0;i<k;i++) printf("%d ",post[i]); remove_tree(root);//记得销毁树  
	} return 0;
} 

如果不释放空间会出现内存泄漏的问题。

hdu 1622 “Trees on the level”

题面请点击这里

分析:就是根据给定的信息让你判断是否能建成一棵树,如果能建成输出层序优先遍历的结果,信息包括结点的键值和路径信息。

这个问题在紫书那里是作为例题的,给出当时的代码就不多作赘述了:

struct Tree{
   //树结点 
	bool value_exist;//表示这个结点是否被赋值了
	int value;//结点值
	Tree *left,*right;//指向左边的子节点,指向右边的子节点 
	Tree(){
   value_exist=NULL;left=NULL;right=NULL;}//构造函数 
}*root;//定义一个根节点
//申请空间 
Tree* create_newTree(){
   return new Tree();}
//添加新节点 
void add(int v,char *s){
   
	Tree *u=root;//从根节点刚开始往下走
	for (int i=0;i<strlen(s);i++){
    
		if (s[i]=='L'){
   //往左边的子节点走 
			if (u->left==NULL) u->left=create_newTree();//如果节点不存在,给它建立一个新节点
			u=u->left;}
		else if (s[i]=='R'){
   //往右边的子节点走,至于为什么不直接用else,因为字符串有括号 
			if (u->right==NULL) u->right=create_newTree();//如果节点不存在,给它建立一个新节点
			u=u->right; }
	}
//	if (u->value_exist) failed=true; 这段代码书上有,但我还不知道用来干嘛 
	u->value=v; u
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值