題目:
現有兩個節點序列,分別是同一個二叉樹進行前序遍歷和中序遍歷的結果。請編寫一個程序,輸出該二叉樹按後序遍歷時的節點序列。
輸入: 第一行輸入二叉樹節點數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:前兩天沒有更新博客,並不是因爲去過情人節了,我這單身狗就是簡單的迎接舍友的一一到來,然後吃吃喝喝~