晴天的魔法乐园——还原二叉树(根据中序遍历和层次遍历建立二叉树)

编辑:谯胜平      分类:程序与算法      标签:晴天的魔法乐园      发布时间:2019-09-03      浏览次数:780

题目链接: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



看不清?换一个