目录
[BST 定义与核心特性](#BST 定义与核心特性)
[4、BST 的缺陷与优化](#4、BST 的缺陷与优化)
一、二叉搜索树简介
二叉搜索树即 Binary Search Tree(左右不要写反喔),是数据结构中最基础也最实用的树形结构之一,它以 "左子树值小于根、右子树值大于根" 的核心特性,实现了高效的增删查操作。是哈希表、红黑树等高级数据结构的基础,本文将全面讲解 BST 的核心功能、实现细节。
BST 定义与核心特性
二叉搜索树是一种满足以下性质的二叉树:
- 任意节点的左子树中,所有节点的键值(Key)均小于该节点的键值;
- 任意节点的右子树中,所有节点的键值均大于该节点的键值;
- 左、右子树本身也必须是二叉搜索树(递归定义);
节点结构体定义
cpp
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
- 模板参数
K:类模板,让节点支持任意可比较的类型- 结构体成员:
_left/_right指向子节点,_key为核心键值- 构造函数:使用
const K&传参,既避免拷贝,又保证不修改传入的键值
BST的核心操作
cpp
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{}
bool Insert(const K& key);
bool Find(const K& key);
bool Erase(const K& key);
void InOrder();
private:
void _InOrder(Node* root);
Node* _root;
};
讲解
typedef BSTreeNode<K> Node:类型别名,简化代码书写,省事儿- 根节点
_root私有化:防止外部直接修改根节点,只能通过类的成员函数操作- void _InOrder(Node* root)私有,防止外部传入错误根节点
二、BST核心功能实现
1、插入的实现
插入是 BST 最基础的操作,核心逻辑是先找到空位置,按大小关系挂个新节点,不允许重复键值。
cpp
bool BSTree<K>::Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
// 情况2:非空树 → 遍历找插入位置
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;
}
这是迭代版本的插入的实现,最关键的一点就是保留父子关系,不断更改parent的位置,进而迭代遍历
- 空树处理:空树则直接创建根节点
- 遍历找位置 :
parent记录父节点,cur负责遍历,两者配合找到空位置后挂新节点- 利用 BST "左小右大" 的特性,不断缩小查找范围,直到
cur为空- 挂新节点 :根据
parent->_key和新key的大小关系,决定挂左还是挂右- BST 的键值必须唯一,遇到重复直接返回
false。- 迭代插入的优势:无函数调用开销,性能虽然略高递归版,但是无栈溢出风险,递归深度太高了会溢出。
2、查找的实现
查找是 BST 的核心优势,利用 "左小右大" 的特性,可以快速缩小查找范围
cpp
bool BSTree<K>::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;
}
比较简单,用cur遍历树,key大的话就去右子树继续找,小的话就去左子树找。
时间复杂度:理想状态O(logn),最坏状态O(n),显而易见嘛,BST 的查找效率远高于数组,这是树形结构的核心优势
3、中序遍历
cpp
void BSTree<K>::InOrder()
{
_InOrder(_root);
cout << endl;
}
void BSTree<K>::_InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}

这是递归的流程图
- 中序遍历顺序:左 、根、 右,这是 BST 升序的核心原因
- 递归终止,
root == nullptr(空树 / 叶子节点的孩子)- 对外提供
InOrder()接口,隐藏递归函数_InOrder()
三、核心功能删除(重点)
删除是 BST 最复杂的操作,核心原则是 "删除节点后,仍保持 BST 的性质"。这是最阴的,需分三种情况处理
cpp
bool BSTree<K>::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 (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
}
else
{
Node* replaceParent = cur; // 重置parent,专门找替代节点
Node* leftMax = cur->_left;
while (leftMax->_right)
{
replaceParent = leftMax;
leftMax = leftMax->_right;
}
// 交换cur和leftMax的key
swap(cur->_key, leftMax->_key);
if (replaceParent->_left == leftMax)
{
replaceParent->_left = leftMax->_left;
}
else
{
replaceParent->_right = leftMax->_left;
}
cur = leftMax;
}
delete cur;
return true;
}
}
return false;
}
这里提供了迭代版的删除实现
情况 1(左空)或 情况 2(右空)
- 直接让父节点跳过当前节点,指向当前节点的非空孩子
- 特殊处理根节点:若删除的是根节点,需将
_root指向非空孩子- 情况 3(左右都非空):
- 不能直接删,直接删除会导致 cur 的左右子树 "失联",破坏 BST 结构
- 替代方案:找 "左子树的最大值" 或 "右子树的最小值"(这两个节点必为叶子 / 单孩子节点,符合情况 1/2)
- 操作逻辑:交换 cur 和替代节点的 key → 删除替代节点(此时替代节点是易删除的情况)
- 删除节点后必须调用
delete cur,否则会导致内存泄漏

