【 算法-10 /Lesson85(2025-12-25)】二叉搜索树(BST)详解:定义、操作与实现🌳

7 阅读5分钟

🌳在计算机科学中,二叉搜索树(Binary Search Tree,简称 BST)是一种非常重要的数据结构。它不仅结构清晰、逻辑优美,而且在实际应用中支持高效的查找、插入和删除操作。本文将从基本定义出发,深入讲解其核心性质、常见操作的原理,并结合代码示例详细说明其实现方式。


🔍 什么是二叉搜索树?

二叉搜索树(也称为排序二叉树)是一种特殊的二叉树,具有如下递归定义:

  • 空树是一棵二叉搜索树;

  • 非空树由一个根节点、一棵左子树和一棵右子树组成,且满足:

    • 左子树中的所有节点的值严格小于根节点的值;
    • 右子树中的所有节点的值严格大于根节点的值;
    • 左子树和右子树本身也必须是二叉搜索树。

⚠️ 注意:通常不允许重复值。若需支持重复,可约定“左 ≤ 根 < 右”或使用计数字段等方式处理。

这种结构性质使得 BST 具备天然的有序性,从而支持高效的查找操作。


⏱️ 时间复杂度分析

由于 BST 的结构依赖于插入顺序,其性能表现分为两种情况:

💡 为避免最坏情况,可使用自平衡二叉搜索树(如 AVL 树、红黑树),但本文聚焦于基础 BST。


🔧 核心操作详解

BST 支持三大基本操作:查找插入删除。下面逐一展开。


🔎 查找(Search)

查找目标值 n 的过程如下:

  1. 从根节点开始;
  2. 若当前节点为空,说明未找到,返回;
  3. 若当前节点值等于 n,找到目标;
  4. n 小于当前节点值,递归在左子树查找;
  5. n 大于当前节点值,递归在右子树查找。

该过程充分利用了 BST 的有序性,每次比较都能排除一半的子树。

✅ 代码实现(来自 1.js

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);
  }
}

此外,1.js 中还定义了 TreeNode 类,并构建了一个示例树:

class TreeNode {
  constructor(val) {
    this.val = val;
    this.left = null;
    this.right = null;
  }
}

const root = new TreeNode(6);
root.left = new TreeNode(3);
root.right = new TreeNode(8);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(4);
root.right.left = new TreeNode(7);
root.right.right = new TreeNode(9);

search(root, 7); // 输出:目标节点 {val: 7, left: null, right: null}

该树结构如下:

        6
       / \
      3   8
     / \ / \
    1  4 7 9

➕ 插入(Insert)

插入新值 n 的规则:

  1. 若树为空,创建新节点作为根;

  2. 否则,从根开始比较:

    • n < root.val,递归插入左子树;
    • n > root.val,递归插入右子树;
  3. 直到找到一个空位置(null),在此处创建新节点。

插入操作不会破坏 BST 的性质,因为始终将新节点放在“正确”的叶子位置。

✅ 代码实现(来自 2.js

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;
}

📌 注意:此函数返回更新后的子树根节点,便于递归回溯时重新连接父节点指针。


❌ 删除(Delete)

删除操作是 BST 中最复杂的部分,需分三种情况处理:

情况一:待删节点为叶子节点(无左右子树)

  • 直接删除,将其父节点对应指针设为 null

情况二:待删节点只有一个子树

  • 用其唯一子节点“顶替”该节点位置。

情况三:待删节点有两个子树

  • 不能简单删除,否则会破坏 BST 结构。
  • 解决方案:用中序前驱(左子树最大值)或中序后继(右子树最小值)替代该节点的值,然后递归删除那个替代节点(它必定最多只有一个子树)。

✅ 常见策略:

  • 使用左子树的最大值(即最右节点);
  • 或使用右子树的最小值(即最左节点)。

✅ 代码实现(来自 3.js

function deleteNode(root, n) {
  if (!root) {
    return root;
  }

  if (root.val === n) {
    // 情况一:叶子节点
    if (!root.left && !root.right) {
      root = null;
    }
    // 情况三(优先用左子树最大值)或情况二(只有左子树)
    else if (root.left) {
      const maxLeft = findMax(root.left); // 找左子树最大值
      root.val = maxLeft.val;             // 替换值
      root.left = deleteNode(root.left, maxLeft.val); // 递归删除原最大值节点
    }
    // 情况二(只有右子树)或情况三(当无左子树时用右子树最小值)
    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 的一个重要特性是:中序遍历(左 → 根 → 右)的结果是一个严格递增的序列

例如,对上述示例树进行中序遍历:

1 → 3 → 4 → 6 → 7 → 8 → 9

这正是 BST 被称为“排序二叉树”的原因。该性质可用于:

  • 验证一棵树是否为 BST;
  • 获取第 k 小元素;
  • 实现范围查询等。

🛠️ 实际应用场景

BST 广泛应用于:

  • 数据库索引(早期实现);
  • 内存中的有序字典(如 C++ 的 std::set/map 基于红黑树);
  • 动态集合操作(频繁插入/删除/查找);
  • 表达式解析树(结合运算符优先级)。

尽管现代系统多采用自平衡树,但理解基础 BST 是掌握高级数据结构的前提。


📌 总结

操作原理简述时间复杂度(平均/最坏)
查找递归比较,向左或向右O(log n) / O(n)
插入找到合适叶子位置插入O(log n) / O(n)
删除分三类处理,用前驱/后继替代O(log n) / O(n)

二叉搜索树以其简洁的结构和高效的平均性能,成为算法与数据结构学习中的基石。掌握其原理与实现,不仅能应对面试题,更为理解更复杂的平衡树打下坚实基础。

🌟 记住:BST 的美,在于“有序”;其弱点,在于“不平衡”。而计算机科学的魅力,正在于不断用更聪明的结构去弥补这些弱点!