二叉树的重建

题目:

现有两个节点序列,分别是同一个二叉树进行前序遍历和中序遍历的结果。请编写一个程序,输出该二叉树按后序遍历时的节点序列。

输入: 第一行输入二叉树节点数n.

          第二行输入前序遍历的节点编号序列,相邻编号用空格隔开。

    第三行输入中序遍历的节点编号序列,相邻编号用空格隔开。

    节点编号是从1至n的整数。注意,1不一定是根节点。

输入:在一行中输出按后序遍历时的节点序列,相邻的节点编号之间用一个空格隔开。

限制:1<=节点数<=100

输入示例:                                                                                    输出示例:

5                                          3 4 2 5 1

1 2 3 4 5

3 2 4 1 5

分析:这里我就不介绍二叉树的前序,中序,后序遍历了,这种很基础的知识不知道的可以自行百度,所以我就默认大家都知道这块的知识点了.

为了更好解决这个这个问题,我们假设有一颗如下所示的二叉树(自动忽略马赛克的地方):

                                                                   《二叉树的重建》

我们可以很容易得到其前序遍历结果是:{1,2,3,4,5,6,7,8,9},中序遍历结果是:{3 ,2,5,4,6,1,8,7,9}.如果想要通过前序,中序遍历的结果获得后序遍历,那么可以有如下思路:

首先我们知道前序遍历中遍历的顺序是:根节点–>左子树–>右子树,那么我们根据前序遍历的结果,其本身就是一一棵完整的二叉树,那么根据前序遍历的规则,其序列的第一个元素肯定是当前这棵树的根节点,也就是这里的1,至于左子树,右子树包括哪些节点,我们暂不知道,但是我们再根据中序遍历,其遍历的顺序是:左子树–>根节点–>右子树,这样的话,我们把刚才根据前序遍历得到的节点1放在中序遍历中,那么在根节点左边的肯定就是左子树,右边是右子树,是严格且完整地划分为两边的。这样的话,我们就可以得到二叉树的最大概信息,也就是知道整颗二叉树根节点和其左右子树的情况,那么我们再根据在中序遍历中根节点左边的节点序列,也就是这里的{3,2,5,4,6},那么其是根节点的左子树,其实也就是一棵二叉树,那么在中序遍历中对应的是序列{2,3,4,5,6},这样就又很简单得到该树的根节点,也就是1的左孩子就是2。根据上面的分析,同样可以得到右子树的根节点是7,也就是1的右孩子是7,在这种不断分析下,我们就可以还原整棵树,那么也就可以得到其后序遍历.

那么如何解决这题呢?

我们可以使用递归的形式,根据上面的原则不断在前序中序遍历序列中查找根节点,左右子树,不断解析这棵树,并在递归解析了其左右子树后,再输出当前的节点编号,这样也就符合了后序遍历中左子树–>右子树–>根节点的遍历顺序.具体的代码和部分解析的注释如下:

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define Max 100                                           //简单数列法 
vector<int> pre,in,post;                                  //前序,中序遍历得到后序遍历 
int n,pos=0;
void recontruction(int start,int end)      // 前闭后开区间[start,end)表示当前解析的树的中序遍历节点的范围  
{
	if(start>=end) return ;
	//根据前序遍历找到当前中序遍历范围表示的树的根
	int root=pre[pos++];
	
	//在中序遍历中找到根的下标 
	int rootIndex=distance(in.begin(),find(in.begin(),in.end(),root));
	
	//中序遍历中,根的左边为其左子树,右边为右子树 ,并继续递归 
	recontruction(start,rootIndex);
	recontruction(rootIndex+1,end);
	//将当前范围表示的树的根保存在序列中 
	post.push_back(root);
}
int main()
{
	cin>>n;
	int temp;
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		pre.push_back(temp);
	}
	
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		in.push_back(temp);
	}
	
	//参数0~n表示中序遍历中树节点编号的下标范围 
	recontruction(0,n);
	
	for(int i=0;i<n;i++)
	 cout<<post[i]<<" ";
	return 0;
}

上面唯一比较难理解的就是直接通过pos下标获得当前树的根节点,其实可以简单想一下,一开始pos=0,pre[0]就是整棵树的根节点,那么为什么pos++就是下一课树的根节点下标,因为递归函数的递归时先遍历左子树,这样在前序遍历中,根节点后面的部分序列正好就是左子树,而后面部分第一个就是左子树的根节点,也就是正好上面求得根节点后一个,直接pos自增就可,如果还有模糊,那么大家可以自己在我上面的代码中输出一些信息来看看。

