CHAPTER_9 提高篇(3)——数据结构(2)
9.2.5 重建二叉树
首先我们要给出一个关于二叉树的结论:给定一个中序遍历序列和先序遍历序列,可以确定一颗二叉树;给定中序遍历和后序遍历,也可以确定一个二叉树;给定中序遍历和层序遍历,同样可以确定二叉树。而后序遍历、先序遍历、层序遍历中任意组合,都不能确定二叉树。
由此这节我们来解决一个重要问题:给定一个二叉树的先序遍历和中序遍历,如何重建这颗二叉树。已知先序序列为pre1、pre2、... 、pren,中序序列为in1、in2、... 、inn。我们接下来对该问题进行分析。
解决这个问题我们先要理解先序和中序遍历的性质。先序遍历序列有一个重要的性质:序列的第一个结点为当前树的根。这根据先序遍历的过程我们很容易理解。因此对于给定的树,根节点即为pre1。中序序列同样有一条有趣的性质:假若已知当前树的根节点为ink,则序列被分为两部分,in1到ink-1为根节点的左子树,ink+1到inn为根节点的右子树。即如下图所示:
通过上面的性质,我们得以确定树的三部分:根节点、左子树的先序和中序序列、右子树的先序和中序序列。接下来我们对左子树的序列用同样的操作重建左子树,右子树也同样。如此一直递归下去,递归边界是什么呢?答案很显然,当前递归的树为空,即给定序列长度小于等于0时,递归结束。通过递归的方法,我们得以重建这颗二叉树。
下面通过一道例题,来练习这个思路的代码实现。
题目:
给出一颗二叉树的先序遍历序列和中序遍历序列。求这颗二叉树的层序遍历序列。
输入样例:
6 //结点个数
1 2 4 5 3 6 //先序序列
4 2 5 1 3 6 //中序序列
输出样例:
1 2 3 4 5 6
思路:
首先由给定序列重建二叉树,再对二叉树层序遍历即可。由于在上一节已经给出层序遍历代码,这里只讲解重建二叉树。
先序序列的第一个元素pre1,是当前树的根节点。我们遍历中序序列找到下标ink=pre1,得而确定根节点在中序序列的位置。因此中序序列被ink分为两部分,左子树的结点个数为ink-1,左子树在先序序列中对应的区间为[2,ink],在中序序列中对应的区间为[1,ink-1];右子树个数为n-ink,在先序序列中对应的区间为[ink+1,n],在中序序列中对应的区间为[ink+1,n]。至此第一轮递归已经完成,我们得到了根节点,然后对左子树和右子树的序列进行递归。
现在来看递归过程的一般情况。假设当前递归的先序序列为[preL,preR],中序序列为[inL,inR]。我们遍历中序序列得到当前根节点的下标k,那么左子树的结点个数为k-inL,其先序序列为[preL+1,preL+k-inL]、中序序列为[inL,k-1];右子树的结点个数为inR-k,其先序序列为[preL+k-inL+1,preR]、中序序列为[k+1,inR]。
通过一层层的递归,我们要确定递归边界。当preR-preL<0或者inR-inL<0时,说明序列长度小于等于0,则退出。
代码如下:
#include<iostream>
#include<queue>
using namespace std;
const int maxn=100;
int preOrder[maxn],inOrder[maxn]; //分别存储先序和中序序列
struct node {
int data;
node *lchild,*rchild;
};
node* create(int preL,int preR,int inL,int inR) { //先序序列[preL,preR],中序序列[inL,inR]
if(preR-preL<0) {
return NULL;
}
node *root=new node; //申请新节点
root->data=preOrder[preL];
int k;
for(k=inL;k<=inR;k++) { //找到中序序列中根节点的下标位置
if(inOrder[k]==preOrder[preL])
break;
}
root->lchild=create(preL+1,preL+k-inL,inL,k-1); //递归构建左子树
root->rchild=create(preL+k-inL+1,preR,k+1,inR); //递归构建右子树
return root;
}
void BFS(node* root) { //层序遍历
queue<node*> q;
q.push(root);
while(!q.empty()) {
node *now=q.front();
q.pop();
cout<<now->data<<' ';
if(now->lchild)
q.push(now->lchild);
if(now->rchild)
q.push(now->rchild);
}
}
int main() {
int n;
cin>>n;
for(int i=0;i<n;i++) {
cin>>preOrder[i];
}
for(int i=0;i<n;i++) {
cin>>inOrder[i];
}
node *root=NULL; //创建根节点
root=create(0,n-1,0,n-1); //重建二叉树
BFS(root); //层序遍历
return 0;
}
解决了中序和先序重建二叉树的问题,我们也同时解决了中序和后序的重建问题。因为后序和中序重建的思路和上面相同,唯一的区别在于:后序序列的最后一个结点为当前树的根节点。