跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

二叉搜索树 C++ 实现:增删查改详解

二叉搜索树(BST)是一种特殊的二叉树,左子树节点值小于等于根节点,右子树大于等于根节点。使用 C++ 模板类实现了 BST 的节点定义、插入、查找、中序遍历及删除操作,涵盖单键与键值对两种场景。重点解析了删除双孩子节点时的替换策略,并对比了二分查找在动态数据下的优劣,为理解红黑树等平衡树结构打下基础。平均时间复杂度 O(log n),最坏 O(n)。

CryptoLab发布于 2026/3/26更新于 2026/9/860 浏览
二叉搜索树 C++ 实现:增删查改详解

什么是二叉搜索树

二叉搜索树(Binary Search Tree, BST)又称二叉排序树。它或者是一棵空树,或者是具有以下性质的二叉树:

  • 左子树上所有节点的值都小于等于根结点的值;
  • 右子树上所有节点的值都大于等于根结点的值;
  • 它的左右子树也分别为二叉搜索树。

注意:二叉搜索树中可以支持插入相等的值,也可以不支持。具体取决于使用场景定义。例如标准库中的 std::map/std::set 不支持插入相等值,而 std::multimap/std::multiset 支持。

性能分析

在最优情况下,二叉搜索树为完全二叉树(或接近完全二叉树),其高度约为 log₂N。

最差情况下,二叉搜索树退化为单支树(类似链表),其高度为 N。

因此平均时间复杂度为 O(log n),最差情况为 O(n)。

虽然二分查找也能实现 O(log₂N) 级别的查找效率,但存在两大缺陷:

  1. 需要存储在支持下标随机访问的结构中,并且数据必须有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除通常需要挪动大量数据。

Key 类型二叉搜索树的实现

后续要学习的 std::set/std::multiset 容器底层数据结构就是红黑树——一种'近似平衡'的二叉搜索树。对于 set/multiset 容器,集合是一种按照特定顺序存储唯一元素的容器。

节点结构

template<class K> struct BSTNode {
    K _key;
    BSTNode<K>* _left;
    BSTNode<K>* _right;
    
    BSTNode(const K& key = 0)
        :_key(key), _left(nullptr), _right(nullptr) {}
};

类结构

template<class K> class BSTree {
    using Node = BSTNode<K>;
public:
    // ... 成员函数
private:
    Node* _root = nullptr;
};

下面实现过程中默认为值不重复的情形。

插入操作

思路如下:

  1. 若树为空,直接新增结点,赋值给 root 指针。
  2. 若树不空,按二叉搜索树性质,用 cur 指针遍历,parent 指针记下父亲节点。若插入值 key 比当前节点大往右走,小则往左走,直到找到空位置插入新结点。
  3. 确定新节点是插在 parent 的左边还是右边。
  4. 若支持插入相等值,需统一规则(如全部往右走)。这里我们实现不支持重复值的逻辑。
bool Insert(const K& key) {
    Node* node = new Node(key);
    if (_root == nullptr) {
        _root = node;
        return true;
    }
    Node* parent = nullptr;
    Node* cur = _root;
    while (cur) {
        if (key < cur->_key) {
            parent = cur;
            cur = cur->_left;
        } else if (key > cur->_key) {
            parent = cur;
            cur = cur->_right;
        } else {
            return false; // 已存在
        }
    }
    if (key < parent->_key) {
        parent->_left = node;
    } else {
        parent->_right = node;
    }
    return true;
}

中序遍历

根据二叉搜索树的特性,其中序遍历的结果恰好为一个有序序列(升序)。由于 _root 是成员变量,在类外调用无法直接传参,通常采用辅助递归函数实现。

void MidOrder() {
    _MidOrder(_root);
}

void _MidOrder(Node* _root) {
    if (_root == nullptr) {
        return;
    }
    _MidOrder(_root->_left);
    cout << _root->_key << " ";
    _MidOrder(_root->_right);
}

查找操作

从根开始比较,val 比根大往右找,小则往左找。最多查找高度次,若走到空仍未找到则返回 false。

bool Find(const K& val) {
    Node* cur = _root;
    while (cur) {
        if (val < cur->_key) {
            cur = cur->_left;
        } else if (val > cur->_key) {
            cur = cur->_right;
        } else {
            return true;
        }
    }
    return false;
}

删除操作

删除是最复杂的部分,主要分三种情况处理:

  1. 左为空:将父节点的指针指向右孩子(若为根节点则更新_root)。
  2. 右为空:将父节点的指针指向左孩子(若为根节点则更新_root)。
  3. 左右都不为空:无法直接删除,需用替换法。找 N 左子树的最大结点(最右结点)或 N 右子树的最小结点(最左结点)替代 N。交换值后,问题转化为删除那个替代结点(此时该结点必属于情况 1 或 2)。