其实看到这题并看到书上的解决方案,我有很多的联想,首先联想到的就是去年参加百度之星比赛时遇到的一道题,就是给你前序,中序遍历的序列,然后对这颗二叉树进行操作,那么上述代码仅仅获得后序遍历就不能满足了,所以在上面基础我有了自己思考下的求出整课二叉树的代码:

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define Max 100                                      //完整二叉树法 
struct Node{                                         //前序,中序遍历得到后序遍历 
	int parent,left,right;
};
vector<int> pre,in,post;
Node T[Max];
int n,pos=0;
int recontruction(int start,int end)// 前闭后开区间[start,end)表示当前解析的树的中序遍历节点的范围 
{
	if(start>=end) return -1;
	//根据前序遍历找到当前中序遍历范围表示的树的根
	int root=pre[pos++]; 
	//在中序遍历中找到根的下标 
	int rootIndex=distance(in.begin(),find(in.begin(),in.end(),root));
	
	//中序遍历中,根的左边为其左子树,右边为右子树 ,并继续递归 
	T[root].left=recontruction(start,rootIndex);
	T[root].right=recontruction(rootIndex+1,end);
	//返回当前范围表示的树的根 
	return root;
}
void postOrder(int root)
{
	if(root==-1) return ;
	postOrder(T[root].left);
	postOrder(T[root].right);
	cout<<root<<" ";
}
int main()
{
	cin>>n;
	int temp;
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		pre.push_back(temp);
	}
	
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		in.push_back(temp);
	}
	
	//参数0~n表示中序遍历中树节点编号的下标范围 
	recontruction(0,n);
	
	postOrder(pre[0]);
	return 0;
}

这样在我们获得了完整二叉树后,不仅仅可以进行后序遍历操作,其他的对于二叉树的操作都可以。

这样的话,我其实又有了一些联想,就是根据后序遍历和中序遍历获得前序遍历或者整课二叉树,其实大体的思路是一样的,就是递归函数的参数有些麻烦了,因为获得根节点的方法不能通过简单的pos自增获得了,具体解释和代码如下:

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define Max 100                                      //完整二叉树法 
struct Node{                                         //后序,中序遍历得到前序遍历 
	int parent,left,right;
};
vector<int> pre,in,post;
Node T[Max];
int n;

//当前遍历的树中,中序遍历数组中树下标的范围[in_start,in_end),后序遍历数组中树下标的范围[post_start,post_end]
int recontruction(int in_start,int in_end,int post_start,int post_end)
{
	if(in_start>=in_end) return -1;
	//后序遍历,数组的最后一个元素是根 
	int root=post[post_end]; 
	//在中序遍历中找到当前树的根的下标 
	int rootIndex=distance(in.begin(),find(in.begin(),in.end(),root));
	
	//当前树的左子树的个数 
	int left_count=rootIndex-in_start;
	
	//中序遍历中,根的左边为其左子树,右边为右子树 ,根据左右子树中节点个数求得左右子树范围 
	T[root].left=recontruction(in_start,rootIndex,post_start,post_start+left_count-1);
	T[root].right=recontruction(rootIndex+1,in_end,post_start+left_count,post_end-1);
	//返回当前范围表示的树的根 
	return root;
}
void preOrder(int root)
{
	if(root==-1) return ;
	cout<<root<<" ";
	preOrder(T[root].left);
	preOrder(T[root].right);
}
int main()
{
	cin>>n;
	int temp;
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		post.push_back(temp);
	}
	
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		in.push_back(temp);
	}
	
	//参数0~n表示中序遍历中树节点编号的下标范围(前闭后开)
	//参数0~n-1表示后序遍历中树节点编号的下标范围(闭区间)
	recontruction(0,n,0,n-1);
	
	preOrder(post[n-1]);
	return 0;
}

至于简单打印前序遍历序列的数组法,可以参考前两个程序的改动方法对第三个代码进行改动就行,就不多废话.

PS:前两天没有更新博客,并不是因为去过情人节了,我这单身狗就是简单的迎接舍友的一一到来,然后吃吃喝喝~

点赞