PAT-A 1138 题解
题意分析:
给出一个树的先序遍历和中序遍历,要求后序遍历输出的第一个数。
考察要点
对于后续遍历过程的认识
算法分析
这道题和后面所有的树的题基本上类似,只需要求局部解,所以我们不需要构建出整个树。只需要利用先序遍历和中序遍历的性质就能把这一个解求出来。
首先分析一下后续遍历的第一个数是怎么输出的:
从根节点开始,不断地分别往左子树和右子树递归。当到达叶子结点的时候输出。而且这个点一定是唯一的。也就是说,存在一条从根节点通往这个节点的唯一路径。
我们换一种思路。这个路径应该是这样来的:从根节点开始,每一次优先选择左子树进行递归,只有当左子树不存在的时候才换成右子树递归,直到左右子树都无法进行递归的时候,选择输出这个点。这就是第一个点的输出过程。那么代码就好写了。这实际上就是一道模板题。
代码
#include<iostream>
#include<cstdio>
#include<unordered_map>
using namespace std;
const int MAX_N = 50010;
int pre[MAX_N],in[MAX_N];
unordered_map<int,int> pos_mappping;//用于映射value-index的关系
int n;
//解法一,利用后序遍历的性质
void find(int in_l_pos,int in_r_pos,int pre_root_pos){
//当中序遍历的左右区间重合的时候,说明它已经没有左右子树了
//这个时候一定是递归的终点。可以放心大胆地输出
if(in_r_pos == in_l_pos) {
cout<<in[in_l_pos]<<endl;
return;
}
//中序遍历的根节点的下标索引
int in_root_pos = pos_mappping[pre[pre_root_pos]];
int left_num = in_root_pos - in_l_pos;
//如果左边还能递归下去,就往左边递归
if(in_l_pos<in_root_pos){
find(in_l_pos,in_root_pos-1,pre_root_pos+1);
}
//如果发现左边无法递归了,就选择往右边递归。
else
{
find(in_root_pos+1,in_r_pos,pre_root_pos+1+left_num);
}
//左边不能递归了,那就往右边递归下去
//因为这里的递归选择是线性增长的,而不是之前的指数增长,所以时间复杂度为O(log n)
}
int main(){
cin>>n;
for(int i = 1;i<=n;i++){
scanf("%d",&pre[i]);
}
for(int i = 1;i<=n;i++){
scanf("%d",&in[i]);
pos_mappping[in[i]] = i;
}
find(1,n,1);
system("pause");
return 0;
}
接下来介绍解法二,也就是直接构建出一颗完整的二叉树。然后对二叉树做后序遍历
#include<iostream>
#include<cstdio>
#include<unordered_map>
using namespace std;
const int MAX_N = 50010;
int pre[MAX_N],in[MAX_N];
unordered_map<int,int> pos_mappping;//用于映射value-index的关系
int n;
struct node
{
node* lchild;
node* rchild;
int data;
};
void find(int in_l_pos,int in_r_pos,int pre_root_pos){
//当中序遍历的左右区间重合的时候,说明它已经没有左右子树了
//这个时候一定是递归的终点。可以放心大胆地输出
if(in_r_pos == in_l_pos) {
cout<<in[in_l_pos]<<endl;
return;
}
//中序遍历的根节点的下标索引
int in_root_pos = pos_mappping[pre[pre_root_pos]];
int left_num = in_root_pos - in_l_pos;
//如果左边还能递归下去,就往左边递归
if(in_l_pos<in_root_pos){
find(in_l_pos,in_root_pos-1,pre_root_pos+1);
}
//如果发现左边无法递归了,就选择往右边递归。
else
{
find(in_root_pos+1,in_r_pos,pre_root_pos+1+left_num);
}
//左边不能递归了,那就往右边递归下去
//因为这里的递归选择是线性增长的,而不是之前的指数增长,所以时间复杂度为O(log n)
}
node* create(int inL,int inR,int preRoot){
if(inL > inR){
return NULL;
}
node* root = new node;
root->data = pre[preRoot];
int inRoot = pos_mappping[pre[preRoot]];
int left_num = inRoot-inL;
root->lchild = create(inL,inRoot-1,preRoot+1);
root->rchild = create(inRoot+1,inR,preRoot+left_num+1);
return root;
}
bool hasPrint = false;
void postOrder(node* root){
if(hasPrint){
return;
}
if(root == NULL){
return;
}
postOrder(root->lchild);
postOrder(root->rchild);
if(!hasPrint){
cout<<root->data<<endl;
hasPrint = true;
}
}
int main(){
cin>>n;
for(int i = 1;i<=n;i++){
scanf("%d",&pre[i]);
}
for(int i = 1;i<=n;i++){
scanf("%d",&in[i]);
pos_mappping[in[i]] = i;
}
//find(1,n,1);
node* root = create(1,n,1);
postOrder(root);
system("pause");
return 0;
}
总结
PAT近几年考察树这方面的知识考的比较灵活,只要你对树这个数据结构了解的很透彻,基本上很少要你从零开始直接构建一棵树出来。像这道题只要利用好这个性质,时间复杂度就在O(log n),这是一种很优秀的解法。而后一种建树的解法也可以用,一开始还以为会TLE,但发现好像其实能过。。。也不知道这道题50000的数据规模开了来干嘛的。。。如果有个测试点数据规模到40000以上,这个点掘b过不了,只能说明PAT的测试数据还是有点水。。