接着上篇博客接着讲述二叉树的面试题 :二叉树面试题(一)
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;
}