这道题是,给出一个二叉树的中根遍历和后根遍历,让你找这个树上从根到叶子和最小的一条路径,即对这个树进行先根遍历(dfs)。
很容易想到的一个思路就是先重建,再遍历,我看网上的代码基本也是这么写的。不过我注意到后根遍历倒过来看,就是一个先遍历右子树的先根遍历。可以这么想,在后根遍历中,最后一个元素就是整个树的根,前面分别是左子树和右子树的后根遍历;而在子树的后根遍历中,这个子树的根又在最后,从后往前看的话,就是在最前面,与上一级的根相邻,这么递归地想一想,它就是个先根遍历。也就是说,我们不需要先重建再遍历,后根遍历倒过来,便是一个天然的dfs过程。
那么下来的问题是,通过后根遍历的逆序数组进行dfs时,1.新读入的元素与上一个元素的关系,儿子or兄弟?2.新读入的是否是叶节点?
1. 第一个问题换句话说是何时进行出入栈,可以弄个数组记录节点的访问次数,或者直接写个递归函数;具体操作时,就要用到中根遍历的数组。在中根遍历时,根节点的左面是左子树,右边是右子树,于是我们在中根遍历的数组中找到一个根后,递归时先往它的右边去搞它的右子树,再往左边去(这点与一般的习惯不太一样),两边都找完了,返回,下一个从后根遍历逆序数组中读入的元素自然就是刚返回的那个节点的兄弟。
这里还有一个小问题,某个根只有一边子树,另一边为空的情况。比如说,一个根只有左子树,没有右子树,那么到达这个根后,我新读入一个元素,这个新元素应该是它的左子树的根,在中根遍历数组中的位置处于当前根的左边;然而在递归函数中,会先往根的右边找,这就出现问题了,往右去肯定找不到啊,难道要找到头才停止?这里就要利用中根遍历的一个特点。
最下面的1,2,3...是各个元素在中根遍历中的出现顺序,也就是它们在中根数组中的下标;我们可以看出,一个节点a与他的儿子节点b之间如果存在节点,那么这些节点的层次必然比a,b都低,这点很重要,因为在先根遍历时,我们是访问节点的层次是从高到低的,所以当我们从一个节点出发,在中根的数组中向右或向左找儿子时,碰到的节点只能是未访问过的(层次较低),假如还没找到儿子就碰到个已访问的节点,就说明当前根在这个方向没有子树。
2.判断叶节点:其实就是上面的那个特点,叶节点两边都没儿子,它左右紧挨着的俩节点层次不会比它低,而中根遍历根在中间,两个叶子也不可能相邻,所以一个叶子的左右节点都是访问过的。
感觉写了这么多快给自己绕迷糊了,有些东西自己想想好像挺明白,真要写出来就不行了,毕竟没有学过数据结构,自己想的很多东西也不严谨。一道大水题竟然写了这么多字,比代码长多了,不过不参考模版自己想,确实隐隐约约地感受到了这些优雅的结构的某些魅力。
#include <iostream>
#include<cstdio>
#include<cstring>
#define max 100000000
using namespace std;
int inodr[10010];
int vis[10010];
int pstodr[10010];
int minsum,leaf,ppst;
void fun(int p,int sum){
if(vis[p-1]&&vis[p+1]){
if(sum<minsum) minsum=sum,leaf=inodr[p];
else if(sum==minsum&&inodr[p]<leaf) leaf=inodr[p];
return;
}
int s=pstodr[ppst],i=p+1;
while(!vis[i]){
if(inodr[i]==s)
ppst--, vis[i]=1,fun(i,sum+s);
i++;
}
i=p-1; s=pstodr[ppst];
while(!vis[i]){
if(inodr[i]==s)
ppst--, vis[i]=1,fun(i,sum+s);
i--;
}
}
int main()
{
while(scanf("%d",inodr+1)!=EOF){
int length=2; minsum=max; memset(vis,0,sizeof(vis));
while(getchar()!='\n') scanf("%d",inodr+length++);
for(int i=1;i<length;i++) scanf("%d",pstodr+i);
ppst=length-1; vis[0]=vis[length]=1;
int s=pstodr[ppst--],p=1;
while(inodr[p]!=s) p++;
vis[p]=1;
fun(p,s);
cout<<leaf<<endl;
}
return 0;
}