二叉搜索树(BST)详解

60 阅读4分钟

二叉搜索树(BST)详解

一、二叉搜索树的定义

根据笔记中的描述,二叉搜索树(又称排序二叉树)是一种具有严格排序特性的二叉树,其定义满足以下两种情况:

  1. 空树:即没有任何节点的树。

  2. 非空树:由根节点、左子树、右子树组成,且同时满足:

    • 左子树和右子树都是二叉搜索树;
    • 左子树中所有节点的值 小于 根节点的值;
    • 右子树中所有节点的值 大于 根节点的值。

这种特性使得二叉搜索树的节点天然有序(左子树 < 根节点 < 右子树),为高效的查找、插入、删除操作奠定了基础。

二、二叉搜索树的核心操作(结合代码实现)

二叉搜索树的核心操作包括查找、插入、删除,以下结合提供的代码详细说明:

1. 查找操作

功能:查找数据域为特定值的节点。原理:利用 BST 的排序特性,通过与根节点比较,逐步缩小查找范围:

  • 若目标值等于当前节点值,找到目标;
  • 若目标值小于当前节点值,递归查找左子树;
  • 若目标值大于当前节点值,递归查找右子树。

代码实现

function search(root, n) {
    if (!root) {  // 树为空或未找到目标,直接返回
        return;
    }
    if (root.val === n) {  // 找到目标节点
        console.log('目标节点', root);
    } else if (root.val > n) {  // 目标在左子树
        search(root.left, n);
    } else {  // 目标在右子树
        search(root.right, n);
    }
}

示例:在如下 BST 中查找值为 7 的节点:

      6
    /   \
   3     8
  / \   / \
 1   4 7   9
  • 从根节点 6 开始,7 > 6 → 查找右子树(根为 8);
  • 7 < 8 → 查找 8 的左子树,找到节点 7。

image.png

插入操作

功能:插入新节点并保持 BST 的排序特性。原理:类似查找逻辑,找到合适的位置(叶子节点的左 / 右子树)插入新节点:

  • 若当前树为空,直接以新值创建根节点;
  • 若新值小于当前节点值,递归向左子树插入;
  • 若新值大于当前节点值,递归向右子树插入。

代码实现

function insertIntoBst(root, n) {
    if (!root) {  // 树为空,直接创建新节点作为根
        root = new TreeNode(n);
        return root;
    }
    if (root.val > n) {  // 新值小,插入左子树
        root.left = insertIntoBst(root.left, n);
    } else {  // 新值大,插入右子树
        root.right = insertIntoBst(root.right, n);
    }
    return root;  // 返回更新后的树
}

示例:向上述 BST 中插入值为 5 的节点:

  • 5 < 6 → 左子树(根为 3);
  • 5 > 3 → 右子树(根为 4);
  • 5 > 4 → 插入 4 的右子树,最终 4 的右节点为 5。
3. 删除操作

功能:删除指定节点并保持 BST 的排序特性。原理:删除节点后需重新调整树结构,分三种情况:

  1. 叶子节点(无左右子树) :直接删除(置为 null);
  2. 有左子树:用左子树中最大值节点(左子树最右侧节点)替换当前节点,再删除该最大值节点;
  3. 有右子树(无左子树) :用右子树中最小值节点(右子树最左侧节点)替换当前节点,再删除该最小值节点。

代码实现

function deleteNode(root, n) {
    if (!root) return root;  // 树为空,直接返回

    if (root.val === n) {  // 找到目标节点
        // 情况1:叶子节点
        if (!root.left && !root.right) {
            root = null;
        } 
        // 情况2:有左子树(用左子树最大值替换)
        else if (root.left) {
            const maxLeft = findMax(root.left);  // 找左子树最大值
            root.val = maxLeft.val;  // 替换值
            root.left = deleteNode(root.left, maxLeft.val);  // 删除最大值节点
        } 
        // 情况3:有右子树(用右子树最小值替换)
        else {
            const minRight = findMin(root.right);  // 找右子树最小值
            root.val = minRight.val;  // 替换值
            root.right = deleteNode(root.right, minRight.val);  // 删除最小值节点
        }
    } 
    // 目标在左子树
    else if (root.val > n) {
        root.left = deleteNode(root.left, n);
    } 
    // 目标在右子树
    else {
        root.right = deleteNode(root.right, n);
    }
    return root;
}

// 辅助函数:找左子树最大值(最右侧节点)
function findMax(root) {
    while (root.right) root = root.right;
    return root;
}

// 辅助函数:找右子树最小值(最左侧节点)
function findMin(root) {
    while (root.left) root = root.left;
    return root;
}

示例:删除上述 BST 中值为 8 的节点:

  • 8 有左右子树(7 和 9),且有右子树,因此用右子树最小值(9)替换 8;
  • 替换后删除原 9 节点,最终 8 的位置变为 9,右子树为空。
三、时间复杂度
  • 平均情况:查找、插入、删除的时间复杂度均为 O(log n) (类似二分查找,每次操作可排除一半节点)。
  • 最坏情况:当树退化为斜树(所有节点只有左子树或右子树)时,时间复杂度为 O(n) (类似链表遍历)。
四、使用场景

二叉搜索树适合需要频繁进行查找、插入、删除操作,且对效率有一定要求的场景,例如:

  1. 数据库索引:利用 BST 的高效查找特性,快速定位数据(实际数据库中常用 B 树 / B + 树,是 BST 的变种,更适合磁盘存储)。
  2. 有序数据维护:如排行榜、字典等,可利用 BST 的有序性快速获取最大 / 最小值(如findMax/findMin函数)。
  3. 平衡树基础:是 AVL 树、红黑树等平衡二叉树的基础(平衡树通过旋转解决 BST 的斜树问题,保证 O (log n) 的稳定效率)。