二叉樹的重建

題目:

現有兩個節點序列,分別是同一個二叉樹進行前序遍歷和中序遍歷的結果。請編寫一個程序,輸出該二叉樹按後序遍歷時的節點序列。

輸入: 第一行輸入二叉樹節點數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:前兩天沒有更新博客,並不是因爲去過情人節了,我這單身狗就是簡單的迎接舍友的一一到來,然後吃吃喝喝~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章