题目:输入某二叉树的前序遍历和中序遍历的结果,请重建二叉树。假设输入的前序遍历和中序遍历的结果中都不包含重复的数字。
例如:输入前序遍历序列{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后面的数字都是右子树节点的值。
由于在中序遍历序列中,有3个数字是左子树节点的值,因此左子树共有3个左子节点。同样,在前序遍历序列中,根节点后面的3个数字就是3个左子树节点的值,再后面的所有数字都是右子树节点的值,这样我们就在前序遍历和中序遍历中分别找到了左、右子树对应的子序列。
既然我们已经分别找到了左、右子树的前序遍历序列和中序遍历序列,我们可以用同样的方法分别构建左、右子树。
思路总结:
先根据前序遍历序列的第一个数字创建根结点,接下来在中序遍历序列中找到根结点的位置,这样就能确定左、右子树结点的数量。在前序遍历和中序遍历的序列中划分了左、右子树结点的值之后,就可以递归地去分别构建它的左右子树。
递归实现(C++)
// 前向声明
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder);
BinaryTreeNode* Construct(int *preorder, int *inorder, int length)
{
// 确保输入的数组是有值的,处理为空的情况
if (preorder == nullptr || inorder == nullptr || length <= 0)
return nullptr;
return ConstructCore(preorder, preorder + length - 1, inorder, inorder + length - 1);
}
// startPreorder 开始前序遍历指针 startInorder 开始的中序遍历指针
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 = nullptr;
// 处理只有根节点的情况
if (startPreorder == endPreorder) { // 如果前序遍历序列的开始指针等于结尾指针
if (startInorder == endInorder) // 中序遍历序列的开始指针等于结束指针,那么只有根节点,直接返回
return root;
else
{
logic_error ex("Invalid Input");
throw exception(ex);
}
}
// 在中序遍历序列中找到根节点的值
int *rootInorder = startInorder;
while (rootInorder<=endInorder && *rootInorder != rootValue)
++rootInorder;
// 中序遍历结束,找不到根节点,输入中序序列错误,抛出异常
if (rootInorder == endInorder && *rootInorder != rootValue)
{
logic_error ex("Invalid Input");
throw exception(ex);
}
// 找到了根节点,就可以确认左子树和右子树的范围
int leftLength = rootInorder - startInorder;
int *leftPreorderEnd = startPreorder + leftLength;
if (leftLength > 0)
{
// 构建左子树
root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);
}
if (leftLength < endPreorder - startPreorder)
{
// 构建右子树
root->m_pRight = ConstructCore(leftPreorderEnd + 1, endInorder, rootInorder + 1, endInorder);
}
return root;
}