二叉搜索树(Binary Search Tree,简称 BST),也叫二叉排序树,是二叉树中最常用的类型之一,核心特点是「有序性」—— 基于节点值的大小关系组织树结构,让查找、插入、删除操作能高效执行。
一、核心定义
二叉搜索树是满足以下递归规则的二叉树:
- 任意节点的左子树中,所有节点的键值(数值)都 严格小于 该节点的键值;
- 任意节点的右子树中,所有节点的键值都 严格大于 该节点的键值;
- 左、右子树本身也必须是二叉搜索树;
- (可选)树中不允许重复键值(若需支持重复,可约定「左子树≤当前节点」或「右子树≥当前节点」,需提前明确规则)。
5 (根节点) / \ 3 7 (3<5,7>5) / \ / \ 2 4 6 8 (2<3、4>3;6<7、8>7)
二、核心特性(高频考点)
-
中序遍历升序:对 BST 做「左→根→右」的中序遍历,结果是严格升序的序列(无重复时)。示例中序遍历结果:
2 → 3 → 4 → 5 → 6 → 7 → 8(这是 BST 最核心的特性,面试必考)。 -
极值位置固定:
- 整棵树的最小值:最左侧的叶子节点(示例中是 2);
- 整棵树的最大值:最右侧的叶子节点(示例中是 8)。
-
无冗余节点:相同值的节点不会出现(默认规则),避免查找 / 插入歧义。
三、节点结构定义(C++ 示例)
先定义 BST 的节点结构,后续操作均基于此:
#include <iostream>
#include <vector>
using namespace std; // 二叉搜索树节点结构
struct TreeNode {
int val; // 节点值(键值)
TreeNode* left; // 左子节点指针
TreeNode* right; // 右子节点指针
// 构造函数
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
四、BST 核心操作(插入 / 查找 / 删除)
BST 的操作均基于「值的比较」递归 / 迭代实现,其中删除操作是难点,需分情况处理。
1. 查找操作(Search)
思路
从根节点开始,按「值的大小」逐层缩小查找范围:
- 若目标值 = 当前节点值:找到,返回该节点;
- 若目标值 < 当前节点值:递归 / 迭代查找左子树;
- 若目标值 > 当前节点值:递归 / 迭代查找右子树;
- 若子树为空:未找到,返回
nullptr。
// 查找值为target的节点,返回节点指针(未找到返回nullptr)
TreeNode* searchBST(TreeNode* root, int target) {
TreeNode* cur = root;
while (cur != nullptr) {
if (cur->val == target) {
return cur; // 找到目标节点 }
else if (target < cur->val) {
cur = cur->left; // 往左找 }
else {
cur = cur->right; // 往右找
}
}
return nullptr; // 未找到
}
复杂度
- 时间:O (h)(h 为树的高度);
- 空间:O (1)(迭代版)/ O (h)(递归版,栈空间)。
2. 插入操作(Insert)
思路
插入的核心是「找到空的插入位置」,且不破坏 BST 规则:
- 若树为空:直接将新节点作为根节点;
- 若树非空:从根节点开始比较,小则往左、大则往右,直到找到「空的左 / 右子节点位置」,插入新节点。
// 插入值为val的节点,返回插入后的树根(处理空树情况)
TreeNode* insertIntoBST(TreeNode* root, int val) {
// 空树:直接新建根节点
if (root == nullptr) {
return new TreeNode(val);
}
TreeNode* cur = root;
TreeNode* parent = nullptr; // 记录当前节点的父节点
// 找到插入位置的父节点
while (cur != nullptr) {
parent = cur;
if (val < cur->val) {
cur = cur->left;
} else if (val > cur->val) {
cur = cur->right;
} else {
// 已有相同值,按规则不插入(直接返回原树)
return root;
}
}
// 插入新节点(父节点的左/右)
if (val < parent->val) {
parent->left = new TreeNode(val);
} else {
parent->right = new TreeNode(val);
}
return root;
}
关键注意
插入操作不会修改已有节点的位置,仅在叶子节点的空位置添加新节点,因此不会破坏原有 BST 结构。
3. 删除操作(Delete)
删除是 BST 最复杂的操作,需根据「待删除节点的子节点数量」分 3 种情况处理:
| 情况 | 处理方式 |
|---|---|
| 情况 1:叶子节点(无左右子节点) | 直接删除,将父节点指向该节点的指针置空 |
| 情况 2:单孩子节点(仅左 / 右子节点) | 将父节点的指针指向该节点的子节点,再删除原节点 |
| 情况 3:双孩子节点(左右子节点都有) | 找「中序后继」(右子树的最小节点)或「中序前驱」(左子树的最大节点),替换待删除节点的值,再删除后继 / 前驱节点(后继 / 前驱必是情况 1 或 2) |
// 删除值为key的节点,返回删除后的树根
TreeNode* deleteNode(TreeNode* root, int key) {
// 步骤1:找到待删除节点(cur)及其父节点(parent)
TreeNode* cur = root;
TreeNode* parent = nullptr;
while (cur != nullptr && cur->val != key) {
parent = cur;
if (key < cur->val) {
cur = cur->left;
} else {
cur = cur->right;
}
}
if (cur == nullptr) {
return root; // 未找到待删除节点,直接返回
}
// 步骤2:分情况删除节点
// 情况1+2:无孩子 或 只有一个孩子
if (cur->left == nullptr || cur->right == nullptr) {
TreeNode* child = (cur->left != nullptr) ? cur->left : cur->right;
// 若待删除节点是根节点(无父节点)
if (parent == nullptr) {
root = child;
} else if (parent->left == cur) {
parent->left = child;
} else {
parent->right = child;
}
delete cur; // 释放内存
}
// 情况3:有两个孩子(找中序后继替换)
else {
// 找右子树的最小节点(中序后继)
TreeNode* successor = cur->right;
TreeNode* sucParent = cur; // 后继节点的父节点
while (successor->left != nullptr) {
sucParent = successor;
successor = successor->left;
}
// 替换待删除节点的值为后继节点的值
cur->val = successor->val;
// 删除后继节点(后继必是叶子/单孩子)
if (sucParent->left == successor) {
sucParent->left = successor->right;
} else {
sucParent->right = successor->right;
}
delete successor;
}
return root;
}
五、BST 的遍历(重点:中序遍历)
遍历是验证 BST 合法性、提取有序数据的核心手段,其中中序遍历是 BST 的「专属遍历方式」。
1. 中序遍历(左→根→右)
// 中序遍历BST,结果存入res(升序)
void inorderTraversal(TreeNode* root, vector<int>& res) {
if (root == nullptr) return;
inorderTraversal(root->left, res); // 左
res.push_back(root->val); // 根
inorderTraversal(root->right, res); // 右
}
// 调用示例
vector<int> res;
inorderTraversal(root, res);
// res 为升序数组:[2,3,4,5,6,7,8]
2. 验证 BST 合法性
利用「中序遍历升序」的特性,验证一棵二叉树是否为 BST:
bool isValidBST(TreeNode* root) {
vector<int> res;
inorderTraversal(root, res);
// 检查是否严格升序
for (int i = 1; i < res.size(); ++i) {
if (res[i] <= res[i-1]) {
return false;
}
}
return true;
}
六、性能分析
BST 操作的时间复杂度完全取决于树的高度 h:
| 树的形态 | 树高 h | 操作时间复杂度 | 示例场景 |
|---|---|---|---|
| 平衡 BST | log₂n | O(logn) | 随机插入节点 |
| 退化 BST(链表) | n | O(n) | 依次插入 1→2→3→4→5(右斜链) |
优化方向:平衡二叉树
为解决 BST 退化问题,衍生出「平衡二叉树」,强制树的高度保持 O (logn):
- AVL 树:严格平衡,任意节点的左右子树高度差≤1,插入 / 删除需旋转调整;
- 红黑树:近似平衡,通过颜色规则限制高度,插入 / 删除旋转次数更少(C++
set/map、JavaTreeMap底层实现)。
七、应用场景
- 动态有序集合:高效支持插入、删除、查找(替代数组,数组插入 / 删除需移动元素,时间 O (n));
- 范围查询:比如「找所有大于 3 且小于 7 的节点」,中序遍历到 3 开始收集,到 7 停止;
- 排序:中序遍历直接得到升序序列(时间 O (n),但空间复杂度高,不如快排 / 归并);
- 数据库索引:MySQL 索引的底层是 B+ 树(BST 的变种,更适合磁盘存储)。
八、核心总结
- BST 核心规则:左小、右大、子树也是 BST;
- 中序遍历升序是 BST 的「身份标识」,验证 / 解题必用;
- 插入 / 查找简单,删除需分 3 种情况(双孩子节点找后继 / 前驱是关键);
- 性能瓶颈在树高,平衡 BST(红黑树 / AVL)是工业级解决方案。