一文搞懂二叉搜索树(BST)

194 阅读3分钟

BST是什么

⼆叉搜索树(Binary Search Tree,简称 BST)是⼀种很常见的的⼆叉树。 每个节点都有两个分支,一个左节点和一个右节点。而且,所有左子树的节点都比父节点小,所有右子树的节点都比父节点大。所以,如果你要在这里找一个数字,你只需要从根节点开始逐个比较就可以了。

BST注意事项

首先,要求数据必须是可比较的,也就是说,你要能够比较两个数据的大小。 其次,有些时候可能会退化成一条链,比如输入的数据是按照从小到大排列的,那么我就只有右子树,而左子树都是空的。

另外还有一些小技巧,比如说,如果你要删除一个节点,可以把它的后继节点(也就是比它大的最小节点)代替它,这样可以避免破坏整个树的结构。

代码实践

判断合法二叉树

思路

要判断一棵树是不是合法的BST,需要满足以下条件:

  1. 对于树中的每个节点,其左子树中的所有节点都小于该节点,右子树中的所有节点都大于该节点。
  2. 对于树中的每个节点,其左子树和右子树都满足上述条件。

为了实现这些条件,可以使用递归的方式来遍历树。假设当前遍历到的节点是node,我们需要检查node的值是否大于其左子树中的所有节点,小于其右子树中的所有节点,并且左子树和右子树都是合法的BST。如果不满足任何一个条件,则说明该树不是合法的BST。

在BST中,对于每个节点,其左子树中的所有节点都小于该节点,右子树中的所有节点都大于该节点。因此,我们需要知道每个节点可以取的最小和最大值,以便在递归中进行比较和判断。 对于任何一个节点,它的最小值为它的左子树的最大值,而最大值为它的右子树的最小值。因此,在递归遍历树的过程中,我们需要不断更新每个节点的最小值和最大值,以确保树的所有节点都满足BST的条件。

代码

/**
 * 判断BST是否合法
 * @param root
 * @return
 */
public static boolean isValidBST(TreeNode root) {
     return isValid(root, null, null);
}

private static boolean isValid(TreeNode root, TreeNode min, TreeNode max) {
    if (root == null){
        return true;
    }
    if (null != min && root.val <= min.val || null != max && root.val >= max.val) {
        return false;
    }

    return isValid(root.left, min, root) && isValid(root.right, root, max);
}

查找BST的某个节点是否存在

思路

graph TD
根节点是否为空 --> 为空返回false
根节点是否为空 --> 不为空 --> 根节点的值等于需要查找的值返回true
不为空 --> 查找的值大于根节点的值 --> 递归地查找右子树
不为空 --> 查找的值小于根节点的值 --> 递归地查找左子树

代码

/**
 * 判断target是否存在于root
 * @param target
 * @param root
 * @return
 */
public static boolean exist(int target, TreeNode root) {
    if (null == root) {
        return false;
    }

    if (target == root.val) {
        return true;
    } else if (target > root.val) {
        return exist(target, root.right);
    } else {
        return exist(target, root.left);
    }


}

插入

思路

graph TD
根节点是否为空 --> 为空 --> 将新节点插入到根节点的位置上并返回新节点
根节点是否为空 --> 不为空
不为空 --> 新节点的值小于根节点的值 --> 递归调用插入方法将新节点插入到左子树中
不为空 --> 新节点的值大于根节点的值 --> 递归调用插入方法将新节点插入到右子树中

代码

/**
 * 插入
 * @param newNode
 * @param root
 * @return
 */
public static TreeNode insert(TreeNode newNode, TreeNode root) {
    if (root == null) {
        return newNode;
    }

    if (newNode.val < root.val) {
        root.left = insert(newNode, root.left);
    } else {
        root.right =  insert(newNode, root.right);
    }

    return root;
}

删除

思路

  1. 如果根节点为空,则直接返回null。
  2. 如果要删除的节点的值等于根节点的值,则执行以下操作:
    • 如果根节点的左子树为空,则返回右子树作为新的根节点。
    • 如果根节点的右子树为空,则返回左子树作为新的根节点。
    • 否则,找到根节点右子树中最小的节点,将该节点的值赋给根节点,然后在右子树中递归删除该最小节点。
  3. 如果要删除的节点的值小于根节点的值,则在左子树中递归查找要删除的节点并进行删除。
  4. 如果要删除的节点的值大于根节点的值,则在右子树中递归查找要删除的节点并进行删除。
  5. 最后返回新的根节点。

代码

/**
 * 删除
 * @param deleteNode
 * @param root
 * @return
 */
public static TreeNode delete(TreeNode deleteNode, TreeNode root) {
    if (null == root) {
        return null;
    }

    if (deleteNode.val == root.val) {
        if (root.left == null) {
            return root.right;
        } else if (root.right == null) {
           return root.left;
        } else {
            TreeNode minNode = getMin(root.right);
            root.val = minNode.val;
            root.right = delete(root, root.right);
        }
    } else if (deleteNode.val < root.val) {
        root.left = delete(deleteNode, root.left);
    } else {
        root.right = delete(deleteNode, root.right);
    }

    return root;
}

private static TreeNode getMin(TreeNode root) {
    while (root.left != null) {
        root = root.left;
    }

    return root;
}