《算法笔记》读书记录DAY_32

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;
}

解决了中序和先序重建二叉树的问题,我们也同时解决了中序和后序的重建问题。因为后序和中序重建的思路和上面相同,唯一的区别在于:后序序列的最后一个结点为当前树的根节点。

猜你喜欢

转载自blog.csdn.net/jgsecurity/article/details/120852491