一、二叉搜索树定义
1. 标准定义
二叉搜索树(Binary Search Tree,简称BST )是在普通二叉树基础上增加有序约束的特殊二叉树,由 n(n≥0)个节点构成。空BST 无任何节点;非空BST所有节点关键字不允许重复,且必须遵守全局有序规则。

2. 核心性质
整棵树统一有序判定规则:任意节点,其左子树 内所有节点关键字 < 当前节点关键字 < 其右子树内所有节点关键字。
补充考点:对二叉搜索树进行中序 遍历,输出序列一定是升序 ;该特征是区分普通二叉树与BST最直观判定依据,考试高频出题点。
3. 专业基础名词:直接前驱、直接后继
直接前驱 指中序遍历序列里,当前节点的前一个节点,分三类查找情况:
- 情况 1:当前节点左子树不为空,前驱为左子树中数值最大的节点,持续向右遍历左子树末端即可
- 情况 2:当前节点左子树 为空,向上回溯父节点,若回溯节点关键字小于当前节点,继续向上回溯;若回溯节点关键字大于当前节点,该节点即为前驱
- 情况 3:持续回溯直至父节点为 NULL,代表该节点无直接前驱(序列第一个元素)
直接后继 指中序遍历序列里,当前节点的下一个节点,分三类查找情况:
- 情况 1:当前节点右子树不为空,后继为右子树中数值最小的节点,持续向左遍历右子树末端即可
- 情况 2:当前节点右子树 为空,向上回溯父节点,若回溯节点关键字小于当前节点,继续向上回溯;若回溯节点关键字大于当前节点,该节点即为后继
- 情况 3:持续回溯直至父节点为 NULL,代表该节点无直接后继(序列最后一个元素)
二、BST 存储结构
1. 存储设计思路
普通二叉树二叉链表仅存储左右孩子指针,无法向上回溯祖先节点,获取前驱、后继逻辑繁琐。本文采用三叉链表 存储结构,在原有左、右孩子指针基础上新增parent父节点指针,支持双向查找,大幅简化删除、前驱后继查询逻辑。
2. 结构体定义
分为两层结构体:BSTNode 为单个节点单元,BSTree封装根节点指针统一管理整棵树。
cpp
// 保持原样使用前置声明
typedef int ELEMTYPE; // 假设元素类型为int
struct BSTNode {
ELEMTYPE data;
BSTNode* leftchild;
BSTNode* rightchild;
BSTNode* parent;
};
struct BSTree {
BSTNode* root;
};
3. 三叉链表优缺点
优点:支持向上回溯父节点,快速获取前驱、后继;删除双分支节点逻辑简化,无需递归遍历查找父节点。
缺点:每个节点多存储一根父指针 ,内存开销更大;增删节点时需同步维护parent指针,操作不当极易出现野指针、指针指向错乱。
适配范围:频繁执行删除、查找前驱 / 后继的业务场景;仅做单纯查找、遍历场景推荐使用二叉链表节省内存。
三、底层工具函数
工具函数为业务操作提供底层支撑,仅由普通对外函数内部调用,不对外提供使用。
1. BuyNode 创建新节点
实现思路:动态分配节点内存,使用 C++ new 创建节点,构造函数自动初始化左右孩子、父指针全部置空,返回新节点地址。
代码实现
cpp
// 申请新节点
BSTNode* BuyNode()
{
BSTNode* pnewnode = new BSTNode;
return pnewnode;
}
2. Get_PreNode 获取当前节点直接前驱
实现思路: 断言校验节点非空;分两大分支处理,左子树 存在取左子树最大值;左子树 不存在则向上回溯父节点匹配前驱规则。
代码实现
cpp
BSTNode* Get_PreNode(BSTNode* node) {
assert(node != nullptr);
BSTNode* p = node;
//当前节点左子树不为空,找左子树最大值
if (node->leftchild != nullptr) {
p = p->leftchild;
while (p->rightchild != nullptr)
p = p->rightchild;
}
//当前节点左子树为空,向上回溯父节点
else {
p = p->parent;
while (p != nullptr && p->data > node->data) {
p = p->parent;
}
//循环退出两种情况:找到前驱 / 回溯至nullptr无先驱
}
return p;
}
3. Get_NextNode 获取当前节点直接后继
实现思路: 断言校验节点非空;分两大分支处理,右子树 存在取右子树最小值;右子树 不存在则向上回溯父节点匹配后继规则。
代码实现
cpp
BSTNode* Get_NextNode(BSTNode* node) {
assert(node != nullptr);
BSTNode* p = node;
//当前节点右子树不为空,找右子树最小值
if (node->rightchild != nullptr) {
p = p->rightchild;
while (p->leftchild != nullptr)
p = p->leftchild;
}
//当前节点右子树为空,向上回溯父节点
else {
p = p->parent;
while (p!= nullptr&&p->data<node->data) {
p = p->parent;
}
//循环退出两种情况:找到后继 / 回溯至nullptr无后继
}
return p;
}
四、BST 五大基础操作
1. Init_BST 初始化二叉搜索树
实现思路: 断言校验树结构体指针合法;将根指针置空,代表空树。
代码实现
cpp
BSTNode* Init_BST(BSTree* pTree) {
assert(pTree != nullptr);
pTree->root = nullptr;
return pTree->root;
}
2. Insert_BST 插入节点
实现思路
- 断言校验树指针,单独处理空树场景,直接新建节点作为根节点
- 双指针遍历:p 遍历查找插入位置,pp 记录 p 的父节点
- 遍历过程匹配有序规则:待插入值小于当前节点往左走,大于则往右走
- 若循环找到等值节点,BST不允许重复值,直接返回
- 创建新节点,根据大小关系挂载至父节点左 / 右分支,绑定父子指针
代码实现
cpp
bool Insert_BST(BSTree* pTree, ELEMTYPE val) {
assert(pTree != nullptr);
//空树直接创建根节点
if (pTree->root == nullptr) {
BSTNode* pnewnode = BuyNode();
pnewnode->data = val;
pTree->root = pnewnode;
pnewnode->parent = nullptr;
return true;
}
//双指针遍历寻找插入位置
BSTNode* p=pTree->root;
BSTNode* pp=nullptr;
while (p!=nullptr&&p->data!=val) {
pp = p;
if (p->data > val)
p = p->leftchild;
else
p = p->rightchild;
}
//存在重复节点,插入失败
if (p != nullptr && p->data == val)
return false;
BSTNode* pnewnode = BuyNode();
pnewnode->data = val;
//挂载到父节点对应分支,绑定父指针
if (pnewnode->data>pp->data)
pp->rightchild = pnewnode;
else
pp->leftchild = pnewnode;
pnewnode->parent = pp;
return true;
}
3. Search_BST 循环查找节点
实现思路
- 断言校验根节点非空,指针 p 从根节点开始遍历
- 循环判定:当前指针不为空且关键字不匹配,按大小规则向左 / 右子树查找
- 循环退出两种分支:p 为 nullptr 代表查找失败;p 不为 nullptr 代表找到目标节点
代码实现
cpp
BSTNode* Search_BST(BSTNode* root,ELEMTYPE val) {
assert(root!=nullptr);
BSTNode* p = root;
while (p!=nullptr&&p->data!=val) {
if (val < p->data) {
p = p->leftchild;
}
else {
p = p->rightchild;
}
}
return p;
}
4. Delete_BST 删除节点
(1)三类删除场景对比
- 零孩子(叶子节点):直接断开父指针,释放当前节点,逻辑最简单
- 单孩子(仅左 / 右子树):唯一子节点直接顶替当前节点位置,更新父指针
- 双孩子(左右子树均存在):无法直接替换子树,采用狸猫换太子方案,转换为前两种场景
(2)狸猫换太子核心原理
直接前驱、直接后继节点的固有特性:二者最多只存在单侧子树,不存在同时拥有左右两个孩子的情况。将后继节点数据覆盖到待删节点,删除逻辑转移至后继节点,规避复杂双分支替换操作,大幅降低代码复杂度。
补充考点:前驱、后继均可替换,二者效果完全一致,工程中常用后继实现。
5. Show_InOrder_BST 中序遍历打印
实现思路: 利用栈模拟递归流程,BST 中序遍历输出严格升序 ;tag 标记控制左子树 持续入栈逻辑,弹出节点打印后处理右子树。
代码实现
cpp
void Show_InOrder_BST(BSTNode* root) {
assert(root != nullptr);
std::stack<BSTNode*> st;
st.push(root);
bool tag = true;
while (!st.empty()) {
while (tag&&st.top()->leftchild != nullptr) {
st.push(st.top()->leftchild);
}
BSTNode* tmp = st.top();
st.pop();
printf("%d ", tmp->data);
if (tmp->rightchild != nullptr) {
st.push(tmp->rightchild);
tag = true;
}
else {
tag = false;
}
}
}
五、BST 删除逻辑专项解析
1. Delete_BST0:仅处理 0 个子节点
实现思路: 先查找目标节点,存在且无左右孩子;断开父节点对应分支指针,释放目标节点内存。
代码实现
cpp
bool Delete_BST0(BSTree* pTree, ELEMTYPE val) {
assert(pTree != nullptr);
BSTNode* p = Search_BST(pTree->root, val);
if (p == nullptr)
return false;
//仅处理叶子节点
if (p->leftchild != nullptr || p->rightchild != nullptr)
return false;
BSTNode* father = p->parent;
if (father == nullptr)
pTree->root = nullptr;
else {
if (p->data<father->data)
father->leftchild = nullptr;
else
father->rightchild = nullptr;
}
delete p;
p = nullptr;
return true;
}
2. Delete_BST1:处理 0/1 个子节点
实现思路: 查找目标节点,判定至多只有单侧子节点;取出唯一子节点,直接顶替待删节点位置,同步修改父指针后释放内存。
代码实现
cpp
bool Delete_BST1(BSTree* pTree, ELEMTYPE val) {
assert(pTree != nullptr);
BSTNode* p = Search_BST(pTree->root, val);
if (p == nullptr)
return false;
//存在两个子节点不处理
if(p->leftchild != nullptr && p->rightchild != nullptr)
return false;
BSTNode* father = p->parent;
//取出唯一存在的子节点
BSTNode* child = p->leftchild != nullptr ? p->leftchild : p->rightchild;
if (father == nullptr)
pTree->root = child;
else {
if (p->data < father->data) {
father->leftchild = child;
}
else {
father->rightchild = child;
}
}
delete p;
p = nullptr;
return true;
}
3. Delete_BST:整合全场景删除
实现思路
- 查找待删除节点,无节点直接返回
- 场景 1:零 / 单分支直接复用单分支删除逻辑
- 场景 2:存在左右双孩子,执行**狸猫换太子,**获取当前节点直接后继,用后继关键字覆盖待删节点数据
- 将删除目标转移至后继节点,后继最多仅单分支,复用单分支删除逻辑完成释放
代码实现
cpp
bool Delete_BST(BSTree* pTree, ELEMTYPE val) {
assert(pTree != nullptr);
BSTNode* p = Search_BST(pTree->root, val);
if (p == nullptr)
return false;
//零分支直接释放
if (p->leftchild == nullptr && p->rightchild == nullptr)
{
Delete_BST0(pTree, val);
return true;
}
//双分支节点:狸猫换太子
if (p->leftchild != nullptr && p->rightchild != nullptr) {
BSTNode* cat = Get_NextNode(p);
p->data = cat->data;
p = cat;
}
//统一处理单分支/转换后的单分支节点
BSTNode* father = p->parent;
BSTNode* child = p->leftchild != nullptr ? p->leftchild : p->rightchild;
if (father == nullptr)
pTree->root = child;
else {
if (p->data < father->data) {
father->leftchild = child;
}
else {
father->rightchild = child;
}
}
delete p;
p = nullptr;
return true;
}
六、总结
1. 结构本质区别
普通二叉树无数值约束 ,仅限制节点度≤2;BST 强制全局有序 、数值唯一,左小右大,是带排序规则的特殊二叉树。
2. 遍历特性区别
普通二叉树所有遍历无序 ;BST 独有中序升序 特性,可快速完成有序输出,先 / 后序遍历不具备有序效果。
3. 性能与工程选型
理想平衡BST 增删查为 O(logn) ,斜树退化 O(n) ; 三叉链表适配高频删改、取前驱后继场景,普通刷题、简单遍历优先用二叉链表,减少指针维护 bug。
| 对比维度 | 普通二叉树 | 二叉搜索树(BST) |
| 节点规则 | 仅限制度≤2,无数值大小约束 | 左小右大 、数值不重复、全局有序 |
| 中序遍历 | 序列无序 | 严格升序(标志性特征) |
| 查找效率 | O(n) | 平衡 O (logn)、斜树 O (n) |
| 核心用途 | 层级存储、遍历 | 排序、检索、动态增删 |
|---|