您的位置:首页 > 理论基础 > 数据结构算法

THU数据结构编程作业一:真二叉树重构(Proper Rebuild)

2015-10-29 20:07 1551 查看
本题是对二叉树进行重构,与普通的二叉树重构不同的是本题是针对特殊的二叉树——真二叉树进行重构。

对于普通的二叉树我们知道可以通过 中序遍历+ 前序遍历/后序遍历 完成重构,并且如果仅已知前序遍历和后续遍历无法对一颗普通二叉树进行重构(可以简单理解为当某节点只有一个孩子节点的时候无法判断该孩子节点是左孩子还是右孩子),之前的博客已经进行了整理。但是如果对于一颗真二叉树在仅知道前序遍历和后序遍历的情况下可以实现重构(应该每个节点要么没有孩子节点要么有两个孩子节点,可以判断出左右。)。

需要提前说明的是,本题是要求在已知一个满二叉树的前序和后序遍历序列的条件下求中序遍历序列,所以我只是针对问题来编写代码,并没有重构出完整的二叉树。而是通过前序,后续和中序遍历的规律对数据进行重排得到中序遍历的序列。当然,重构该二叉树的过程也与之重排的过程类似,只是考虑到程序运行的效率没有真正重构。

问题

问题描述

一般来说,给定二叉树的先序遍历序列和后序遍历序列,并不能确定唯一确定该二叉树。



(图一)


比如图一中的两棵二叉树,虽然它们是不同二叉树,但是它们的先序、后序遍历序列都是相同的。

但是对于“真二叉树”(每个内部节点都有两个孩子的二叉树),给定它的先序、后序遍历序列足以完全确定它的结构。

将二叉树的n个节点用[1, n]内的整数进行编号,输入一棵真二叉树的先序、后序遍历序列,请输出它的中序遍历序列。

输入

第一行为一个整数n,即二叉树中节点的个数。

第二、三行为已知的先序、后序遍历序列。

输出

仅一行,给定真二叉树的中序遍历序列。

样例

Input

5
1 2 4 5 3
4 5 2 3 1


Output

4 2 5 1 3


限制

对于95%的测例:1 ≤ n ≤ 1,000,000

对于100%的测例:1 ≤ n ≤ 4,000,000

输入的序列是{1,2…n}的排列,且对应于一棵合法的真二叉树

时间:2 sec

空间:256 MB

提示



观察左、右孩子在先序、后序遍历序列中的位置

重温视频05e5-3

程序

先上代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;

const int SZ = 1<<20;  //fast io
struct fastio{
char inbuf[SZ];
char outbuf[SZ];
fastio(){
setvbuf(stdin,inbuf,_IOFBF,SZ);
setvbuf(stdout,outbuf,_IOFBF,SZ);
}
}io;

//maximun number of nodes is 4000000
const size_t max_size = 4000010;
//traversal sequences
int preOrder[max_size]={0};
int postOrder[max_size]={0};
int inOrder[max_size]={0};

void rebuild(int pre_left, int pre_right, int post_left, int post_right){
if(pre_right == pre_left )
/*      if(last_insert_position == post_left + 1){
inOrder[post_left] = postOrder[post_left];
*/          return;
/*      }else if(last_insert_position  == post_left - 1){
inOrder[post_left] = postOrder[post_left];
return;
}
*/
int i, j, k;
for(i = post_left, j = 0; postOrder[i] != preOrder[pre_left +1]; ++i, j++)
;

inOrder[i+1] = preOrder[pre_left];
for(k = post_right; k > i + 1; --k)
postOrder[k] = postOrder[k-1];
if(k != post_right)
postOrder[k] = 0;

rebuild(pre_left+1, pre_left+1+j, post_left, i);
rebuild(pre_left+1+j+1, pre_right, i+2, post_right);

return;
}

int main(){
#ifndef _OJ_
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
int size;
scanf("%d", &size);
//get preorder and postorder traversal sequence
for(int i = 0; i < size; ++i)
scanf("%d", &preOrder[i]);
//getchar();
for(int i = 0; i < size; ++i)
scanf("%d", &postOrder[i]);
rebuild(0, size-1, 0, size-1);
for(int i = 0; i < size; ++i){
inOrder[i] += postOrder[i];
printf("%d ", inOrder[i]);
}
return 0;
}


