目录

前言:
二叉搜索树(Binary Search Tree,简称 BST)是数据结构领域的 “基石”,它不仅是面试高频考点,更是很多工业级组件(如 C++ std::map/std::set、数据库索引)的底层基础。本文将严格遵循你的大纲,从概念、性能、核心操作、完整实现到key/value 场景实战,用 C++ 代码带你彻底掌握 BST 的原理与工程应用。
一、二叉搜索树的概念
1.1定义
二叉搜索树是一棵空树,或满足以下递归性质的二叉树:
- 任意节点的左子树所有节点值 < 当前节点值;
- 任意节点的右子树所有节点值 > 当前节点值;
- 左、右子树本身也必须是二叉搜索树。
注意:二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值。
1.2核心特性
- 中序遍历升序:对 BST 进行中序遍历(左→根→右),得到的序列是严格递增的。
- 动态结构:节点可动态增删,无需预先分配空间,兼顾链表的灵活性与数组的查找效率。

二、二叉搜索树的性能分析
最优情况下,二叉搜索树为(接近)完全二叉树,树的左右子树高度差较小,操作效率接近二分查找,其高度为:log2 N
最差情况下,二叉搜索树为(接近)单支树,退化为单链表,其高度为:N
BST 的性能完全由树的高度(h)决定,以下是核心操作的时间复杂度:


注意:
二分查找也可以实现O(log2 N) 级别的查找效率,但是二分查找有两大缺陷:
1. 需要存储在支持下标随机访问的结构中,并且有序。
2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数 据。
三、二叉搜索树的插入
我们可以先定义二叉搜索树需要的框架和节点,代码如下:
namespace key
{
template<class K>
//Binary Search Tree
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:
private:
Node* _root = nullptr;
};
}
1. 核心逻辑
插入的目标是找到符合 BST 规则的空位置,并保持树的有序性,步骤如下:
- 若树为空:新建节点作为根节点。
- 若树非空:从根节点出发,比较当前节点值与待插入值:
- 待插入值 < 当前节点值 → 递归 / 迭代进入左子树;
- 待插入值 > 当前节点值 → 递归 / 迭代进入右子树;
- 遇到空节点时,在此处插入新节点。
2. C++ 代码实现
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;
}
四、二叉搜索树的查找
1. 核心逻辑
从根节点开始,根据值的大小关系导航:
- 目标值 < 当前节点值 → 去左子树查找;
- 目标值 > 当前节点值 → 去右子树查找;
- 目标值 = 当前节点值 → 找到目标;
- 导航到空节点 → 目标不存在。
2. 扩展:查找最值
- 最小值:一直向左子树遍历,直到左子节点为空,当前节点即为最小值。
- 最大值:一直向右子树遍历,直到右子节点为空,当前节点即为最大值。
3. C++ 代码实现
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;
}
五、二叉搜索树的删除
删除是 BST 最复杂的操作,需分四种情况处理,核心原则是 “删除后仍保持 BST 性质”:
1. 要删除结点N左右孩子均为空
2. 要删除的结点N左孩子位空,右孩子结点不为空
3. 要删除的结点N右孩子位空,左孩子结点不为空
4. 要删除的结点N左右孩子结点均不为空
对应以上四种情况的解决方案: 1. 把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是一样的)
2. 把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点
3. 把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点
4. 无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值最大结点 R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意一个,放到N的位置,都满足二叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结 点,R结点符合情况2或情况3,可以直接删除。
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
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 (cur == _root)
{
_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 (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
//都不为空
else
{
//找右子树的最左
Node* replaceparent = cur;
Node* replace = cur->_right;
while (replace->_left)
{
replaceparent = replace;
replace = replace->_left;
}
cur->_key = replace->_key;
if (replaceparent->_left == replace)
replaceparent->_left = replace->_right;
else
replaceparent->_right = replace->_right;
delete replace;
}
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);
}
验证代码:
#include <iostream>
using namespace std;
#include"BinarySearch.h"
int main()
{
key::BSTree<int> t;
int a[] = { 8, 3, 1, 10, 1, 6, 4, 7, 14, 13 };
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
t.Insert(16);
t.Insert(3);
t.InOrder();
t.Erase(3);
t.InOrder();
t.Erase(8);
t.InOrder();
for (auto e : a)
{
t.Erase(e);
t.InOrder();
}
return 0;
}
七、二叉搜索树 key 和 key/value 使用场景
在工程实践中,BST 常以两种形式出现:仅存 key 的搜索树(如std::set)和存 key/value 键值对的映射树(如std::map)。
7.1 key 搜索场景
适用场景
- 数据去重:存储唯一值,快速判断元素是否存在(如用户 ID、订单号)。
- 有序遍历:需要按升序 / 降序处理数据(如排行榜、时间序列)。
- 范围查询:查找某一区间内的所有元素(如 “查找年龄在 20-30 之间的用户”)。
7.2 key/value 搜索场景
适用场景
- 键值映射:通过唯一 key 快速查找对应的 value(如字典、配置项)。
- 动态更新:支持插入、删除、修改键值对(如缓存系统、用户信息存储)。
- 有序映射:需要按 key 的顺序遍历键值对(如按字母顺序遍历用户昵称)。
777

被折叠的 条评论
为什么被折叠?



