[LeetCode]根据树的遍历还原树(1)

来源:互联网 发布:微博个性域名是什么 编辑:程序博客网 时间:2024/06/06 23:43

[LeetCode]根据树的遍历还原树

0.题目

leetcode : 106. Construct Binary Tree from Inorder and Postorder Traversal

1.题目描述

Given inorder and postorder traversal of a tree, construct the binary tree.

Note:
You may assume that duplicates do not exist in the tree.

2.题目分析

题目的意思很清楚,给定一棵树的中序和后序遍历,要我们还原这棵树。
要想解题,首先我们要了解在二叉树中的三种基本遍历方式:先序遍历,中序遍历,后序遍历(按先左后右)
1. 先序遍历 : 在访问二叉树的时候,先访问根节点,然后依次访问左右子树
图一
遍历数组:1,2,4,5,7,8,3,6
2. 中序遍历 : 先访问左子树,然后访问根节点,最后访问右子树
图二
遍历数组:4,2,7,5,8,1,3,6
3. 后序遍历 : 先访问左右子树,然后访问根节点
图三
遍历数组:4,7,8,5,2,6,3,1
(图片均来自wikipedia)

根据定义我们可以发现,三种遍历的不同之处在于对根节点的访问次序。所以对于三种遍历方式我们一定要熟悉,这是结题的关键。(具体的遍历代码可以根据链接得到,我就不再赘述了)。
那下面我们就会有一个问题了,为什么需要两个不同的遍历才能还原树呢?难道一个遍历不能还原吗?
假设我们给定一个先序遍历数组:3, 2, 1 那我们能肯定的就只有3是根节点,但是我们不知道2和1之间是父子关系还是兄弟关系,这就会导致二叉树无法还原。

        3                            3      /   \                         /        2     1                       2                                  /                                 1

假设我们再给定中序遍历数组:1,2,3:,那我们可以知道2和1是父子关系

                           3                          /                           2                       /                     1  

如果中序遍历数组是这个:2,1,3,那我们可以知道2和1一定是兄弟关系

                           3                                                      /   \                                                    2     1                       

而且,我们需要的两个遍历数组里面必须有一个是中序遍历数组,先序遍历数组和后序遍历数组并不能还原二叉树(尚未能证明,正在努力)!中序遍历的唯一一个作用就是得到左右子树(我会在后面解释)
现在我们先来分析一下先序遍历和后序遍历有什么特点。
对于一个后序遍历数组,我们可以写成[left_subtree_preorder,right_subtree_preorder,root],其中root是指根节点,left_subtree_preorder是对左子树进行后序遍历的结果,right_subtree_preorder是对右子树进行后序遍历的结果
就图一来说,我们可以写成[(1),(2,4,5,7,8),(3,6)]

对于中序遍历数组,我们可以写成[left_subtree_preorder,root,right_subtree_preorder]
就图二来说,我们可以写成[(4,2,7,5,8),(1),(3,6)]

看着这种表达方式你能发现什么?我们是不是可以根据后序遍历数组找到根节点,然后在中序遍历中将左子树和右子树独立出来?得到左子树和右子树的先序和中序数组之后,我们是不是可以递归的得到左右子树的构造呢?
这样我们就找到了一种解题方法,通过递归的构造左右子树我们们就能还原二叉树了。下面我们来看看如何通过代码实现还原二叉树。

3.代码实现

我们先看看完整的代码

TreeNode* buildTree(vector<int> inorder, vector<int> postorder) {    TreeNode* root =new TreeNode(*(postorder.end() - 1));    if (inorder.size() == 1)        return root;    int root_position = -1;    for (int i = 0; i < inorder.size(); i++)        if (inorder[i] == root->val)            root_position = i;    vector<int> left_inorder(inorder.begin(), inorder.begin() + root_position);    vector<int> left_postorder(postorder.begin(), postorder.begin() + root_position);    vector<int> right_inorder(inorder.begin() + root_position + 1, inorder.end());    vector<int> right_postorder(postorder.begin() + root_position, postorder.end() - 1);    if (left_inorder.size() != 0) {        TreeNode* left = buildTree(left_inorder, left_postorder);        root->left = left;    }    if (right_inorder.size() != 0) {        TreeNode* right = buildTree(right_inorder, right_postorder);        root->right = right;    }    return root;}

首先我们可以通过后序遍历得到根节点的值(后序遍历中根节点一定是在最后访问的,也就是一定在数组最后),所以我们先构造根节点。当我们的遍历数组只有一个元素时,证明这是一个叶子节点,我们可以直接返回

TreeNode* buildTree(vector<int> inorder, vector<int> postorder) {    TreeNode* root =new TreeNode(*(postorder.end() - 1));    if (inorder.size() == 1)        return root;}

下面我们需要将左右子树分离出来,分别得到左右子树的后序和中序遍历数组。我们需要找到根节点在中序遍历中的位置,然后根节点左边的序列是左子树的中序遍历,根节点右边的序列是右子树的中序遍历。还有一个很关键的点是我们只需要知道左右子树分别有多少个元素就能在后序遍历中找到左右子树的后序遍历,因为后序遍历数组满足[(left_subtree_preorder),(right_subtree_preorder),root].

    int root_position = -1;    for (int i = 0; i < inorder.size(); i++)        if (inorder[i] == root->val)            root_position = i;    vector<int> left_inorder(inorder.begin(), inorder.begin() + root_position);    vector<int> left_postorder(postorder.begin(), postorder.begin() + root_position);    vector<int> right_inorder(inorder.begin() + root_position + 1, inorder.end());    vector<int> right_postorder(postorder.begin() + root_position, postorder.end() - 1);

接下来就是递归的得到左右子树的构造。当遍历数组size为0的时候证明该根节点没有左或右子树

    if (left_inorder.size() != 0) {        TreeNode* left = buildTree(left_inorder, left_postorder);        root->left = left;    }    if (right_inorder.size() != 0) {        TreeNode* right = buildTree(right_inorder, right_postorder);        root->right = right;    }

4.结束

解题的关键在于了解三种遍历方式的特点。比如在先序遍历中,根节点一定是第一个数;在后序遍历中,根节点一定是最后一个数。然后我们可以通过根节点在中序遍历中的位置将左子树和右子树分离出来,递归调用函数就能解决二叉树的还原。

有任何问题欢迎在评论区留言。

阅读全文
0 0
原创粉丝点击