跳到主要内容
C++ 二叉搜索树设计与实现详解 | 极客日志
C++ 算法
C++ 二叉搜索树设计与实现详解 二叉搜索树是基础数据结构,左子节点值小于根节点,右子节点值大于根节点。本文详解 C++ 中二叉搜索树的实现,涵盖节点与树类设计、构造析构、拷贝赋值、插入查找删除及中序遍历。对比了迭代与递归两种实现方式,重点解析删除操作的三种情况及替换逻辑。最后分析时间复杂度与性能瓶颈,为学习 AVL 树和红黑树奠定基础。
独立开发者 发布于 2026/3/27 更新于 2026/4/23 2 浏览二叉搜索树
1. 二叉搜索树的定义
二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树 :
若它的左子树不为空,则左子树上所有节点的值都小于其根节点的值
若它的右子树不为空,则右子树上所有节点的值大于根节点的值
它的左右子树也分别为二叉搜索树
二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义。
后续学习的 map/set/multimap/multiset 系列容器底层就是二叉搜索树,其中 map/set 不支持插入相等值,multimap/multiset 支持插入相等值
2. 整体架构设计
结点设计
template <class K >
struct BSTreeNode {
BSTreeNode (const K& key) : _left(nullptr ), _right(nullptr ), _key(key) {}
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
};
二叉搜索树常用于存储键值,方便查找关键字 。因此我们的模板参数使用 K 来表示 ,结点内存放的数据为 _key
结点中的成员变量 :
BSTreeNode* _left:指向左孩子的指针
BSTreeNode* _right:指向右孩子的指针
K _key:存储相应的的键值
默认构造函数 :
将三个指针初始化为 nullptr
初始化数据 _key 为传入的值 key
结点采用 struct 设计,默认权限为 public,方便下文的 BSTree 类访问成员
树结构设计
template <typename K>
class BSTree {
typedef BSTreeNode<K> Node;
public :
private :
Node* _root;
};
BSTreeNode<K>
左右指针
_key
BSTree<K> 以 _root 指向树根。类内 typedef BSTreeNode<K> Node; 简化后续代码书写。
设计上采用裸指针管理节点(非智能指针),因此必须显式实现拷贝与析构,避免内存泄露 。
3. 相关操作实现
1. 构造与析构
构造 public :
BSTree () : _root(nullptr ) {}
BSTree():默认构造,构造一颗空树 ,只需将根节点指针 _root 置空即可
析构 public :
~BSTree () { Destroy (_root); }
private :
void Destroy (Node*& root) {
if (root == nullptr ) return ;
Destroy (root->_left);
Destroy (root->_right);
delete root;
root = nullptr ;
}
由于析构函数无参数无返回值 ,而我们释放节点需要操作 _root 结点及其子节点管理的内存资源,因此无法通过传统传参递归的方式释放节点 。
我们设计了一个子函数,子函数完成结点的清理释放工作,析构函数直接调用子函数即可 。
子函数 void Destroy(Node*& root):后序遍历删除实现
必须先释放左右子节点再释放父节点 ,因此我们采用后序遍历删除,依次递归释放左子树,右子树,最后释放根节点 ,这里和普通二叉树的释放结点操作相同
Node*& root 引用形式,函数栈帧中的形参 root 成为了每个节点的地址的别名 。Node*& root 引用形式允许在删除左右子树后把父指针设为 nullptr,避免悬空指针。
析构 ~BSTree():调用 Destroy(_root) 做后序销毁,释放所有节点。
2. 拷贝构造与赋值
拷贝构造 public :
BSTree (const BSTree<K>& tree) : _root(nullptr ) { _root = Copy (tree._root); }
private :
Node* Copy (Node* root) {
if (root == nullptr ) return nullptr ;
else {
Node* newRoot = new Node (root->_key);
newRoot->_left = Copy (root->_left);
newRoot->_right = Copy (root->_right);
return newRoot;
}
}
这里和析构函数类似,利用子函数实现树的拷贝 。
原因是:拷贝构造函数的参数必须为 const BSTree& 类型 ,而我们拷贝树需要依次拷贝每个节点,递归拷贝节点时需要传入根节点作为参数 。
拷贝构造 BSTree(const BSTree<K>&):先把 _root 初始化为 nullptr,再调用私有 Copy 做深拷贝,_root 接收返回的新树的根。这样可以保证两个树互不干扰。
Copy() 函数的逻辑,整体逻辑采用递归实现 :
遇到非空结点,对该结点进行拷贝,新结点的指针保存在函数栈帧 中
再分别递归拷贝左子树、右子树
遇到 nullptr 空结点时,向上级栈帧中返回结点的指针,完成结点的链接关系
operator=赋值
public :
BSTree<K>& operator =(BSTree<K> tmp) {
std::swap (_root, tmp._root);
return *this ;
}
赋值运算符 operator=(BSTree<K> tmp) 重载:采用**拷贝 - 并 - 交换(copy-and-swap)**技巧:t2 = t1
形参 tmp 为值传递,会调用拷贝构造生成一个局部副本 (或借助移动语义),该副本 tmp 也为一个 BSTree 对象,和 t1 的内容完全一致,且有各自独立的数据空间。
std::swap(_root, tmp._root) 交换资源,更改管理资源的指针的指向,这样 tmp 的析构会自动释放旧资源。
这种写法简洁且有异常安全性(strong exception guarantee)。
以上写法可以类比之前 vector 中 operator= 的实现,只需要把 _start 指针换成这里的 _root 指针即可
3. 插入
迭代版插入
bool insert (const K& key) {
if (_root == nullptr ) {
_root = new Node (key);
return true ;
}
Node* parent = nullptr ;
Node* curNode = _root;
while (curNode != nullptr ) {
if (key > curNode->_key) {
parent = curNode;
curNode = curNode->_right;
} else if (key < curNode->_key) {
parent = curNode;
curNode = curNode->_left;
} else {
return false ;
}
}
curNode = new Node (key);
if (key > parent->_key)
parent->_right = curNode;
else
parent->_left = curNode;
return true ;
}
空树特判 :若 _root == nullptr,直接把根设为新节点(new Node(key))。
否则从 _root 向下查找插入位置:
使用 curNode 跟随,parent 保存其父节点(因为当 curNode 为 nullptr 时需要把新节点挂到 parent)。
如果 key > curNode->_key,curNode 沿右子树移动;key < curNode->_key 时,curNode 沿左子树移动。
如果 key == curNode->_key,返回 false(二叉搜索树默认不允许重复键 )。
当 curNode 走到 nullptr(找到空位)后,代表 curNode 已找到合适的可以插入的位置。
new Node(key) 建节点
要插入新结点,必须修改 curNode 的父节点内的左右孩子指针 ,但父节点并不知道要插入的 key 比自己大还是自己小,只知道下面有位置可以插入 ,不知道插入到哪个位置
因此要根据 key 与 parent->_key 的比较把它接为左/右子节点。
如果 key > parent->_key → 插到右边 (parent->_right = newNode)
如果 key < parent->_key → 插到左边 (parent->_left = newNode)
总结 :✔️ 循环结束时,位置已经找到了,就是 curNode == nullptr 的地方。
✔️ 但是插入操作不能直接修改 curNode,必须通过 parent 去改指针。
✔️ 而 parent 自己并不知道空位是在左边还是右边,所以需要再比较一次 来决定。
curNode 用来遍历,parent 跟随;这是常见的迭代插入模式。
新节点分配后必须从父节点 parent 链入整棵树
不允许重复键
递归插入 public :
bool insert_R (const K& key) {
return _insert_R(_root, key);
}
private :
bool _insert_R(Node*& root, const K& key) {
if (root == nullptr ) {
root = new Node (key);
return true ;
}
if (key < root->_key)
return _insert_R(root->_left, key);
else if (key > root->_key)
return _insert_R(root->_right, key);
else
return false ;
}
_insert_R 的参数是 Node*& root(对指针的引用),这使得当 root 为 nullptr 时可以直接写 root = new Node(key); 来修改父节点的指针,而不需要再记录父节点再通过父节点的比较完成结点的插入 ,简化了父指针的管理。
按照二叉搜索树的规则,递归插入 :
key < root->_key 时,return _insert_R(root->_left, key) 递归去左子树中插入
key > root->_key 时,return _insert_R(root->_right, key) 递归去右子树中插入
== 时返回 false,键不允许重复
时间复杂度 :
平均为 O(h),其中 h 为树的高度;
若树退化为链表(完全不平衡),退化到 O(n)。
空间复杂度 :
(递归版)额外为递归栈深度 O(h),其中 h 为树的高度;
4. 查找
迭代查找: bool find (const K& key) const {
Node* curNode = _root;
while (curNode) {
if (key == curNode->_key)
return true ;
else if (key > curNode->_key)
curNode = curNode->_right;
else
curNode = curNode->_left;
}
return false ;
}
标准 BST 查找:从根出发比较,curNode = _root,curNode从根节点出发开始找 ,按照二叉搜索树的性质查找 即可
curNode 表示当前结点,curNode 的移动结合了二叉搜树的性质:
key == curNode->_key 时,成功,返回 true
key > curNode->_key 时,curNode 去右子树查找
key < curNode->_key 时,curNode 去左子树查找
循环结束时,若循环内没有返回,代表没有找到,则循环外返回 false
递归查找: public :
bool find_R (const K& key) const {
return _find_R(_root, key);
}
private :
bool _find_R(const Node* root, const K& key) const {
if (root == nullptr )
return false ;
if (key < root->_key)
return _find_R(root->_left, key);
else if (key > root->_key)
return _find_R(root->_right, key);
else
return true ;
}
递归版本自然表达了 BST 的定义:
key > 当前节点的_key 时,递归去右子树查找
key < 当前节点的_key 时,递归去左子树查找
不大于,不小于时,表明查找成功,返回 true
参数 const Node* 保证不会修改树。
5. 中序遍历 由二叉搜索树的性质可得,对二叉搜索树进行中序遍历,可以得到升序序列
中序遍历可以用于升序打印 BST 中的键 。代码如下:
public :
void Inorder () const {
_Inorder(_root);
printf ("\n" );
}
private :
void _Inorder(Node* root = _root) const {
if (root == nullptr ) {
return ;
}
_Inorder(root->_left);
cout << root->_key << " " ;
_Inorder(root->_right);
}
利用子函数实现中序遍历即可
_Inorder 是典型的递归中序:左 - 根 - 右。
对 BST 做中序遍历会得到有序(从小到大)序列,这也是 BST 在排序/输出上的常见用途。
注意:_Inorder 写成 void _Inorder(Node* root = _root) const,默认参数指向成员 _root,调用 Inorder() 默认从根节点开始遍历
6. 删除
迭代删除 bool erase (const K& key) {
Node* parent = nullptr ;
Node* curNode = _root;
while (curNode) {
if (key > curNode->_key) {
parent = curNode;
curNode = curNode->_right;
} else if (key < curNode->_key) {
parent = curNode;
curNode = curNode->_left;
}
else {
}
}
return false ;
}
情况一:左子树为空
else {
if (curNode->_left == nullptr ) {
if (curNode == _root)
_root = curNode->_right;
else if (curNode == parent->_left)
parent->_left = curNode->_right;
else
parent->_right = curNode->_right;
delete curNode;
return true ;
}
return true ;
}
左子树为空(curNode->_left == nullptr) :
如果该节点是根:_root = curNode->_right;
否则根据 curNode 是父节点的左/右孩子,把父指针指向结点 curNode->_right(托孤),再 delete curNode。
这包含了叶子节点(左右都空)和只有右孩子的情况(因为左空且右可能为空或非空) 。
情况二:右子树为空
else {
else if (curNode->_right == nullptr ) {
if (curNode == _root)
_root = curNode->_left;
else if (curNode == parent->_left)
parent->_left = curNode->_left;
else
parent->_right = curNode->_left;
delete curNode;
return true ;
}
return true ;
}
右子树为空(curNode->_right == nullptr) :
与左子树为空时对称 :
如果该节点是根:_root = curNode->_left;
否则根据 curNode 是父节点的左/右孩子,把父指针指向结点 curNode->_left(托孤),再 delete curNode。
这包含了叶子节点(左右都空)和只有右孩子的情况(因为左空且右可能为空或非空) 。
情况三:左右子树皆非空
else {
else {
Node* maxNode = curNode->_left;
Node* maxParent = curNode;
while (maxNode->_right != nullptr ) {
maxParent = maxNode;
maxNode = maxNode->_right;
}
curNode->_key = maxNode->_key;
if (maxNode == maxParent->_right)
maxParent->_right = maxNode->_left;
else {
maxParent->_left = maxNode->_left;
}
delete maxNode;
}
return true ;
}
左右子树皆非空 :
用左子树中的最大节点 或右子树中的最小节点 替换当前节点的值,然后删除左子树中的最大节点 或右子树中的最小节点
本代码选择用左子树的最大节点 maxNode 替换 curNode(即向左一次再一路向右走到最右)。
找到 maxNode 及其父 maxParent 后, curNode->_key = maxNode->_key 替换 curNode 中的值,然后把 maxNode 从树上摘除:
maxNode 是左子树的最大值,因此 maxNode一定没有右孩子 ,但可能会有左孩子,左孩子要挂回 maxParent
且 maxNode 既可能是 maxParent 的左节点,也可能是 maxParent 的右节点
托孤的代码 :
托孤需要确认孤儿结点是在 parent 的左子树还是右子树,正确托孤后释放结点
if (maxNode == maxParent->_right) {
maxParent->_right = maxNode->_left;
}
else {
maxParent->_left = maxNode->_left;
}
delete maxNode;
完整代码与逐步说明 bool erase (const K& key) {
Node* parent = nullptr ;
Node* curNode = _root;
while (curNode) {
if (key > curNode->_key) {
parent = curNode;
curNode = curNode->_right;
} else if (key < curNode->_key) {
parent = curNode;
curNode = curNode->_left;
}
else {
if (curNode->_left == nullptr ) {
if (curNode == _root)
_root = curNode->_right;
else if (curNode == parent->_left)
parent->_left = curNode->_right;
else
parent->_right = curNode->_right;
delete curNode;
return true ;
}
else if (curNode->_right == nullptr ) {
if (curNode == _root)
_root = curNode->_left;
else if (curNode == parent->_left)
parent->_left = curNode->_left;
else
parent->_right = curNode->_left;
delete curNode;
return true ;
}
else {
Node* maxNode = curNode->_left;
Node* maxParent = curNode;
while (maxNode->_right != nullptr ) {
maxParent = maxNode;
maxNode = maxNode->_right;
}
curNode->_key = maxNode->_key;
if (maxNode == maxParent->_right)
maxParent->_right = maxNode->_left;
else {
maxParent->_left = maxNode->_left;
}
delete maxNode;
}
return true ;
}
}
return false ;
}
左子树为空(curNode->_left == nullptr) :
如果该节点是根:_root = curNode->_right;
否则根据 curNode 是父节点的左/右孩子,把父指针指向 curNode->_right(托孤),再 delete curNode。
这包含了叶子节点(左右都空)和只有右孩子的情况(因为左空且右可能为空或非空) 。
右子树为空(curNode->_right == nullptr) :
与左子树为空时对称 :
如果该节点是根:_root = curNode->_left;
否则根据 curNode 是父节点的左/右孩子,把父指针指向 curNode->_left(托孤),再 delete curNode。
这包含了叶子节点(左右都空)和只有右孩子的情况(因为左空且右可能为空或非空)
左右子树皆非空 :
需要找左子树中最大的、或右子树中最小的替换 当前要被删除的结点
二叉搜索树中:
子树的最大结点 ,是树中最右边的结点
子树的最小结点,是树中最左边的结点
这里找左子树中最大的结点
用左子树中的最大节点 或右子树中的最小节点 替换当前节点的值,然后删除左子树中的最大节点 或右子树中的最小节点
本代码选择用左子树的最大节点 maxNode 替换 curNode(即向左一次再一路向右走到最右)。
找到 maxNode 及其父 maxParent 后, curNode->_key = maxNode->_key 替换 curNode 中的值,然后把 maxNode 从树上摘除:
maxNode 是左子树的最大值,因此 maxNode一定没有右孩子 ,但可能会有左孩子,左孩子要挂回 maxParent
且 maxNode 既可能是 maxParent 的左节点,也可能是 maxParent 的右节点
托孤的代码 :
托孤需要确认孤儿结点是在 parent 的左子树还是右子树,正确托孤后释放结点
while 循环结束后,代表三种情况中都未完成删除 ,返回 false
if (maxNode == maxParent->_right) {
maxParent->_right = maxNode->_left;
}
else {
maxParent->_left = maxNode->_left;
}
delete maxNode;
递归删除 public :
bool erase_R (const K& key) {
return _erase_R(_root, key);
}
private :
bool _erase_R(Node*& root, const K& key) {
if (root == nullptr )
return false ;
if (key > root->_key)
return _erase_R(root->_right, key);
else if (key < root->_key)
return _erase_R(root->_left, key);
else {
Node* delNode = root;
if (root->_left == nullptr ) {
root = root->_right;
}
else if (root->_right == nullptr ) {
root = root->_left;
}
else {
Node* maxNode = root->_left;
while (maxNode->_right) {
maxNode = maxNode->_right;
}
std::swap (maxNode->_key, root->_key);
return _erase_R(root->_left, key);
}
delete delNode;
return true ;
}
}
root == nullptr 时,返回 false
key > root->_key 时,递归去右子树中查找
key < root->_key 时,递归去左子树中查找
else 代表相等,查找到了要被删除的结点
bool _erase_R(Node*& root, const K& key) {
if (root == nullptr )
return false ;
if (key > root->_key)
return _erase_R(root->_right, key);
else if (key < root->_key)
return _erase_R(root->_left, key);
else {
}
}
else {
Node* delNode = root;
if (root->_left == nullptr ) {
root = root->_right;
}
else if (root->_right == nullptr ) {
root = root->_left;
}
else {
Node* maxNode = root->_left;
while (maxNode->_right) {
maxNode = maxNode->_right;
}
std::swap (maxNode->_key, root->_key);
return _erase_R(root->_left, key);
}
delete delNode;
return true ;
}
参数 Node*& root 是对结点指针的直接引用 ,使得当我们把 root 指向 root->_right(或 _left)时,父节点相应的指针会被更新(递归中直接修改)。
删除时前两种情况的思路依然是托孤 ,第三种采用替换法:
Node*& root 是对结点指针的直接引用 ,托孤时无需记录 parent 指针,可以直接对其修改
若其左子树为空,将 root 指向 root->_right(托孤),再 delete delNode。
若其右子树为空,类似处理。
若左右都不为空:找到左子树最大节点 maxNode,交换值(swap),然后在左子树递归删除原来持有该值的那个节点。
递归的本质 :
交换 key
root 是要被删除的结点,但它有左右孩子,不能直接删。
于是我们找到左子树中最大的结点 maxNode,把 root->_key 和 maxNode->_key 交换。
这样 逻辑上相当于'把删除任务转移到了 maxNode' 。
为什么 maxNode 一定能删除?
maxNode 是左子树中最大的结点,所以它 不可能有右子树 (因为再往右就超过它了)。
它可能有左孩子,也可能没有。
这就转化成了 情况 1 或 情况 2 (更简单的删除)。
递归删除
因为 root->_key 已经被换成了 maxNode->_key,那么树里相当于有两个相同的 key。
所以我们递归到 root->_left 去 继续删除 key 。
最终会定位到 maxNode,并用情况 1 或 2 把它删掉。
递归删除写法短小且清晰,利用引用参数避免手工维护父节点 parent 。
👉 总结一句话:
第三种情况的递归逻辑就是:把当前结点和左子树最大结点交换,让删除目标转移到最大结点,然后递归在左子树中删除这个目标。最终会简化成情况 1 或 2。
4. 性能分析
查找 / 插入 / 删除(平均)时间复杂度 :O(h) ,h 为树高。
空间 :迭代版本额外 O(1);递归版本额外 O(h) 递归栈。
拷贝构造/Copy : O(n) 时间与 O(h) 递归栈。
结点数为 N 的二叉搜索树,最多查找高度次。对于随机插入的平衡树平均 h = O(log n);最坏情况下 h = O(n)。
最优情况下 :⼆叉搜索树为完全⼆叉树 (或者接近完全二叉树) ,其高度为: log2 N
最差情况下 :⼆叉搜索树(退化为单链表 ),其高度为: N
综合而言,⼆叉搜索树增删查改 时间复杂度为: O(N)
那么这样的效率显然是⽆法满⾜我们需求的,后续会学习**⼆叉搜索树的变形**,平衡⼆叉搜索树 AVL 树和红⿊树,才能适⽤于我们在内存中存储和搜索数据高性能需求 。
⼆分查找 也可以实现 O(log2 N) 级别的查找效率,但是**⼆分查找有两⼤缺陷**:
需要存储在**⽀持下标随机访问的结构中,并且 有序**。
插⼊和删除数据效率很低 ,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。
5. 完整实现代码
template <class K >
struct BSTreeNode {
BSTreeNode (const K& key) : _left(nullptr ), _right(nullptr ), _key(key) {}
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
};
template <typename K>
class BSTree {
typedef BSTreeNode<K> Node;
public :
BSTree () : _root(nullptr ) {}
BSTree (const BSTree<K>& tree) : _root(nullptr ) { _root = Copy (tree._root); }
BSTree<K>& operator =(BSTree<K> tmp) {
std::swap (_root, tmp._root);
return *this ;
}
~BSTree () { Destroy (_root); }
bool insert (const K& key) {
if (_root == nullptr ) {
_root = new Node (key);
return true ;
}
Node* parent = nullptr ;
Node* curNode = _root;
while (curNode != nullptr ) {
if (key > curNode->_key) {
parent = curNode;
curNode = curNode->_right;
} else if (key < curNode->_key) {
parent = curNode;
curNode = curNode->_left;
} else {
return false ;
}
}
curNode = new Node (key);
if (key > parent->_key) {
parent->_right = curNode;
} else
parent->_left = curNode;
return true ;
}
void Inorder () const {
_Inorder(_root);
printf ("\n" );
}
bool find (const K& key) const {
Node* curNode = _root;
while (curNode) {
if (key == curNode->_key)
return true ;
else if (key > curNode->_key)
curNode = curNode->_right;
else
curNode = curNode->_left;
}
return false ;
}
bool erase (const K& key) {
Node* parent = nullptr ;
Node* curNode = _root;
while (curNode) {
if (key > curNode->_key) {
parent = curNode;
curNode = curNode->_right;
} else if (key < curNode->_key) {
parent = curNode;
curNode = curNode->_left;
}
else {
if (curNode->_left == nullptr ) {
if (curNode == _root)
_root = curNode->_right;
else if (curNode == parent->_left)
parent->_left = curNode->_right;
else
parent->_right = curNode->_right;
delete curNode;
return true ;
}
else if (curNode->_right == nullptr ) {
if (curNode == _root)
_root = curNode->_left;
else if (curNode == parent->_left)
parent->_left = curNode->_left;
else
parent->_right = curNode->_left;
delete curNode;
return true ;
}
else {
Node* maxNode = curNode->_left;
Node* maxParent = curNode;
while (maxNode->_right != nullptr ) {
maxParent = maxNode;
maxNode = maxNode->_right;
}
curNode->_key = maxNode->_key;
if (maxNode == maxParent->_right)
maxParent->_right = maxNode->_left;
else {
maxParent->_left = maxNode->_left;
}
delete maxNode;
}
return true ;
}
}
return false ;
}
bool find_R (const K& key) const {
return _find_R(_root, key);
}
bool insert_R (const K& key) {
return _insert_R(_root, key);
}
bool erase_R (const K& key) {
return _erase_R(_root, key);
}
private :
Node* Copy (Node* root) {
if (root == nullptr )
return nullptr ;
else {
Node* newRoot = new Node (root->_key);
newRoot->_left = Copy (root->_left);
newRoot->_right = Copy (root->_right);
return newRoot;
}
}
void Destroy (Node*& root) {
if (root == nullptr )
return ;
Destroy (root->_left);
Destroy (root->_right);
delete root;
root = nullptr ;
}
void _Inorder(Node* root = _root) const {
if (root == nullptr ) {
return ;
}
_Inorder(root->_left);
cout << root->_key << " " ;
_Inorder(root->_right);
}
bool _find_R(const Node* root, const K& key) const {
if (root == nullptr )
return false ;
if (key < root->_key)
return _find_R(root->_left, key);
else if (key > root->_key)
return _find_R(root->_right, key);
else
return true ;
}
bool _insert_R(Node*& root, const K& key) {
if (root == nullptr ) {
root = new Node (key);
return true ;
}
if (key < root->_key)
return _insert_R(root->_left, key);
else if (key > root->_key)
return _insert_R(root->_right, key);
else
return false ;
}
bool _erase_R(Node*& root, const K& key) {
if (root == nullptr )
return false ;
if (key > root->_key)
return _erase_R(root->_right, key);
else if (key < root->_key)
return _erase_R(root->_left, key);
else {
Node* delNode = root;
if (root->_left == nullptr ) {
root = root->_right;
}
else if (root->_right == nullptr ) {
root = root->_left;
}
else {
Node* maxNode = root->_left;
while (maxNode->_right) {
maxNode = maxNode->_right;
}
std::swap (maxNode->_key, root->_key);
return _erase_R(root->_left, key);
}
delete delNode;
return true ;
}
}
private :
Node* _root;
};
6. 结语 通过本文的实现,我们完整梳理了二叉搜索树的设计与核心操作:
从结点与树的架构设计 入手,逐步实现了 构造/析构、拷贝与赋值 ;
结合 迭代与递归 两种思路,详细探讨了 插入、查找、中序遍历、删除 的实现细节与关键陷阱;
最后分析了二叉搜索树在不同情况下的性能表现,并指出了其在退化时的效率瓶颈。
二叉搜索树作为最基础的树形数据结构,是理解 AVL 树、红黑树等平衡二叉树 的起点。掌握 BST 的实现,不仅能加深对 STL 底层的理解,也能为后续学习更复杂的数据结构打下坚实的基础。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online