二叉树的面试题(二)

接着上篇博客接着讲述二叉树的面试题 :二叉树面试题(一)

9 由前序遍历序列和中序遍历序列重建二叉树

10 判断一个节点是否在二叉树中

11 判断二叉树是不是平衡二叉树

12 判断一颗二叉树是否为完全二叉树

13 求二叉树的镜像

14 将二叉查找树变为有序的双向链表


9 由前序遍历序列和中序遍历序列重建二叉树

 遍历组合唯一 确定二叉树:必须包含中序遍历才能唯一确定二叉树

思路:二叉树前序遍历序列中,第一个元素总是树的根节点的值。中序遍历序列中,左子树的节点的值位于根节点的值的左边,右子树的节点的值位于根节点的右边。

递归解法:
(1)如果前序遍历为空或中序遍历为空或节点个数小于等于0,返回NULL。
(2)创建根节点。前序遍历的第一个数据就是根节点的数据,在中序遍历中找到根节点的位置,可分别得知左子树和右子树的前序和中序遍历序列,重建左右子树。

	BinaryTreeNode<T>* _ConstructTree(T *startPreOrder,T *endPreOrder,T *startInOrder,T *endInOrder)
	{
		//前序遍历的第一个结点为根节点
		T rootValue = startPreOrder[0];
		BinaryTreeNode<T>* pRoot = new	BinaryTreeNode<T>(rootValue);
		//只有一个元素
		if (startPreOrder == endPreOrder && *startInOrder == *startPreOrder)
			return pRoot;
		//在中序遍历中找到根节点的值
		T* rootInOreder = startInOrder;
		while (rootInOreder <= endInOrder && *rootInOreder != rootValue)
		{
			++rootInOreder;
		}//跳出循环可能没有找到
		if (*rootInOreder != rootValue)
			throw std::exception("Invalid input");

		int leftlength = rootInOreder - startInOrder; //左子树的长度
		T *leftPreEnd = startPreOrder + leftlength; //前序左子树的end位置
		if (leftlength > 0)//构建左子树
		{
			pRoot->_pLeftChild = _ConstructTree(startPreOrder + 1, leftPreEnd, startInOrder, rootInOreder - 1);
		}
		if (leftlength < endPreOrder - startPreOrder)//构建右子树
		{
			pRoot->_pRightChild = _ConstructTree(leftPreEnd + 1, endPreOrder, rootInOreder + 1, endInOrder);
		}
		return pRoot;
	}

10 判断一个节点是否在二叉树中

思路:先看该节点是否在为根节点,在查看左子树,再查看右子树

	BinaryTreeNode<T>* _Find(BinaryTreeNode<T>* pRoot, const T& data)
	{
		if (pRoot == NULL)
			return NULL;
		if (pRoot->_data == data)
			return pRoot;
		BinaryTreeNode<T>* find;
		if (find = _Find(pRoot->_pLeftChild, data))
			return find;
		return _Find(pRoot->_pRightChild, data);
	}

11 判断二叉树是不是平衡二叉树

递归解法:
(1)如果二叉树为空,返回真
(2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假


bool IsAVL(BinaryTreeNode * pRoot, int & height)  
{  
    if(pRoot == NULL) // 空树,返回真  
    {  
        height = 0;  
        return true;  
    }  
    int heightLeft;  
    bool resultLeft = IsAVL(pRoot->m_pLeft, heightLeft);  
    int heightRight;  
    bool resultRight = IsAVL(pRoot->m_pRight, heightRight);  
    if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1) // 左子树和右子树都是AVL,并且高度相差不大于1,返回真  
    {  
        height = max(heightLeft, heightRight) + 1;  
        return true;  
    }  
    else  
    {  
        height = max(heightLeft, heightRight) + 1;  
        return false;  
    }  
}


12 判断一颗二叉树是否为完全二叉树

思路:判断二叉树是否为完全二叉树。完全二叉树的定义是,前n-1层都是满的,第n层如有空缺,则是缺在右边,即第n层的最右边的节点,它的左边是满的,右边是空的。

解法:采用广度优先遍历,从根节点开始,入队列,如果队列不为空,循环。遇到第一个没有左儿子或者右儿子的节点,设置标志位,如果之后再遇到有左/右儿子的节点,那么这不是一颗完全二叉树。


