什么是二叉搜索树?
二叉搜索树(Binary Search Tree,BST) 是一种特殊的二叉树,它满足以下性质:
-
若左子树不空,则左子树上所有节点的值 < 根节点的值
-
若右子树不空,则右子树上所有节点的值 > 根节点的值
-
左右子树也分别是二叉搜索树
注意:通常不允许有重复值(或规定重复值放左/右均可,但需统一)。
8
/ \
3 10
/ \ \
1 6 14
/ \ /
4 7 13
节点定义
cpp
struct TreeNode {
int key;
TreeNode* left;
TreeNode* right;
TreeNode(int k) : key(k), left(nullptr), right(nullptr) {}
};
-
每个节点存储一个整型键值
key。 -
left、right分别指向左、右孩子,初始化为nullptr。
二叉搜索树类声明
cpp
class BST {
private:
TreeNode* root;
// 递归辅助函数声明
TreeNode* insertRec(TreeNode* node, int key);
TreeNode* deleteRec(TreeNode* node, int key);
TreeNode* findMin(TreeNode* node);
bool searchRec(TreeNode* node, int key);
void inorderRec(TreeNode* node, std::vector<int>& res);
void destroyRec(TreeNode* node);
public:
BST() : root(nullptr) {}
~BST() { destroyRec(root); } // 释放所有节点
void insert(int key) { root = insertRec(root, key); }
void remove(int key) { root = deleteRec(root, key); }
bool search(int key) { return searchRec(root, key); }
std::vector<int> inorder();
};
-
root是整棵树的入口。 -
公开接口封装了对递归私有函数的调用,用户无需关心根节点指针传递。
-
析构函数负责递归销毁所有节点,防止内存泄漏。
插入(Insert)
cpp
TreeNode* BST::insertRec(TreeNode* node, int key) {
if (node == nullptr)
return new TreeNode(key); // 找到空位,创建新节点
if (key < node->key)
node->left = insertRec(node->left, key);
else if (key > node->key)
node->right = insertRec(node->right, key);
// 若相等,不插入(也可选择更新值)
return node; // 返回当前子树根节点,维持链接
}
-
递归向下寻找插入位置。
-
遇
nullptr时创建节点并返回,由上层node->left或node->right接收。 -
返回
node使整条路径上的链接保持不变(若未创建新节点,原样返回)。 -
时间复杂度:平均 O(log n),退化为链表时 O(n)。

查找(Search)
cpp
bool BST::searchRec(TreeNode* node, int key) {
if (node == nullptr)
return false;
if (key == node->key)
return true;
else if (key < node->key)
return searchRec(node->left, key);
else
return searchRec(node->right, key);
}
删除(Delete)
cpp
TreeNode* BST::deleteRec(TreeNode* node, int key) {
if (node == nullptr)
return nullptr;
// 1. 查找待删除节点
if (key < node->key) {
node->left = deleteRec(node->left, key);
} else if (key > node->key) {
node->right = deleteRec(node->right, key);
} else {
// 2. 已找到,执行删除
// 情况1/2:只有一个孩子或无孩子
if (node->left == nullptr) {
TreeNode* temp = node->right;
delete node;
return temp; // 用右孩子替换
} else if (node->right == nullptr) {
TreeNode* temp = node->left;
delete node;
return temp; // 用左孩子替换
}
// 情况3:有两个孩子 -> 用右子树最小节点(后继)替换
TreeNode* successor = findMin(node->right);
node->key = successor->key; // 复制后继的值
node->right = deleteRec(node->right, successor->key); // 删除后继
}
return node;
}
TreeNode* BST::findMin(TreeNode* node) {
while (node->left != nullptr)
node = node->left;
return node;
}
-
查找路径 :通过递归在树中寻找
key,到达要删除的节点。 -
单子/无子:
-
若
node->left == nullptr,无论右孩子是否存在,直接返回右孩子指针,并delete node。该返回被上层链接接收,相当于用右子树取代当前节点。 -
若只有左孩子同理,返回左孩子。
-
这些情况覆盖了叶子节点(左右都空,返回
nullptr)。
-
-
双子节点:
-
调用
findMin找到右子树中最小节点(后继),它一定没有左孩子。 -
把后继的
key拷贝到当前节点,相当于"替换"当前节点的数据。 -
然后递归从右子树中删除那个后继节点(此时后继节点的删除必为情况1或2,简单)。
-
-
返回
node保证了路径上指针链接的正确更新。
注意:这里显式
delete释放了被删节点的内存。在寻找后继并拷贝值后,实际上删除的是原后继节点(那个节点被deleteRec中的delete释放)。
核心操作与复杂度
| 操作 | 平均时间复杂度 | 最坏时间复杂度(退化成链表) |
|---|---|---|
| 查找 | O(log n) | O(n) |
| 插入 | O(log n) | O(n) |
| 删除 | O(log n) | O(n) |
为什么最坏是 O(n)?
- 当插入的数据本身有序时,树会退化成一根斜线(完全偏左或偏右),此时与链表无异。
例如依次插入 1, 2, 3, 4, 5:
1
\
2
\
3
\
4
\
5
2)经过某些插入删除操作,二叉树可能会失去平衡

中序遍历(返回有序结果)
cpp
void BST::inorderRec(TreeNode* node, std::vector<int>& res) {
if (node == nullptr) return;
inorderRec(node->left, res);
res.push_back(node->key);
inorderRec(node->right, res);
}
std::vector<int> BST::inorder() {
std::vector<int> result;
inorderRec(root, result);
return result;
}
内存释放(析构辅助函数)
cpp
void BST::destroyRec(TreeNode* node) {
if (node == nullptr) return;
destroyRec(node->left);
destroyRec(node->right);
delete node;
}
如何避免树会退化成一根斜线→ 平衡二叉搜索树
-
AVL树:每个节点维护高度差,插入/删除后通过旋转恢复平衡。
-
红黑树 :通过颜色和旋转维持近似平衡,C++ STL 的
std::map、std::set均基于红黑树。 -
两者均保证查找、插入、删除的 最坏时间复杂度为 O(log n)。
