剑指offer - 重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建二叉树。假设输入的前序遍历和中序遍历的结果中都不包含重复的数字。

例如:输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建如下图的二叉树并输出它的头节点。

182347578161183.jpg

二叉树节点的定义如下

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

二叉树的遍历流程

通常树有如下几种遍历方式:

前序遍历:先访问根结点,再访问左子结点,最后访问右子结点。
中序遍历:先访问左子结点,再访问根结点,最后访问右子结点。
后序遍历:先访问左子结点,再访问右子结点,最后访问根结点。

jlIAfhlcL3.png

注意:

前序和后序的遍历序列的无法唯一确定一颗二叉树。

分析

在二叉树的前序遍历中,第一个数字总是树的根节点的值,但在中序遍历序列中,根节点的值在序列的中间,左子树的节点的值位于根节点的值的左边,而右子树的节点的值位于根节点的值的右边。因此我们需要扫描中序遍历序列,才能找到根节点的值

前序遍历序列的第一个数字1就是根节点的值。扫描中序遍历序列,就能确定根节点的值的位置。根据中序遍历的特点,在根节点的值1前面的3个数字都是左子树节点的值,位于1后面的数字都是右子树节点的值。

由于在中序遍历序列中,有3个数字是左子树节点的值,因此左子树共有3个左子节点。同样,在前序遍历序列中,根节点后面的3个数字就是3个左子树节点的值,再后面的所有数字都是右子树节点的值,这样我们就在前序遍历和中序遍历中分别找到了左、右子树对应的子序列。

182352237859627.jpg

既然我们已经分别找到了左、右子树的前序遍历序列和中序遍历序列,我们可以用同样的方法分别构建左、右子树。

思路总结:

先根据前序遍历序列的第一个数字创建根结点,接下来在中序遍历序列中找到根结点的位置,这样就能确定左、右子树结点的数量。在前序遍历和中序遍历的序列中划分了左、右子树结点的值之后,就可以递归地去分别构建它的左右子树。

递归实现(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;
}

参考

数据结构:关于重建二叉树的三种思路
二叉树怎样序列化才能重建

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容