388,先序遍历构造二叉树

想了解更多数据结构以及算法题,可以关注微信公众号“数据结构和算法”,每天一题为你精彩解答。也可以扫描下面的二维码关注
在这里插入图片描述

返回与给定先序遍历相匹配的二叉搜索树的根结点。


示例:

输入:[8,5,1,7,10,12]

输出:[8,5,10,1,7,null,12]

在这里插入图片描述


问题分析

我们知道先序遍历的顺序是:根节点→左子树→右子树二叉搜索树的特点是当前节点左子树的值都小于当前节点,当前节点右子树的值都大于当前节点。比如我们在下面的搜索二叉树中插入节点4
在这里插入图片描述
在这里插入图片描述

原理很简单,我们来看下如果插入一个结点的时候代码该怎么写

//data是插入的结点
private static void addTreeNode(TreeNode root, int data) {
    TreeNode node = new TreeNode(data);
    TreeNode p = root;
    while (true) {
        //如果要插入的结点data比结点p的值小,就往p结点的左
        //子节点找,否则往p的右子节点找
        if (p.val > data) {
            //如果p的左子节点等于空,直接放进去
            if (p.left == null) {
                p.left = node;
                break;
            } else {
                p = p.left;
            }
        } else {
            //如果p的右子节点等于空,直接放进去
            if (p.right == null) {
                p.right = node;
                break;
            } else {
                p = p.right;
            }
        }
    }
}

上面代码很简单,插入一个结点的代码写出来了,我们只需要把数组中的元素全部遍历一遍然后再一个个插入即可,代码如下

public TreeNode bstFromPreorder(int[] preorder) {
    TreeNode root = new TreeNode();
    root.val = preorder[0];
    for (int i = 1; i < preorder.length; i++)
        addTreeNode(root, preorder[i]);
    return root;
}

递归方式

上面节点插入的时候我们使用的是while循环的方式,这种比较容易理解,但代码量相对比较多,我们还可以改为递归的方式

private TreeNode addTreeNode(TreeNode root, int val) {
    if (root == null)
        return new TreeNode(val);
    else if (root.val > val)
        root.left = addTreeNode(root.left, val);
    else
        root.right = addTreeNode(root.right, val);
    return root;
}

这种递归的方式代码会更简洁一些。如果root为空的话会新建一个节点。否则会一直走下去,他会根据root节点的大小判断往左走还是往右走,注意这里的root节点不一定是根节点,在递归的时候他是一直变的。


二分法构造

我们知道输入的数据是二叉树的先序遍历,那么第一个节点肯定是头结点,比他小的是他左子树的节点值,比他大的是他右子树的节点值,我们就拿上面的[8,5,1,7,10,12]来说,8是根节点,比8小的[5,1,7]是他左子树上的值,比他大的[10,12]是他右子树上的值。所以可以参照二分法查找的方式,把数组分为两部分,他是这样的
在这里插入图片描述

然后左边的[5,1,7]我们再按照上面的方式拆分,5是根节点,比5小的1是左子节点,比5大的7是右子节点。同理右边的[10,12]中10是根节点,比10大的12是右子节点,这样我们一直拆分下去,直到不能拆分为止,所以结果是下面这样
在这里插入图片描述

我们再来看下代码

public TreeNode bstFromPreorder(int[] preorder) {
    return buildBST(preorder, 0, preorder.length - 1);
}

//数组的范围从left到right
private TreeNode buildBST(int[] preorder, int left, int right) {
    if (left > right)
        return null;
    TreeNode root = new TreeNode(preorder[left]);
    //如果left==right说明只有一个元素,没法再拆分了
    if (left == right)
        return root;
    int i = left;
    //拆分为两部分,一部分是比preorder[left]大的,一部分是比preorder[left]小的
    while (i + 1 <= right && preorder[i + 1] < preorder[left])
        i++;
    //区间[left + 1,i]所有元素都在root节点的左子树
    //区间[i + 1,right]所有元素都在root节点的右子树
    root.left = buildBST(preorder, left + 1, i);
    root.right = buildBST(preorder, i + 1, right);
    return root;
}

先序遍历

我们还可以参照先序遍历的方式把数组元素一个个取出来,也很好理解,直接上代码。

int index = 0;

public TreeNode bstFromPreorder(int[] preorder) {
    return bstFromPreorder(preorder, Integer.MAX_VALUE);
}

public TreeNode bstFromPreorder(int[] preorder, int max) {
    if (index == preorder.length || preorder[index] > max)
        return null;
    //把数组中的元素一个个取出来创建节点
    TreeNode root = new TreeNode(preorder[index++]);
    //左子树的最大值不能超过root.val
    root.left = bstFromPreorder(preorder, root.val);
    //右子树的最大值不能超过max
    root.right = bstFromPreorder(preorder, max);
    return root;
}

使用栈来解决

这题解法比较多,再来看最后一种解题思路。我们还可以使用一个栈来维护二叉搜索树中的节点,栈中存放的是已经构建好的二叉搜索树的结点(但不是全部,有些可能已经出栈了),其中栈中元素从栈底到栈顶是递减的,我们遍历数组的时候如果当前值小于栈顶元素的值,我们直接让当前值成为栈顶元素节点的左子节点,然后压栈。

        if (preorder[i] < stack.peek().val) {
               stack.peek().left = node;
        } 

如果当前元素的值大于栈顶元素的值,我们就让栈顶元素出栈,直到当前元素的值小于栈顶元素的值为止(或者栈为空为止)。而前一个比当前元素值小的节点就是当前元素的父节点。而当前元素是他父节点的右子节点。

            TreeNode parent = stack.peek();
            //栈从栈底到栈顶是递减的
            while (!stack.isEmpty() && preorder[i] > stack.peek().val) {
                   parent = stack.pop();
            }
            parent.right = node;

解惑:

这里如果思路不是很清晰的可能会有点疑问,出栈的时候把小于当前元素的值出栈了,如果再遇到比出栈的元素还要小的值那不是完蛋了,因为那个值已经出栈了,找不到了。其实有这个想法是正确的,但这种想法有点多余了,我们就拿下面的图来说吧[8,5,1,7,10,12]
在这里插入图片描述

比如当我们插入节点7的时候,节点1,5都已经全部出栈,但7后面无论如何都不会再出现比1或者5还小的值了,因为他是二叉搜索树,5的右节点的所有值都是比5大的。我们来画个简单的图看下
在这里插入图片描述
在这里插入图片描述

所以我们看到后面无论走到哪一步都不可能在遇到比出栈元素更小的值了,最后我们再来看下完整代码

public TreeNode bstFromPreorder(int[] preorder) {
    Stack<TreeNode> stack = new Stack<>();
    TreeNode root = new TreeNode(preorder[0]);
    stack.push(root);
    for (int i = 1; i < preorder.length; i++) {
        TreeNode node = new TreeNode(preorder[i]);
        //小于栈顶元素的值,说明应该在栈顶元素的左子树
        if (preorder[i] < stack.peek().val) {
            stack.peek().left = node;
        } else {//大于栈顶元素的值,我们要找到当前元素的父节点
            TreeNode parent = stack.peek();
            //栈从栈底到栈顶是递减的
            while (!stack.isEmpty() && preorder[i] > stack.peek().val) {
                parent = stack.pop();
            }
            parent.right = node;
        }
        //节点压栈
        stack.push(node);
    }
    return root;
}

在这里插入图片描述

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页