[编程之美] PSet3.9 重建二叉树

问题描述:

       给定前序遍历和中序遍历结果,比如:

       前序: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为左子树的根节点,以此类推···



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值