《剑指Offer》读书笔记---面试题6:重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出二叉树并输出它的头结点。二叉树结点的定义为:

struct BinaryTreeNode
{
	int				m_nValue ;
	BinaryTreeNode*	m_pLeft ;	
	BinaryTreeNode*	m_pRight ;
} ;


一开始看到这道题的时候还真没有想法,后来也的确没有什么想法,基础太弱了,果断去看答案了。


先写一下书上面的思路:

在二叉树的前序遍历序列中,第一个数字总是树的根结点的值。但在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。因此我们需要扫描中序遍历序列,才能找到根结点的值。如题目给出的序列,前序遍历中的第一个数字1就是根结点的值。扫描中序遍历序列,就能确定根结点的值的位置。根据中序遍历的特点,在根结点的值1前面的3个数字都是左子树结点的值,位于1后面的数字都是右子树结点的值。这样我们就在前序遍历和中序遍历两个序列中,分别找到了左右子树对应的子序列。因此,可以用同样的方法去构建左右子树。也就是说,接下来的事情可以用递归的方法去完成。

详见代码:

#include<cstdio>
#include<iostream>

struct BinaryTreeNode
{
	int				m_nValue ;
	BinaryTreeNode*	m_pLeft ;	
	BinaryTreeNode*	m_pRight ;
} ;

BinaryTreeNode* Construct(int* preorder,int* inorder,int length) ;
BinaryTreeNode* ConstructCore(int* startPreorder,int *endPreorder, int* startInorder,int * endInorder) ;

void PreOrderPrintTree(const BinaryTreeNode *pNode) ;

int main(void)
{
	int nPreOrder[] = {1,2,4,7,3,5,6,8} ;
	int nInOrder[] = {4,7,2,1,5,3,8,6} ;
	BinaryTreeNode* root = Construct(nPreOrder,nInOrder,sizeof(nPreOrder)/sizeof(int)) ;

	PreOrderPrintTree(root) ;
	printf("\n") ;

	return 0 ;
}

//前序遍历函数,用来验证结果
void PreOrderPrintTree(const BinaryTreeNode *pNode) 
{
	if(pNode != NULL)
	{
		printf("%d ",pNode->m_nValue) ;
		PreOrderPrintTree(pNode->m_pLeft) ;
		PreOrderPrintTree(pNode->m_pRight) ;
	}
}

//从前序遍历和中序遍历,重建二叉树
BinaryTreeNode* Construct(int* preorder,int* inorder,int length)  //length为序列的长度
{
	if(preorder == NULL || inorder == NULL || length <= 0)
	{
		return	NULL ;
	}

	return ConstructCore(preorder,preorder + length - 1 ,inorder,inorder + length - 1) ;
}

//核心函数
BinaryTreeNode* ConstructCore(int* startPreorder,int* endPreorder, int* startInorder,int* endInorder)
{
	//前序遍历序列的第一个数字是根结点的值
	int rootValue = startPreorder[0] ;
	BinaryTreeNode* root = new BinaryTreeNode() ;

	//构造一个结点
	root->m_nValue = rootValue ;
	root->m_pLeft = root->m_pRight = NULL ;

	if(startPreorder == endPreorder) 
	{
		if(startInorder == endInorder
				&& *startPreorder == *startInorder) //只有一个结点
		{
			return root ;
		}
		else 
			throw std::exception("Invalid input.") ;
	}

	//在中序遍历中找到根结点的值
	int* rootInorder = startInorder ;
	while(rootInorder <= endInorder && *rootInorder != rootValue)
	{
		++rootInorder ;
	}
	
	//如果根结点等于中序遍历的末结点,则数据出错
	if(rootInorder == endInorder && *rootInorder != rootValue)
	{
		throw std::exception("Invalid input.") ;
	}

	int leftLength = rootInorder - startInorder ;		//中序遍历中根结点左结点数
	int* leftPreorderEnd = startPreorder + leftLength ; //前序遍历中左子树结点的下一个位置

	if(leftLength > 0)
	{
		//构建左子树,startPreorder+1为左子树的第一个结点,rootInorder-1为左子树的最后一个结点
		root->m_pLeft = ConstructCore(startPreorder + 1,leftPreorderEnd,startInorder,rootInorder - 1) ;
	}
	if(leftLength < endPreorder - startPreorder)
	{
		//构建右子树
		root->m_pRight = ConstructCore(leftPreorderEnd + 1 ,endPreorder,rootInorder + 1,endInorder) ;
	}
	
	return root ;
}

然后,自己模拟了一下有后序遍历序列和踱遍历序列的情况,如何去构建二叉树。

#include<iostream>
#include<cstdio>

struct BinaryTreeNode
{
	int m_nValue ;
	BinaryTreeNode* m_pLeft ;
	BinaryTreeNode* m_pRight ;
} ;

BinaryTreeNode* Construct(int *postorder,int* inorder,int length) ;
BinaryTreeNode* ConstructCore(int *startPostorder,int *endPostorder,int* startInorder,int *endInorder) ;

void PrintPreOrder(BinaryTreeNode *pNode) ;

int main(void)
{
	int nPostOrder[] = {7,4,2,5,8,6,3,1} ;
	int nInOrder[] = {4,7,2,1,5,3,8,6} ;

	BinaryTreeNode* root = Construct(nPostOrder,nInOrder,sizeof(nPostOrder)/sizeof(int)) ;
	PrintPreOrder(root) ;	
	printf("\n") ;

	return 0 ;
}

BinaryTreeNode* Construct(int *postorder,int* inorder,int length) 
{
	if(NULL == postorder || NULL == inorder || length <= 0)  
	{
		return NULL ;
	}

	return ConstructCore(postorder,postorder + length -1 ,inorder,inorder + length - 1)  ;
}


void PrintPreOrder(BinaryTreeNode *pNode) 
{
	if(pNode != NULL)
	{
		PrintPreOrder(pNode->m_pLeft) ;
		PrintPreOrder(pNode->m_pRight) ;
		printf("%d ",pNode->m_nValue) ;
	}
}


BinaryTreeNode* ConstructCore(int *startPostorder,int *endPostorder,int* startInorder,int *endInorder) 
{
	int nValue = *endPostorder ;
	BinaryTreeNode* root = new BinaryTreeNode ;
	root->m_nValue = nValue ;
	root->m_pLeft = root->m_pRight = NULL ;

	if(startPostorder == endPostorder)  //叶子结点
	{
		if(startInorder == endInorder &&   //两个序列都要判断,否则就是无效僌
				 *startPostorder == *endPostorder)
		{
			return root ;
		}
		else
		{
			throw std::exception("Invalid input.") ;
		}
	}

	int* rootInorder = startInorder ;
	while(rootInorder <= endInorder && *rootInorder != nValue)
	{
		rootInorder++ ;
	}

	if(*rootInorder != nValue && rootInorder == endInorder)
	{
		throw std::exception("Invalid input.") ;
	}
	
	int nRightLength = endInorder - rootInorder ;
	int* rightPostorderStart = endPostorder - nRightLength ;

	if(nRightLength > 0)
	{
		//构建右子树
		root->m_pRight = ConstructCore(rightPostorderStart,endPostorder - 1,rootInorder + 1,endInorder) ;
	}
	if(startInorder < rootInorder)
	{
		//构建左子树 
		root->m_pLeft = ConstructCore(startPostorder,rightPostorderStart-1,startInorder,rootInorder - 1) ;
	}
	
	return root ;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值