甲級PAT1020 Tree Traversals (樹的後序和中序轉樹的層序)
1020 Tree Traversals (25 分)
Suppose that all the keys in a binary tree are distinct positive integers. Given the postorder and inorder traversal sequences, you are supposed to output the level order traversal sequence of the corresponding binary tree.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤30), the total number of nodes in the binary tree. The second line gives the postorder sequence and the third line gives the inorder sequence. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print in one line the level order traversal sequence of the corresponding binary tree. All the numbers in a line must be separated by exactly one space, and there must be no extra space at the end of the line.
Sample Input:
7 2 3 1 5 7 6 4 1 2 3 4 5 6 7
Sample Output:
4 1 6 3 5 7 2
題目要求:
給出一棵二叉樹的後序遍歷以及中序遍歷,輸出這棵樹的層序遍歷
解題思路一(先構建二叉樹再層序遍歷,知道的直接跳到下一個解題思路):
這裡我是先按照二叉樹的後序遍歷和中序遍歷,將二叉樹構建出來,然後按照層序遍歷將這棵二叉樹輸出。
如何構建二叉樹首先對後序遍歷和中序遍歷進行分析。
後序遍歷:左右根
中序遍歷:左根右
下面結合這個看起來很複雜而且有點醜的圖來分析後序和中序遍歷怎麼構成一個二叉樹。從原理上深入解釋過程。
由於後序遍歷始終按從左右根的順序,最後一個一定是二叉樹的根,這裡為4,記做根1。
在中序中找到根1(即為4),由於中序按照左根右的順序,可以根據根1將剩下分成兩部分,在根1的左邊為其左子樹部分,在根1右邊的為其右子樹部分。
再找到根1左子樹對應的左子樹根,以及右子樹對應的右子樹根。後序左右根的順序,所以在根1左子樹部分出現在後序遍歷順序最後的就是左子樹根,左子樹1,2,3中在後序順序中最後出現的是1,記為根2左。在根1右子樹部分出現在後序遍歷順序最後的就是右子樹根,右子樹5,6,7中在後序順序中最後出現的是6,記為根2右。
重複上述步驟,找到根2左(1)對應在中序中的位置,其左邊部分為左子樹,這裡沒有。其右邊部分為右子樹(2,3)。找到根2右(6)對應在中序中的位置,其左邊部分為左子樹(5)。其右邊部分為右子樹(7)。
大概就是這樣的一種思路。首先在後序遍歷中找到根,再根據這個根找到其在中序中的位置區分它的左子樹和右子樹部分。直到將所有的結點都遍歷結束。
注意:
1.遞迴時建立左孩子以及右孩子有三種情況。
a.根在中序範圍內的中間,這樣根既有左孩子又有右孩子
b.根在中序範圍內的最左邊,這樣根只有右孩子,沒有左孩子
c.根在中序範圍內的最右邊,這樣根只有左孩子,沒有右孩子
2.遞迴的結束條件:
a.樹完全建立完成
b.根既沒有左孩子也沒有右孩子,此時根滿足根在中序範圍內的最左邊,但是找右子樹的根的時候會找不到,所以需要再找右子樹根找不到時結束遞迴。
完整程式碼:
#include<bits/stdc++.h>
using namespace std;
typedef struct Node{
int data;
struct Node* left;
struct Node* right;
}*BinTree;
int post[31];
int in[31];
int level[31];
int num = 0;
queue<BinTree> q;
int N;
void LevelOrder(Node * T){ //對樹進行層序遍歷
int i=0;
Node* p = T;
if(p){
q.push(p);
}
while(!q.empty()){
p = q.front();
level[i++] = p->data;
q.pop();
if(p->left){
q.push(p->left);
}
if(p->right){
q.push(p->right);
}
}
}
void create(Node* T,int px,int py,int ix,int iy,int proot){ //根結點,對應子樹部分在後序的起點下標px,終點下標py,對應子樹部分在中序的起點下標ix,終點下標iy
int i,j,k,iroot,lroot=-1,rroot=-1; //iroot為根proot在中序中的下標;lroot為對應左子樹根在後序中的下標;rroot為對應右子樹根在後序中的下標
num++;
if(num == N) return;
for(i=ix;i<=iy;i++){
if(post[proot] == in[i]){
iroot = i;
break;
}
}
if(iroot !=ix && iroot!=iy){ //根在中序範圍的中間
for(i = py;i>=px;i--){ //找到根iroot左子樹部分在後序中最後出現的為其左子樹的根lroot
for(j=ix;j<iroot;j++){
if(lroot == -1 && post[i] == in[j]){
lroot = i;
}
}
for(k=iy;k>iroot;k--){//找到根iroot右子樹部分在後序中最後出現的為其右子樹的根rroot
if(rroot == -1 && post[i] == in[k]){
rroot = i;
}
}
}
Node* L =(Node*)malloc(sizeof(Node));
Node* R =(Node*)malloc(sizeof(Node));
L->data = post[lroot];
L->left = NULL;
L->right = NULL;
R->data = post[rroot];
R->left = NULL;
R->right = NULL;
T->left = L;
T->right = R;
create(L,px,lroot-1,ix,iroot-1,lroot); //對左子樹部分繼續建立
create(R,lroot+1,rroot-1,iroot+1,iy,rroot); //對右子樹部分繼續建立
}else if(iroot == ix){ //根在中序範圍的最左邊
for(i = py;i>=px;i--){
for(k=iy;k>iroot;k--){
if(rroot == -1 && post[i] == in[k]){
rroot = i;
}
}
}
if(rroot == -1) return;
Node* R =(Node*)malloc(sizeof(Node));
R->data = post[rroot];
R->left = NULL;
R->right = NULL;
T->left = NULL;
T->right = R;
create(R,px,rroot-1,iroot+1,iy,rroot);
}else if(iroot == iy){ //根在中序範圍內的最右邊
for(i = py;i>=px;i--){
for(j=ix;j<iroot;j++){
if(lroot == -1 && post[i] == in[j]){
lroot = i;
}
}
}
Node* L =(Node*)malloc(sizeof(Node));
L->data = post[lroot];
L->left = NULL;
L->right = NULL;
T->left = L;
T->right = NULL;
create(L,px,lroot-1,ix,iroot-1,lroot);
}
}
int main(){
int i;
cin>>N;
for(i=0;i<N;i++){
scanf("%d",&post[i]);
}
for(i=0;i<N;i++){
scanf("%d",&in[i]);
}
Node* T = (Node*)malloc(sizeof(Node));
T->data = post[N-1];
T->left = NULL;
T->right = NULL;
create(T,0,N-2,0,N-1,N-1);
LevelOrder(T);
for(i=0;i<N-1;i++){
printf("%d ",level[i]);
}
printf("%d",level[N-1]);
return 0;
}
非常開心自己摸索的寫完之後。。看了大神的程式碼。。人家30行就解決了。。o(╥﹏╥)o,不過也的確讓我對樹有了更深刻的理解。過程還是非常快樂的。
解題思路二:
首先來個鋪墊:根據後序和中序輸出先序遍歷
仍然是在中序中找到後序中的根以此區分左右子樹。但是不同的是右孩子為後序中根下標-1,左孩子為後序中根下標-右子樹結點個數-1(這裡的理解是,中序中根就區分了左子樹和右子樹,而後序中左子樹的根一定是左子樹中最後的,後續中右子樹的根也是右子樹中最後的)
再根據對應根的下標以及所在的子樹區域,左子樹是原根範圍的起始下標——根的中序下標-1;右子樹是根的中序下標+1——原根範圍的結束下標
void pre(int root, int start, int end) {
if(start > end) return ;
int i = start;
while(i < end && in[i] != post[root]) i++;
printf("%d ", post[root]);
pre(root - 1 - end + i, start, i - 1);
pre(root - 1, i + 1, end);
}
這樣每次輸出根即按照先序遍歷輸出。
進階:根據後序和中序輸出層序遍歷
所以按照先序遍歷的順序只需要用一個數組按照下標順序記錄每個位置對應的結點的值即可。初始化-1表示結點不存在。這樣之後按下標順序輸出所有不為-1的結點就可以了。
完整程式碼:
#include <iostream>
#include <vector>
using namespace std;
vector<int> post, in, level(100000, -1);
void pre(int root, int start, int end, int index) {
if(start > end) return ;
int i = start;
while(i < end && in[i] != post[root]) i++;
level[index] = post[root];
pre(root - 1 - end + i, start, i - 1, 2 * index + 1);
pre(root - 1, i + 1, end, 2 * index + 2);
}
int main() {
int n, cnt = 0;
scanf("%d", &n);
post.resize(n);
in.resize(n);
for(int i = 0; i < n; i++) scanf("%d", &post[i]);
for(int i = 0; i < n; i++) scanf("%d", &in[i]);
pre(n-1, 0, n-1, 0);
for(int i = 0; i < level.size(); i++) {
if (level[i] != -1) {
if (cnt != 0) printf(" ");
printf("%d", level[i]);
cnt++;
}
if (cnt == n) break;
}
return 0;
}