Description
二叉树的三种遍历都可以通过递归实现。
如果我们知道一棵二叉树的先序和中序序列,可以用递归的方法求后序遍历序列。
输入格式
两行,第一行一个字符串,表示树的先序遍历,第二行一个字符串,表示树的中序遍历。树的结点一律用小写字母表示。
输出格式
一个字符串,树的后序序列。
输入样例
abcde
bcade
输出样例
cbeda
方法1:利用先序遍历和中序遍历的数据来确定整棵树的结构,然后根据这棵树的结构来进行一次后序遍历即可。
那么应该如何来进行这样的操作呢?首先先明确先序遍历和中序遍历的特点。
先序遍历的特点是:第一个元素必定是这棵树的根节点,而中序遍历居中的那一个节点是根节点(奇数和偶数的情况需要分类,而先序遍历正好为我们提供了一个参照物),来确定每一棵树的结构。
那么先序遍历的第二个元素是什么呢?根据根左右可知,第二个元素是左子树的根节点,第三个节点是左子树的非空左子树的根节点…以此类推直到达到整棵树的终节点。由此我们可以看出这个遍历具有一个特点:每一次遍历的方法都是一样的,直到遇到某一个边界就退回上一次,这正是一种递归分治的算法思想,所以利用递归分治来建出我们想要的树就可以了。
那么如何来建呢?我们首先来模拟这个过程。
首先先要找到整棵树的根节点。
我们先来处理左子树的问题,首先的问题肯定是要先做一个左子树的创建了,在这里我们找到了根节点的位置,先把根节点存进去,然后把范围给缩小到左子树的范围,如图。
通过这样的模拟,我们可以知道,先序遍历的数组每次都只是向前移动一格,而在左遍历的过程中,左遍历的边界是根据当前的长度来限定的,也就是当前根节点(指的是树,可以是子树,也可以是我们完整的一颗树)的位置-中序遍历到的元素的位置(子树长度),这个意思是左遍历的区间是(左子树的起点,左子树的起点+当前子树长度),当前子树长度就是根据先序遍历得到的当前根节点来确定的了。
那么什么时候是结束条件呢?也就是当左遍历的区间没有含义的时候就可以结束了,我们知道每次建数操作左子树的起点都会+1,而长度是在每一次递归的过程都会减少的,当上一次的左子树起点==左子树的起点+长度的时候,那么下一次就会左子树的长度+1,而长度=0,这时候就会触发条件XL>XR,将本次递归得到的树节点置空返回。那么这时候左子树就构建完成了。
那么右子树的遍历也是一致的,同样的思想,只是左右边界的确定不一样了,我们知道中序遍历和先序遍历都具有一个特点,那么就是都是最后遍历右子树,那么也就是右子树的边界恒为数组的最右端,那么左边界如何确定和进行调整呢?同样的,中序遍历可以先通过找到右子树的起点,然后从右子树的起点+1出发,值得注意的是,由于中序遍历和先序遍历都是最后遍历右子树,那么也就是说通过中序遍历找出来的右子树的长度可以用在先序遍历中来找到起点,也就是XL+len+1。
讲解了这么多来看看AC代码。
#include <iostream>
#include <cstring>
using namespace std;
const int M=500+5;
char x[M],z[M];/*先序中序数组*/
typedef char ElemType;
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;//左右孩子指针
} BiTNode,*BiTree;
void CT(BiTree &T,int XL,int XR,int ZL,int ZR)
{
if(XL>XR)
{
T=NULL;return ;
}
else
{
T=new BiTNode;T->data=x[XL];
int pos;
for(int i=ZL;i<=ZR;i++)
{
if(z[i]==x[XL])
{
pos=i;
break;
}
}
int len=pos-ZL;
CT(T->lchild,XL+1,XL+len,ZL,pos-1);
CT(T->rchild,XL+len+1,XR,pos+1,ZR);
}
}
void HX(BiTree T)/*左右根*/
{
if(T==NULL)
{
return ;
}
HX(T->lchild);
HX(T->rchild);
cout<<T->data;
}
int main()
{
ios::sync_with_stdio(false);
BiTree T;
cin>>x>>z;
int n=strlen(x);
CT(T,0,n-1,0,n-1);
HX(T);
return 0;
}