晴天的魔法樂園——還原二叉樹(根據中序遍歷和層次遍歷建立二叉樹)

題目鏈接:https://judger.net/problem/1005

Problem Description

給一棵二叉樹的層序遍歷序列和中序遍歷序列,求這棵二叉樹的先序遍歷序列和後序遍歷序列。

Input

每個輸入文件中一組數據。

第一行一個正整數N(1<=N<=30),代表二叉樹的結點個數(結點編號爲1~N)。接下來兩行,每行N個正整數,分別代表二叉樹的層序遍歷序列和中序遍歷序列。數據保證序列中1~N的每個數出現且只出現一次。

Output

輸出兩行,每行N個正整數,分別代表二叉樹的先序遍歷序列和後序遍歷序列。每行末尾不輸出額外的空格。

Sample Input

7
3 5 4 2 6 7 1
2 5 3 6 4 7 1

Sample Output

3 5 2 4 6 7 1
2 5 6 1 7 4 3

1、分析

我們通常遇到的是利用中序遍歷和後序遍歷或者先序遍歷進行建樹,採用遞歸建樹非常簡單,但是利用中序遍歷和層次遍歷建樹就比較複雜了。

  • 遞歸方法(猜想,並未實現):根據層次遍歷確定根節點,根據中序遍歷確定左右子樹節點。然後根據左右子樹節點到層次遍歷中找出對應的左子樹和右子樹的層次遍歷,然後進行遞歸。比較麻煩的是尋找左右子樹的層次遍歷。

  • 非遞歸方法(已實現):使用靜態方法建立二叉樹。使用一個隊列保存還未確定左右子樹的節點。依次掃描層次遍歷,然後求出當前掃描到的節點在中序遍歷中出現的位置,將這個位置與隊列頭部節點的左右子樹節點範圍(同樣是根據中序遍歷序列確定的)進行比較:

    ① 如果在隊首元素左子樹下標範圍內,且隊首元素未分配過左孩子(左孩子指針爲-1),則該節點是隊首元素的左孩子,然後根據隊首元素的左右孩子下標範圍確定當前節點的左右孩子下標範圍,跳出循環

    ② 如果在隊首元素右子樹下標範圍內,且隊首元素未分配過右孩子(右孩子指針爲-1),則該節點是隊首元素的右孩子,然後根據隊首元素的左右孩子下標範圍確定當前節點的左右孩子下標範圍(同上),隊首元素已經分配完了右孩子,則隊首元素出對,跳出循環

    ③ 如果既不在隊首元素左子樹下標範圍內,也不在右子樹下標範圍內,則證明該隊首元素沒有孩子節點,將隊首元素出對,繼續比較隊列中的下一個元素。

    比較完成之後,將該節點加入隊列中,等待分配孩子節點。該過程直到層次遍歷最後一個節點結束。

爲了方便,將根節點單獨處理。

根節點的操作如下圖所示:

ubang_image_20190903391582.png

將根節點入隊,當掃描到第二個節點,即'2'時,它在中序遍歷中的第二個位置,即在隊列頭部元素'1'的左子樹範圍內,且隊首元素並未分配過左孩子,則'2'是'1'的左孩子,然後將'2'入隊,同理,掃描到'3'時可得,'3'是'1'的右孩子,'3'入隊,此時隊首元素'1'的左右孩子已經分配完畢,出對。重複以上操作直到掃描完層次遍歷序列即可建樹完畢。

2、代碼

#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 35;
struct node{
	int data;
	int left = -1, right = -1;
	int LL, LR, RL, RR;			//左右子樹下標範圍 
}tree[maxn];
int in[maxn], level[maxn];	//中序遍歷和層次遍歷
 
 //根據中序遍歷和層次遍歷建樹,n爲節點個數 
int create(int n){
	//根節點單獨處理 
	tree[0].data = level[0];
	int j = 0;
	for(j = 0; j < n; j++){
		if(in[j] == level[0]){
			break;
		}
	}
	//根節點左右子樹下標範圍 
	tree[0].LL = 0;
	tree[0].LR = j - 1;
	tree[0].RL = j + 1;
	tree[0].RR = n - 1;
	queue<int> q;
	q.push(0);		//將隊首頂點入隊 
	for(int i = 1; i < n; i++){
		//查找當前節點在中序中的位置 
		for(j = 0; j < n; j++){
			if(in[j] == level[i]){
				break;
			}
		} 
		tree[i].data = level[i];
		//當隊列列非空時進入 
		while(!q.empty()){
			int temp = q.front();
			//如果當前節點在中序中的位置處在隊首節點的左右子樹下標範圍之內,且隊首節點未分配過節點,則該節點是隊首節點的孩子 
			if(tree[temp].LL <= j && tree[temp].LR >= j && tree[temp].left == -1){	//左孩子 
				tree[temp].left = i;	//隊首節點的左孩子 
				tree[i].LL = tree[temp].LL;
				tree[i].LR = j - 1;
				tree[i].RL = j + 1;
				tree[i].RR = tree[temp].LR; 
				break;
			}else if(tree[temp].RL <= j && tree[temp].RR >= j && tree[temp].right == -1){	//右孩子 
				tree[temp].right = i;	//隊首節點的右孩子
				tree[i].LL = tree[temp].RL;
				tree[i].LR = j - 1;
				tree[i].RL = j + 1;
				tree[i].RR = tree[temp].RR; 
				//右孩子分配完畢,出對
				q.pop();
				break; 
			}else{		//都不是,證明隊首節點沒有孩子,出對,繼續在隊列中尋找當前節點的父親節點 
				q.pop();
			} 
		}
		//當前節點入隊領取左右孩子 
		q.push(i); 
	}
	return 0;
} 

//先序遍歷 
void preOrder(int root, vector<int> &pre){
	if(root == -1) return;
	pre.push_back(tree[root].data);
	preOrder(tree[root].left, pre);
	preOrder(tree[root].right, pre);
}

//後續遍歷
void postOrder(int root, vector<int> &post){
	if(root == -1) return;
	postOrder(tree[root].left, post);
	postOrder(tree[root].right, post);
	post.push_back(tree[root].data);
} 

int main(){
	int n;
	scanf("%d", &n);
	for(int i = 0; i < n; i++){
		scanf("%d", &level[i]);
	}
	for(int i = 0; i < n; i++){
		scanf("%d", &in[i]);
	}
	//建樹 
	int root = create(n);
	vector<int> pre, post;
	preOrder(root, pre);
	postOrder(root, post);
	
	//打印先序遍歷和後續遍歷 
	for(int i = 0; i < n; i++){
		printf("%d", pre[i]);
		if(i < n - 1) printf(" ");
	}
	printf("\n");
	for(int i = 0; i < n; i++){
		printf("%d", post[i]);
		if(i < n - 1) printf(" ");
	}
	return 0;
}

參考鏈接:https://blog.csdn.net/qq_37142034/article/details/88029290

原文鏈接:https://www.qsp.net.cn/art/178.html

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