问题描述:
给定前序遍历和中序遍历结果,比如:
前序:a b d c e f
中序:d b a e c f
重建这颗二叉树。
思路:
1.前序遍历的第一个结点必然是要重建的这棵树的根节点。比如a就是当前序列的根节点。
2. 在中序序列中找到a结点,把中序遍历结果分为两个序列,分别为a 的左子树d b,以及a的右子树 e c f。同时把前序序列也分为了左子树序列和右子树序列。
3.重建a的左子树和右子树,将序列划分为(b d, d b)(c e f, e c f),然后重复步骤1,递归求解。
代码如下:
struct Node
{
Node():pLChild(NULL),pRChild(NULL){}//如果不初始化为NULL就要在递归函数中返回NULL
char cdata;
Node *pLChild;
Node *pRChild;
};
//pre前缀的下标用来分割前序遍历字符串,in前缀下标用来分割中序遍历字符串
Node *RebuildBinTree(string preOrder,string inOrder,int preBegin , int preEnd ,int inBegin,int inEnd)
{
if(preOrder.size()==0 || inOrder.size()==0 ||preOrder.size()!=inOrder.size()){//不合理的输入
cerr<<"不合理的输入!"<<endl;
exit(-1);
}
if(preBegin > preEnd)//递归截止条件
return NULL;
int pos = inOrder.find(preOrder[preBegin]);//前序第begin个元素在中序中的下标
if(string::npos == pos){//未找到,返回空
cerr<<"错误,无法建立这样的树!"<<endl;
exit(-2);
}
Node *pRoot = new Node;//为根节点分配内存
pRoot->cdata = preOrder[preBegin];
int leftTreeLen = pos-inBegin;
int rightTreeLen = inEnd-pos;
if(leftTreeLen>0)
pRoot->pLChild = RebuildBinTree(preOrder,inOrder,preBegin+1,preBegin+leftTreeLen,inBegin,pos-1);
if(rightTreeLen>0)
pRoot->pRChild = RebuildBinTree(preOrder,inOrder,preBegin+leftTreeLen+1,preEnd,pos+1,inEnd);
return pRoot;
}
注意到代码实现的过程中,如果不添加构造函数初始化pLChild与pRChild,则一定要在函数中返回NULL,比如下面的例子,将会导致某些没有孩子的节点的左或右孩子节点不为NULL。
struct Node
{
char cdata;
Node *pLChild;
Node *pRChild;
};
//给出一个常见的错误范例,递归时一定要注意return NULL或是给节点初始化为NULL
Node *RebuildBinTree(string preOrder,string inOrder,int preBegin , int preEnd ,int inBegin,int inEnd)
{
if(preOrder.size()==0 || inOrder.size()==0 ||preOrder.size()!=inOrder.size()){//不合理的输入
cerr<<"不合理的输入!"<<endl;
exit(-1);
}
if(preBegin > preEnd)//递归截止条件
return NULL;
int pos = inOrder.find(preOrder[preBegin]);//前序第begin个元素在中序中的下标
if(string::npos == pos){//未找到,返回空
cerr<<"错误,无法建立这样的树!"<<endl;
exit(-2);
}
Node *pRoot = new Node;//为根节点分配内存
pRoot->cdata = preOrder[preBegin];
int leftTreeLen = pos-inBegin;
int rightTreeLen = inEnd-pos;
if(leftTreeLen>0)
pRoot->pLChild = RebuildBinTree(preOrder,inOrder,preBegin+1,preBegin+leftTreeLen,inBegin,pos-1);
// else pRoot->pLChild=NULL;//忘记了置空则会在遍历的时候访问异常。或者可以去掉if判断,直接让函数遍历节点的左右孩子,对于叶子会让它的左右孩子返回NULL
if(rightTreeLen>0)
pRoot->pRChild = RebuildBinTree(preOrder,inOrder,preBegin+leftTreeLen+1,preEnd,pos+1,inEnd);
// else pRoot->pRChild=NULL;
return pRoot;
}
扩展问题:
1.如何判断给定的前序遍历和中序遍历的结果是否合理呢?
答:由于前序的第一个节点是根节点,从中序遍历里面找到这个根节点,然后从中序中看左右子树是否在前序中合理。如string pre2 = "abdefc";string mid2 = "dcfeab";
就不合理,a的左子树dcfe在前序遍历中布局不连续,且不是紧挨着根节点a。判断代码如下:
bool valid = true;
void isValid(string preOrder , string inOrder , int preBegin , int preEnd , int inBegin , int inEnd)
{
if(preOrder.size()!=inOrder.size()){//序列长度不同,不合理
valid = false;
return;
}
if(preBegin>preEnd || inBegin>inEnd || !valid)//一旦为false或是到了根节点,返回
return;
int pos = inOrder.find(preOrder[preBegin]);
if(pos == string::npos){
valid = false;
return;
}
int leftTreeLen = pos-inBegin;
int rightTreeLen = inEnd-leftTreeLen;
if(leftTreeLen<0 || rightTreeLen<0){//说明find函数返回的pos越出规定界了,序列不合理。
valid = false;
return;
}
if(leftTreeLen > 0)//如果存在左子树,遍历左子树
isValid(preOrder,inOrder,preBegin+1,preBegin+leftTreeLen,inBegin,pos);
if(rightTreeLen > 0)//如果存在右子树,遍历右子树
isValid(preOrder,inOrder,preBegin+leftTreeLen+1,preEnd,pos+1,inEnd);
}
2.如果知道前序和后序遍历的结果,能重构二叉树么?
答:中序遍历配合另外任何一个遍历,能重建二叉树。其他的任意两个序列的组合都不能唯一的确定重建的二叉树。反例:任何结点只有左子树的二叉树和任何结点只有右子树的二叉树,其前序序列相同,后序序列相同。
答:中序遍历配合另外任何一个遍历,能重建二叉树。其他的任意两个序列的组合都不能唯一的确定重建的二叉树。反例:任何结点只有左子树的二叉树和任何结点只有右子树的二叉树,其前序序列相同,后序序列相同。
对于已知中序和后序,先在后序中取最后一位节点,可以知道是根节点,将中序序列一分为二,左边为左子树,右边为右子树,再在后序中找与左子树对应的节点序列的最后一位,可知它为左子树根节点,同理可以递归的得到整个树。如中序:dbaecf,后序dbefca,先在后序中找到a为根节点,a把中序中一分为二,找到左子树db的后序对应位db,可知后序序列的最后一个节点b为左子树的根节点,以此类推···