bool Erase(const K& val) {
    Node* cur = _root;
    Node* parent = nullptr;
    
    while (cur) {
        if (val < cur->_key) {
            parent = cur;
            cur = cur->_left;
        } else if (val > cur->_key) {
            parent = cur;
            cur = cur->_right;
        } else {
            // 找到待删除节点
            // 左为空
            if (cur->_left == nullptr) {
                if (_root == cur) {
                    _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 (_root == cur) {
                    _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;
                // 找右子树中最小值的节点 R
                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;
}

Key_Value 类型二叉搜索树的实现

对于 std::map/std::multimap 容器,映射是一种关联容器,存储由'键值(key value)'和'映射值(mapped value)'组合而成的元素。

节点与类结构

相比 Key 类型,节点多了 _value 成员,类结构基本一致。

template<class K, class V> struct BSTNode {
    K _key;
    V _value;
    BSTNode<K, V>* _left;
    BSTNode<K, V>* _right;
    
    BSTNode(const K& key = 0, const V& value = 0)
        :_key(key), _value(value), _left(nullptr), _right(nullptr) {}
};

template<class K, class V> class BSTree {
    using Node = BSTNode<K, V>;
public:
    // ...
private:
    Node* _root = nullptr;
};

除插入操作略有不同外,其他操作逻辑与 Key 类型基本相同,重点补充构造、析构及拷贝控制。

构造函数与拷贝构造

默认构造可直接使用 = default。拷贝构造需进行深拷贝,避免浅拷贝导致的悬空指针。

// 默认构造
BSTree() = default;

// 拷贝构造(深拷贝)
BSTree(const BSTree& tree) {
    _root = Copy(tree._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;
}

赋值重载与析构

现代 C++ 写法中,赋值运算符可通过交换实现(Copy-and-Swap 惯用法)。析构函数需使用后序遍历释放内存,防止根节点释放后丢失子节点引用。

// 赋值重载
BSTree& operator=(BSTree tree) {
    swap(_root, tree._root);
    return *this;
}

// 析构函数
~BSTree() {
    Destroy(_root);
    _root = nullptr;
}

void Destroy(Node* root) {
    if (root == nullptr) return;
    Destroy(root->_left);
    Destroy(root->_right);
    delete root;
}

插入操作

bool Insert(const K& key, const V& value) {
    Node* node = new Node(key, value);
    if (_root == nullptr) {
        _root = node;
        return true;
    }
    Node* parent = nullptr;
    Node* cur = _root;
    while (cur) {
        if (key < cur->_key) {
            parent = cur;
            cur = cur->_left;
        } else if (key > cur->_key) {
            parent = cur;
            cur = cur->_right;
        } else {
            return false;
        }
    }
    if (key < parent->_key) {
        parent->_left = node;
    } else {
        parent->_right = node;
    }
    return true;
}

总结

本文简单实现了二叉搜索树的基础版本,涵盖增删查改及构造析构等核心功能。它与 std::set/std::map 底层的平衡二叉搜索树(如红黑树)有所不同,未过多关注底层平衡细节,旨在帮助大家理解 BST 的基本原理,为后续学习平衡树打下基础。

目录

  1. 什么是二叉搜索树
  2. 性能分析
  3. Key 类型二叉搜索树的实现
  4. 节点结构
  5. 类结构
  6. 插入操作
  7. 中序遍历
  8. 查找操作
  9. 删除操作
  10. Key_Value 类型二叉搜索树的实现
  11. 节点与类结构
  12. 构造函数与拷贝构造
  13. 赋值重载与析构
  14. 插入操作
  15. 总结

更多推荐文章

查看全部
  • Flask 实战:从环境搭建到鉴权中间件
  • Linux System V 共享内存:原理、实操与常见陷阱
  • 基于 AnyRouter 中转的 Claude Code 本地配置指南
  • 无线蜂窝网络:原理、架构与代际演进
  • Python 通过 ctypes 调用 C++ DLL 的原理与实战
  • Llama-3.2-3B 在 Ollama 中配置长上下文与生成限制
  • Vue3 模板语法详解:插值、指令与响应式数据
  • OpenClaw Web Search 工具配置与渠道详解
  • 分布式文件系统 HDFS 数据读写过程详解
  • B 站转型观察:从二次元社区到 AI 创新孵化器
  • PowerWiki:基于 Git 的知识管理系统
  • Java 静态代码块与构造代码块详解
  • LFM2.5-1.2B-Thinking 模型效果展示与性能分析
  • 次模函数(Submodular Function)核心概念与机器学习应用
  • 蓝耘 × 通义万相 2.1,AIGC 双雄合璧,点燃数字艺术新引擎
  • JavaShop 新零售电商系统核心优势与功能解析
  • AI 产品经理指南:面试百人后的洞察与职业建议
  • 基于 Microi 吾码低代码框架构建 Vue 高效应用
  • Spring Boot 实战:MyBatis 操作数据库(上)
  • Perplexity 揭秘:AI 时代顶级团队的工作模式与协作范式

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,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