引言
二叉搜索树(Binary Search Tree,BST),也称为二叉排序树,是一种特殊的二叉树结构。它要么是一棵空树,要么满足以下性质:
-
若左子树非空,则左子树上所有结点的值小于等于根结点的值;
-
若右子树非空,则右子树上所有结点的值大于等于根结点的值;
-
左右子树本身也都是二叉搜索树。
二叉搜索树是后续学习平衡二叉搜索树(AVL树、红黑树)以及标准库中 set、map、multiset、multimap 等关联式容器底层实现的基础。本文将从 BST 的基本概念出发,系统讲解其插入、查找、删除操作的具体实现与代码细节,分析其性能优缺点,并深入阐述 key 搜索模型与 key/value 搜索模型的应用场景及完整代码实现。
目录
[六、二叉搜索树的代码实现(key 模型)](#六、二叉搜索树的代码实现(key 模型))
[七、二叉搜索树的 key/value 使用场景](#七、二叉搜索树的 key/value 使用场景)
[7.1 key 搜索场景](#7.1 key 搜索场景)
[7.2 key/value 搜索场景](#7.2 key/value 搜索场景)
[7.3 key/value 二叉搜索树代码实现](#7.3 key/value 二叉搜索树代码实现)
一、二叉搜索树的概念与性质
二叉搜索树的核心性质是有序性:中序遍历 BST 可以得到一个有序序列(升序)。正是这种性质,使得 BST 在查找数据时能够通过比较大小快速缩小搜索范围。
关于相等值的处理:
-
某些 BST 实现不支持插入相等值 (如
set/map),即所有结点的值唯一。 -
另一些实现支持插入相等值 (如
multiset/multimap),此时可以约定相等值统一向右(或向左)插入,保持逻辑一致性。
二、二叉搜索树的性能分析
在最优情况 下,BST 为完全二叉树或接近完全二叉树,高度为 log2Nlog2N,此时增删查改的时间复杂度为 O(logN)O(logN)。
在最差情况下,BST 退化为单支树(如插入已排序的数据),高度为 NN,时间复杂度退化为 O(N)O(N)。
与二分查找的对比:
-
二分查找也能达到 O(logN)O(logN) 的查找效率,但它要求数据存储在支持随机访问且有序的数组中。
-
二分查找的致命缺陷是插入和删除效率极低,因为需要挪动大量元素(平均 O(N)O(N))。
-
而 BST(尤其是平衡 BST)可以在 O(logN)O(logN) 时间内完成插入和删除,弥补了数组的不足。
由于原生 BST 在最坏情况下性能较差,后续将引入 AVL 树和红黑树等平衡二叉搜索树,以保证树的高度始终保持在 O(logN)O(logN)。
三、二叉搜索树的插入
插入过程:
-
若树为空,则直接创建新结点作为根结点。
-
若树非空,从根开始比较:
-
插入值大于当前结点值,向右走;
-
插入值小于当前结点值,向左走;
-
如果支持插入相等值,可以统一向右(或向左)走到空位置。
-
-
走到空位置后,创建新结点并链接到父结点的对应孩子指针。
注意:不支持相等值时,遇到相同 key 应插入失败(返回 false)。
示例 :向空树依次插入 {8, 3, 1, 10, 6, 4, 7, 14, 13} 后,得到的 BST 形状如课件图示。
四、二叉搜索树的查找
查找过程:
-
从根开始比较,若查找值等于当前结点值,则查找成功。
-
若查找值大于当前结点值,则向右子树继续查找;若小于,则向左子树继续查找。
-
若走到空结点仍未找到,则查找失败。
对于支持相等值的 BST(如 multiset),查找时通常要求返回中序遍历中第一个 等于 key 的结点。例如树中有多个 3,中序序列为 1, 3, 3, 4, ...,应返回第一个 3。
五、二叉搜索树的删除
删除是 BST 最复杂的操作。首先查找待删除结点 N,若不存在则返回 false;若存在,分四种情况处理:
| 情况 | 描述 | 处理方式 |
|---|---|---|
| 1 | N 左右孩子均为空 | 直接删除,父结点对应指针置空(可合并到情况2/3) |
| 2 | N 左空右非空 | 父结点指向 N 的右孩子,删除 N |
| 3 | N 右空左非空 | 父结点指向 N 的左孩子,删除 N |
| 4 | N 左右均非空 | 替换法:找 N 左子树的最大结点(最右)或右子树的最小结点(最左),交换两者的值,然后转而删除那个替代结点(替代结点必然属于情况2或3,可安全删除) |
关键细节:
-
删除根结点时,父结点为
nullptr,需单独更新_root。 -
替换法中,若右子树的最小结点就是
cur->right(即右子树的根没有左孩子),需特殊处理父指针的指向。
六、二叉搜索树的代码实现(key 模型)
以下是仅存储 key 的 BST 核心代码(不支持重复值):
cpp
template<class K>
struct BSTNode {
K _key;
BSTNode<K>* _left;
BSTNode<K>* _right;
BSTNode(const K& key) : _key(key), _left(nullptr), _right(nullptr) {}
};
template<class K>
class BSTree {
typedef BSTNode<K> Node;
public:
bool Insert(const K& key) {
if (_root == nullptr) {
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_key < key) {
parent = cur;
cur = cur->_right;
} else if (cur->_key > key) {
parent = cur;
cur = cur->_left;
} else {
return false; // 不允许重复
}
}
cur = new Node(key);
if (parent->_key < key) parent->_right = cur;
else parent->_left = cur;
return true;
}
bool Find(const K& key) {
Node* cur = _root;
while (cur) {
if (cur->_key < key) cur = cur->_right;
else if (cur->_key > key) cur = cur->_left;
else return true;
}
return false;
}
bool Erase(const K& key) {
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_key < key) {
parent = cur;
cur = cur->_right;
} else if (cur->_key > key) {
parent = cur;
cur = cur->_left;
} else {
// 处理左空或右空的情况
if (cur->_left == nullptr) {
if (parent == nullptr) _root = cur->_right;
else if (parent->_left == cur) parent->_left = cur->_right;
else parent->_right = cur->_right;
delete cur;
} else if (cur->_right == nullptr) {
if (parent == nullptr) _root = cur->_left;
else if (parent->_left == cur) parent->_left = cur->_left;
else parent->_right = cur->_left;
delete cur;
} else {
// 左右均非空:找右子树最小结点
Node* rightMinP = cur;
Node* rightMin = cur->_right;
while (rightMin->_left) {
rightMinP = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key; // 交换值
if (rightMinP->_left == rightMin) rightMinP->_left = rightMin->_right;
else rightMinP->_right = rightMin->_right;
delete rightMin;
}
return true;
}
}
return false;
}
void InOrder() { _InOrder(_root); cout << endl; }
private:
void _InOrder(Node* root) {
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
Node* _root = nullptr;
};
七、二叉搜索树的 key/value 使用场景
7.1 key 搜索场景
仅存储 key,用于判断某个值是否存在。典型应用:
-
车库门禁系统:已登记的车牌号作为 key,车辆进入时扫描车牌,若 key 存在则抬杆。
-
英文拼写检查:将词库中所有单词存入 BST,读取文章单词并查找是否存在,不存在则标红提示。
7.2 key/value 搜索场景
每个 key 关联一个 value,查找 key 的同时获取对应的 value。value 可以是任意类型。支持修改 value,但不允许修改 key(会破坏 BST 结构)。典型应用:
-
英汉词典:key 为英文单词,value 为中文释义。
-
商场停车场计费:入口扫描车牌(key)并记录入场时间(value);出口扫描车牌查找入场时间,计算费用。
-
统计单词出现次数 :遍历文档,若单词(key)不存在,则插入
(单词, 1);若存在,则将其 value(次数)加 1。
7.3 key/value 二叉搜索树代码实现
cpp
template<class K, class V>
struct BSTNode {
K _key;
V _value;
BSTNode<K, V>* _left;
BSTNode<K, V>* _right;
BSTNode(const K& key, const V& value) : _key(key), _value(value), _left(nullptr), _right(nullptr) {}
};
template<class K, class V>
class BSTree {
typedef BSTNode<K, V> Node;
public:
bool Insert(const K& key, const V& value) {
if (_root == nullptr) {
_root = new Node(key, value);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_key < key) {
parent = cur;
cur = cur->_right;
} else if (cur->_key > key) {
parent = cur;
cur = cur->_left;
} else {
return false; // key 已存在,不允许重复(可根据需求修改为更新 value)
}
}
cur = new Node(key, value);
if (parent->_key < key) parent->_right = cur;
else parent->_left = cur;
return true;
}
Node* Find(const K& key) {
Node* cur = _root;
while (cur) {
if (cur->_key < key) cur = cur->_right;
else if (cur->_key > key) cur = cur->_left;
else return cur;
}
return nullptr;
}
bool Erase(const K& key) {
// 实现与 key 模型相同,仅需注意交换时同时交换 _key 和 _value
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_key < key) { parent = cur; cur = cur->_right; }
else if (cur->_key > key) { parent = cur; cur = cur->_left; }
else {
if (cur->_left == nullptr) {
if (parent == nullptr) _root = cur->_right;
else if (parent->_left == cur) parent->_left = cur->_right;
else parent->_right = cur->_right;
delete cur;
} else if (cur->_right == nullptr) {
if (parent == nullptr) _root = cur->_left;
else if (parent->_left == cur) parent->_left = cur->_left;
else parent->_right = cur->_left;
delete cur;
} else {
Node* rightMinP = cur;
Node* rightMin = cur->_right;
while (rightMin->_left) {
rightMinP = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
cur->_value = rightMin->_value; // 同时替换 value
if (rightMinP->_left == rightMin) rightMinP->_left = rightMin->_right;
else rightMinP->_right = rightMin->_right;
delete rightMin;
}
return true;
}
}
return false;
}
void InOrder() { _InOrder(_root); cout << endl; }
// 拷贝构造、赋值重载、析构等(参见课件完整代码)
private:
void _InOrder(Node* root) {
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
void Destroy(Node* root) {
if (root == nullptr) return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
Node* Copy(Node* root) {
if (root == nullptr) return nullptr;
Node* newRoot = new Node(root->_key, root->_value);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
Node* _root = nullptr;
};
使用示例:统计水果出现次数
cpp
int main() {
string arr[] = {"苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉"};
BSTree<string, int> countTree;
for (const auto& str : arr) {
auto ret = countTree.Find(str);
if (ret == nullptr) countTree.Insert(str, 1);
else ret->_value++;
}
countTree.InOrder();
return 0;
}
总结
二叉搜索树是一种基于二分思想的数据结构,其核心操作(插入、查找、删除)在平均情况下复杂度为 O(logN)O(logN),但最坏情况会退化到 O(N)O(N)。本文详细阐述了 BST 的定义、性能瓶颈、插入查找删除的完整流程及代码实现,并区分了 key 模型与 key/value 模型的实际应用场景。
掌握 BST 是理解更高级平衡树(AVL、红黑树)的基础,也是掌握 C++ 标准库关联容器底层原理的关键一步。在实际工程中,若数据量较大且操作频繁,应优先使用平衡二叉搜索树或哈希表,但对于学习数据结构的内在逻辑和递归/迭代操作,BST 依然是不可替代的经典范例。