我们用这棵树举例子
待删节点左孩子为空(例:删除 10)
找到待删节点cur=10,parent=8
判断cur左孩子为空 → 检查cur不是根节点
parent(8)的右孩子是cur(10) → 把parent->_right指向cur->_right(14)
待删节点左右孩子都存在(例:删除 3)找到待删节点cur=3,parent=8(cur左右孩子都存在,左=1,右=6)
步骤2:找cur左子树的最右节点: replaceParent初始=3,leftMax初始=1 → leftMax->_right=nullptr,所以替代节点是1
步骤3:交换cur(3)和leftMax(1)的key → cur->_key=1,leftMax->_key=3
步骤4:删除替代节点leftMax(1): replaceParent(3)的左孩子是leftMax(1) → 把replaceParent->_left置为leftMax->_left(nullptr)
步骤5:删除leftMax(1)

四、递归实现
迭代版本非常的冗长,考试时可以使用递归版本进行解决
1、代码实现
cpp
#pragma once
#include <iostream>
#include <string>
using namespace std;
namespace BSTree_Enhanced
{
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree() :_root(nullptr) {}
// 深拷贝(避免浅拷贝问题)
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
// 赋值运算符重载
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
Destroy(_root);
}
bool InsertR(const K& key) { return _InsertR(_root, key); }
bool FindR(const K& key) { return _FindR(_root, key); }
bool EraseR(const K& key) { return _EraseR(_root, key); }
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
Node* Copy(Node* root)
{
if (root == nullptr) return nullptr;
Node* newNode = new Node(root->_key);
newNode->_left = Copy(root->_left);
newNode->_right = Copy(root->_right);
return newNode;
}
// (后序遍历:左→右→根)
void Destroy(Node*& root)
{
if (root == nullptr) return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key); // 引用传参,直接修改原指针
return true;
}
if (root->_key < key)
{
return _InsertR(root->_right, key);
}
else if (root->_key > key)
{
return _InsertR(root->_left, key);
}
else
{
return false; // 重复key
}
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr) return false;
if (root->_key < key) return _FindR(root->_right, key);
else if (root->_key > key) return _FindR(root->_left, key);
else return true;
}
// 递归删除核心(最关键)
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr) return false;
if (root->_key < key) return _EraseR(root->_right, key);
else if (root->_key > key) return _EraseR(root->_left, key);
else
{
Node* del = root;
// 左空 → 指向右孩子
if (root->_left == nullptr)
{
root = root->_right;
}
// 右空 → 指向左孩子
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* leftMax = root->_left;
while (leftMax->_right) leftMax = leftMax->_right;
swap(root->_key, leftMax->_key);
return _EraseR(root->_left, key);
}
delete del;
return true;
}
}
// 中序遍历递归实现
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root;
};
void TestBSTree_Enhanced1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BSTree<int> t;
for (auto e : a) t.InsertR(e);
cout << "递归插入后中序遍历:";
t.InOrder();
BSTree<int> t1(t);
cout << "拷贝构造后t1中序遍历:";
t1.InOrder(); // 与t一致
t.EraseR(3);
cout << "递归删除3后中序遍历:";
t.InOrder();
}
}
2、运行结果

3、核心重点
1、深拷贝(Copy):
浅拷贝问题:直接赋值
_root = t._root会导致两个对象共用同一棵树,析构时重复释放内存(程序崩溃)深拷贝逻辑:递归创建新节点,复制每一个节点的 key,形成独立的树
前序遍历保证 、先有根,再挂孩子
递归插入 / 删除:引用传参是核心:无需记录父节点,直接修改原指针(如
root = root->_right),等价于迭代版中 "父节点指向孩子" 的操作;递归删除更简洁:找替代节点后,递归删除左子树的最大值,将复杂问题分解为子问题。
4、BST 的缺陷与优化
基础 BST 的最大问题是 易退化,比如插入有序序列会退化为链表,导致效率下降
- AVL 树:严格平衡二叉树,任意节点的左右子树高度差≤1,通过旋转(左旋、右旋、左右旋、右左旋)保持平衡
- 红黑树:近似平衡二叉树,通过 "红黑规则"(节点红 / 黑、根黑、红节点子节点黑等)保持平衡,旋转和变色操作更少,性能优于 AVL 树
- 后续博客我们继续研究
以上就是本篇博客的全部内容了,欢迎大家在评论区交流讨论