问题分析

本题也是利用递归的方法。

例如:对于下面的一个慢二叉树



程序首先定义了三个数组preOrder,postOrder , inOrder用来存放数值,并且都初始化为0.



对于前序和后续遍历的结构如下:



前序遍历:根-> 左子树-> 右子树

后序遍历:左子树-> 右子树-> 根

中序遍历:左子树-> 根 -> 右子树

运用递归的思想先确定根,先序遍历的根节点在前紧接着是左子树的根节点,而后续遍历中找到左子树的根就能把后序遍历序列一分为二分,分成左右子树了,中序遍历的根节点的位置正好在左右子树中间。我们这里通过前序序列确定某一根节点的值,因为该根节点后面紧跟的一定是左子树的根, 再通过在后序序列中查找到该左子树的根的位置就可以知道根节点在中序数组中应该插入的位置。如下图:

!


红色为当前的根节点,黄色为左子树,蓝色为右子树,对于根 1 其后面的 2 一定是 1 的左子树的根,在后序中找到 2 的位置就能知道该左子树一共有 5 个节点,因此,应该把 1 插入到中序遍历的如图位置,前面留下的位置是左子树的,后面的位置是右子树的。

对于左子树同样用上面方法确定左子树的根 2 在中序遍历数组的位置。



到这里一一切似乎正常。

但是请注意:对于节点 4 我们应该怎么处理呢?还有后面的 节点 5 ,这两个节点特殊之处是它们是左子树的叶子节点了。另外还有一个问题, 我们是通过查找左子树的根节点在后序的位置加 1 来作为整个树的根节点在中序数组中的位置。现在我们看右子树的根节点 3, 同样方法在后序遍历中找到3的左子树的根 6 的位置 ,如果我们按照之前的做法 就会把 3 插入到 1 的后面,但是这样明显不对,因为3 和 1 之间应该是插入6 的,3 把 6 的 位置抢了,6 岂不是无家可归了?

因此我们会发现直接在中序中插入节点的做法是有欠考虑的。因为每次在中序数组的某个位置插入节点后该位置在后序数组中的对应位置的节点就被遮挡住了,而且后序数组最右边的节点的位置在中序数组中的位置已经被确定了,不需要再留着了。所以在每次确定一个根节点的位置之后,就在后序中把该节点的右子树整体右移一位,同时把后序遍历中对应中序数组中插入节点位置置 0 ,防止干扰下一次的查找操作,结果就变成了如下:



这时候我们把第二个小问题解决了。但是第一个小问题呢?对于叶子节点怎么处理,开始我是想着通过加入一些 if 判断语句判断是否为叶子节点,如果是叶子节点就另外处理,如上面程序注释掉的部分,但是后来发现总有一个 case (runtime error)过不了, 死活找不到错在哪里,程序反复看了N遍感觉也没有什么问题,而且是case3过不了,没想到是程序执行效率的问题。后来我换了一种办法,不去插入叶子节点,遇到叶子节点就不处理,留在后序遍历数组中,应该可以想象到这样做所有的叶子节点都被留在了后序遍历数组中,而他们的根节点都已经被插入到了中序数组中了,因此原来在后序遍历数组中的根节点的位置都被之零了,最后就成了如下所示的样子了:



可以看出来,最后只要把后序数组加到中序数组中就可以得到最终的中序遍历数组,100%AC了。

最后一点小小的感悟:作为非计算机科班出身,学习数据结构确实感觉得比较吃力。开始看到这一题的时候迟迟不敢下手,感觉很难解决,后来搁浅了一个星期后决定还是应该要尝试。开始没有全面考虑到上面的两个问题,调试了很久才发现bug,但是又不知道该怎么解决,后来95% 的case都通过了又遇到了runtime error 的问题,又反复看了代码,整理了思路,换了方法才解决。真正明白一个道理:当你要做好一件事的时候,虽然遇到的困难会远比自己想象的要多,但是解决这些的问题的方法同样也会比问题本身多。加油!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: