【剑指offer】——与二叉树遍历相关习题练习2

一、二叉搜索树的后序遍历序列

1、题目要求
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回true,否则返回false.假设输入的数组的任意两个数字互不相同。例如下图的树
在这里插入图片描述
输入数组{5,7,6,9,11,10,8}就是他的后序遍历结果,返回ture。

2、题目分析
首先,我们要明确一下二叉搜索树的特点——左子树节点的值都比根结点小,右子树结点的值都比根结点大。再结合我们的后序遍历的特点来分析,后序遍历数组的最后一个数字即为二叉搜索树的根结点。因此在数组中,有一部分连续比该根结点小的数字即为该数的左子树,有一部分连续比该根结点大的数字即为该数的右子树。
还是以上一个例子分析,9为该树的根,{5,7,6}即为该树的左子树,{11,10,8}即为该树的右子树。再在每一个子树里面递归判断该数组序列是否合乎规则。相反的,如果数组是{7,4,6,5}则不能构成二叉搜索树的后续遍历结果。
有了上述的分析,我们就可以写出代码如下:

bool VerifySequenceOfBST(int arr[], int length)
{
	if (arr == nullptr || length <= 0)
		return false;

	int root = arr[length - 1];

	//在二叉搜索树中左子树节点的值小于根结点的值
	int i = 0;
	for (i; i < length; i++)
	{
		if (arr[i] > root)
			break;
	}

	//在二叉搜素树中右子树的值大于根结点的值
	int j = i;
	for (j; j < length; j++)
	{
		if (arr[j] < root)
			return false;
	}

	//判断左子树是不是二叉搜索树
	bool left = true;
	if (i > 0)
		left = VerifySequenceOfBST(arr, i);

	//判断右子树是不是二叉搜索树
	bool right = true;
	if (i < length - 1)
		right = VerifySequenceOfBST(arr, length - 1 - i);

	return (left && right);
}

二、二叉树中和为某一值的路径

1、题目要求
输入一颗二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根结点开始往下一致到叶节点所有经过的结点形成一条路径。例如:图中的二叉树和整数22,则打印出两条路径。在这里插入图片描述
2、题目分析
由于路径是从根结点出发到叶子结点的,所以我们应该先去遍历根结点,在树的三种遍历方式中,只有先序遍历是先遍历根结点的。因为题目要求的打印路径,所以每访问一个结点都需要我们把当前结点添加到路径中去。
还是以题目中具体的例子来加以分析,按照前序遍历的方式第一个结点为根结点10,接下来为结点5,再接下来是结点4到达叶子结点,但是该路径上的三个结点值的和不是22,因此不符合要求的路径。接着要遍历其他结点,再遍历结点7的时候,要先回到结点5,所以,我们需要把结点4从路径中删除,接下来访问结点7的时候再把该结点添加到路径中。
有了以上的分析,我们就可以找到一些规律了:当用前序遍历的方式访问到某个结点是,我们就把该结点添加到路径上,并累加该结点的值,如果该结点是叶子结点,并且路径中结点的值的和刚好哦等于输入的整数,就是符合要求的。代码实现如下:

struct BinaryTreeNode
{
	double m_data;
	BinaryTreeNode* leftchild;
	BinaryTreeNode* rightchild;
};

void findPath(BinaryTreeNode* pRoot, int expectedSum,
	vector<int>& path, int currentSum)
{
	currentSum += pRoot->m_data;
	path.push_back(pRoot->m_data);

	//如果是叶子结点,并且路径上结点值的和等于输入的值,则打印出该路径
	bool isleaf = pRoot->leftchild == nullptr && pRoot->rightchild == nullptr;
	if (currentSum == expectedSum && isleaf)
	{
		cout << "A path is found";
		vector<int>::iterator it = path.begin();
		for (; it != path.end(); ++it)
			cout << *it << endl;
	}

	//如果不是叶子结点,则遍历他的子节点
	if (pRoot->leftchild != nullptr)
		findPath(pRoot->leftchild, expectedSum, path, currentSum);
	if (pRoot->rightchild!= nullptr)
		findPath(pRoot->rightchild, expectedSum, path, currentSum);

	//在返回父节点之前,在路径上删除当前结点
	path.pop_back();
}
void FindPath(BinaryTreeNode* pRoot, int expectedSum)
{
	if (pRoot == nullptr)
		return;

	std::vector<int> path;
	int currentSum = 0;
	findPath(pRoot, expectedSum, path, currentSum);
}

因为在函数退出之前要在路径上删除当前结点并减去当前结点的值以确保返回父节点时路径刚好是从根结点到父节点。由此不难看出保存路径的数据结构实际是一个栈,但是为什么我们要使用vector来模拟栈的结构是因为stack只能得到其栈顶元素,但是我们在打印的时候需要得到路径上的所有节点。

三、二叉树的深度

1、题目要求
输入一颗二叉树的根结点,求该树的深度。从根结点到叶节点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
2、题目分析
这道题很好容易理解,如果该树没有左右子树,则深度为1;如果该树只有左子树没有右子树,则该树的深度为左子树的深度+1;如果该树只有右子树没有左子树,则该树的深度为右子树的深度+1;如果该树既有左子树又有右子树,则该树的深度为深度较大的子树深度+1。用递归加以实现,在遍历的基础上稍做更改即可。

int TreeDepth(BinaryTreeNode* pRoot)
{
	if (pRoot == nullptr)
		return 0;

	int nleft = TreeDepth(pRoot->leftchild);
	int nright = TreeDepth(pRoot->rightchild);

	return (nleft > nright) ? (nleft + 1) : (nright + 1);
}

四、平衡二叉树

1、题目要求
输入一颗二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么他就是一颗平衡二叉树。
2、题目分析
方式一:
有了上一题对二叉树深度的理解过后,做这道题就不是很难理解的了,我们很容易想到一种思路:在遍历树的每个结点的时候,调用函数TreeDepath得到他的左右子树的深度。但是这种方法的时间效率不高,存在一个结点被重复遍历的情况。

方式二:
基于上一种方式的弊端,如果我们用后序遍历的方式遍历二叉树的每一个结点,那么在遍历到一个结点之前我们就已经遍历了他的左右子树,此时只需要记录遍历每一个结点的深度,我们就可以一边遍历一边判断是不是平衡的。代码实现如下:

bool isBalanced(BinaryTreeNode* pRoot, int* depth)
{
	if (pRoot == nullptr)
	{
		*depth = 0;
		return true;
	}
	
	int left, right;
	if(isBalanced(pRoot->leftchild,&left)
		&& isBalanced(pRoot->rightchild, &right))
	{
		int diff = left - right;
		if (diff <= 1 && diff >= -1)
		{
			*depth = 1 + (left > right ? left : right);
			return true;
		}
	}
	return false;
}
bool IsBalanced(BinaryTreeNode* pRoot)
{
	int depth = 0;
	return isBalanced(pRoot, &depth);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值