bool IsCompleteBinaryTree(BinaryTreeNode<T> *pRoot )
	{
		//空树也是完全二叉树
		if (pRoot == NULL)
			return true; 
		queue<BinaryTreeNode<T>*> q;
		q.push(pRoot); //根节点入队
		bool mustLeft = false;
		bool result = true;
		while (!q.empty())
		{
			BinaryTreeNode<T> *pCur = q.front();
			q.pop();
			if (mustLeft) //出现了 空子树的节点
			{
				if (pCur->_pLeftChild != NULL || pCur->_pRightChild != NULL)
				{
					result = false;
					break;
				}
			}
			else 
			{
				if (pCur->_pLeftChild != NULL && pCur->_pRightChild != NULL)
				{
					q.push(pCur->_pLeftChild);
					q.push(pCur->_pRightChild);
				}
				else if (pCur->_pLeftChild == NULL && pCur->_pRightChild != NULL)
				{
					result = false;
					break;
				}
				else if (pCur->_pRightChild == NULL && pCur->_pLeftChild != NULL)
				{
					q.push(pCur->_pLeftChild);
					mustLeft = true;
				}
				else
				{
					mustLeft = true;
				}
			}
		}
		return result;
	}

另一种思路:

任意的一个二叉树,都可以补成一个满二叉树。这样中间就会有很多空洞。在广度优先遍历的时候,如果是满二叉树,或者完全二叉树,这些空洞是在广度优先的遍历的末尾,所以,但我们遍历到空洞的时候,整个二叉树就已经遍历完成了。而如果,是非完全二叉树,我们遍历到空洞的时候,就会发现,空洞后面还有没有遍历到的值。这样,只要根据是否遍历到空洞,整个树的遍历是否结束来判断是否是完全的二叉树。

	bool IsCompleteTree(BinaryTreeNode<T> *pRoot)
	{
		if (pRoot == NULL)
			return true;
		queue<BinaryTreeNode<T>*> q;
		q.push(pRoot);
		BinaryTreeNode<T>* pCur = NULL;
		//层序遍历二叉树 ,当遇到空节点是退出
		while ((pCur = q.front()) != NULL)
		{
			q.pop();
			q.push(pCur->_pLeftChild);
			q.push(pCur->_pRightChild);
		}
		q.pop();//把当前节点为空出队
		//查看剩余队列中是否有不为空的节点
		while (!q.empty())
		{
			if (q.front() != NULL)
				return false;
			q.pop();
		}
		return true;
	}


13 求二叉树的镜像

递归解法:
(1)如果二叉树为空,返回空
(2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树


	BinaryTreeNode<T>* Mirror(BinaryTreeNode<T> * pRoot)
	{
		if (pRoot == NULL) // 返回NULL  
			return NULL;
		BinaryTreeNode<T> * pLeft = Mirror(pRoot->_pLeftChild); // 求左子树镜像  
		BinaryTreeNode<T> * pRight = Mirror(pRoot->_pRightChild); // 求右子树镜像  
		// 交换左子树和右子树  
		pRoot->_pLeftChild = pRight;
		pRoot->_pRightChild = pLeft;
		return pRoot;
	}


14将二叉查找树变为有序的双向链表

要求:不能创建新的节点,只能调整树种节点指针的指向

在二叉搜索树中,每个结点都有两个分别指向其左、右子树的指针,左子树结点的值总是小于父结点的值,右子树结点的值总是大于父结点的值。

思路:(1)由于要求链表是有序的,可以借助二叉树中序遍历,因为中序遍历算法的特点就是从小到大访问结点。当遍历访问到根结点时,假设根结点的左侧已经处理好,只需将根结点与上次访问的最近结点(左子树中最大值结点)的指针连接好即可。进而更新当前链表的最后一个结点指针。
(2)由于中序遍历过程正好是转换成链表的过程,即可采用递归处理

void ConvertNode(BinaryTreeNode<T>* pNode, BinaryTreeNode<T>** pLastNodeInList)
	{
		if (pNode == NULL)
			return;
		BinaryTreeNode<T>* pCurrent = pNode;
		//递归处理左子树  
		if (pCurrent->_pLeftChild != NULL)
			ConvertNode(pNode->_pLeftChild, pLastNodeInList);
		//处理当前结点              
		pCurrent->_pLeftChild = *pLastNodeInList;    //将当前结点的左指针指向已经转换好的链表的最后一个位置  
		if (*pLastNodeInList != NULL)
			(*pLastNodeInList)->_pRightChild = pCurrent;//将已转换好的链表的最后一个结点的右指针指向当前结点  

		*pLastNodeInList = pCurrent;//更新链表的最后一个结点  
		//递归处理当前结点的右子树  
		if (pCurrent->_pRightChild != NULL)
			ConvertNode(pNode->_pRightChild, pLastNodeInList);
	}
BinaryTreeNode<T>* Convert(BinaryTreeNode<T>* pRootInTree)
	{
		BinaryTreeNode<T>* pLastNodeInList = NULL;

		ConvertNode(pRootInTree, &pLastNodeInList);

		//pLastNodeInList指向双向链表的尾结点,再次遍历找到头结点  
		BinaryTreeNode<T>* pHeadOfList = pLastNodeInList;
		while (pHeadOfList != NULL && pHeadOfList->_pLeftChild != NULL)
			pHeadOfList = pHeadOfList->_pLeftChild;

		return pHeadOfList;
	}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值