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

C++ 二叉搜索树:原理与增删查实现详解

二叉搜索树(BST)是一种特殊的二叉树,满足左子树所有节点值小于根节点,右子树所有节点值大于根节点。其核心操作包括查找、插入和删除。查找与二分查找类似,平均时间复杂度为 O(logn)。插入需确保不重复且保持有序性。删除操作最复杂,分无子节点、单子节点和双子节点三种情况,双子节点时通常用右子树最小节点或左子树最大节点替换。BST 适用于 K 模型(集合)和 KV 模型(字典),但需注意数据有序插入会导致退化为链表,实际应用中常结合平衡树结构使用。

指针猎手发布于 2026/3/22更新于 2026/7/932 浏览
C++ 二叉搜索树:原理与增删查实现详解

C++ 二叉搜索树:原理与增删查实现详解

二叉搜索树(Binary Search Tree, BST)是数据结构中非常基础且重要的一种树形结构。它不仅仅是面试中的常客,更是理解红黑树、AVL 树等平衡树的基础。今天我们就从原理出发,结合 C++ 代码,把它的增删查操作彻底搞懂。

理解二叉搜索树

一棵二叉搜索树必须满足以下两个核心性质:

  1. 左小右大:若根节点存在,其左子树中所有节点的值都小于根节点的值,右子树中所有节点的值都大于根节点的值。
  2. 递归定义:任意节点的左右子树也必须是二叉搜索树。

这意味着,对于树中的任意节点,其左侧的所有后代都比它小,右侧的所有后代都比它大。这种有序性使得查找效率远高于普通链表或无序树。

需要注意的是,如果插入的数据本身是有序的(例如 1, 2, 3, 4...),BST 会退化成一条链表,此时性能会急剧下降至 O(n)。这也是后续学习平衡树的原因,但在掌握 BST 本身之前,我们先专注于其基本逻辑。

核心操作解析

我们将树封装为一个模板类 BSTree,内部维护一个指向根节点的指针 _root。

1. 查找操作

查找的逻辑其实和二分查找非常像。给定目标值 val,我们从根节点开始比较:

  • 如果 val < cur->_key,说明目标在左边,往左走;
  • 如果 val > cur->_key,说明目标在右边,往右走;
  • 如果相等,直接返回当前节点。

当遍历到空指针时,说明树中不存在该值。平均情况下,时间复杂度为 O(log n),最坏情况(退化链表)为 O(n)。

Node* 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
            break; // 找到目标
    }
    return cur;
}

2. 插入操作

插入的核心在于保持'左小右大'的性质。流程如下:

  1. 处理空树:如果树是空的,新节点直接成为根节点。
  2. 寻找位置:从根节点出发,根据大小关系向下遍历,直到遇到空指针。
  3. 插入节点:在空指针处挂上新节点。

这里有个细节要注意:BST 通常不允许重复键值。如果在查找过程中发现已存在该值,应直接返回失败,不执行插入。

bool insert(const K& val) {
    // 空树情况
    if (_root == ) {
        _root =  (val);
         ;
    }

    Node* cur = _root;
    Node* curPre = ; 

     (cur) {
         (val == cur->_key)
             ; 
          (val < cur->_key) {
            curPre = cur;
            cur = cur->_left;
        }  {
            curPre = cur;
            cur = cur->_right;
        }
    }

    
     (curPre->_key > val)
        curPre->_left =  (val);
    
        curPre->_right =  (val);

     ;
}
nullptr
new
Node
return
true
nullptr
// 记录父节点,方便挂载
while
if
return
false
// 重复值,不插入
else
if
else
// 确定挂载方向
if
new
Node
else
new
Node
return
true

3. 删除操作

删除是 BST 中最复杂的部分,因为删除后必须保证树的性质不被破坏。我们需要分三种情况讨论:

情况一:待删除节点是叶子节点(无孩子)

直接删除即可,不影响其他节点。

情况二:待删除节点只有一个孩子

用唯一的子节点替换待删除节点的位置,相当于把子节点'提上来'。

情况三:待删除节点有两个孩子

这是难点。不能直接删除,否则左右子树无法连接。策略是:找替代者。

通常选择右子树的最小节点(即右子树的最左下节点)或者左子树的最大节点来覆盖待删除节点的值,然后删除那个替代节点。这样既保持了有序性,又避免了复杂的指针重连。

以选择右子树最小节点为例:

  1. 找到右子树的最小节点 rightMin。
  2. 将 rightMin 的值复制到待删除节点。
  3. 删除 rightMin 节点(此时 rightMin 最多只有一个右孩子,简化了删除逻辑)。
bool erase(const K& val) {
    if (_root == nullptr) return false;

    Node* cur = _root;
    Node* curPre = nullptr;

    // 1. 查找待删除节点
    while (cur) {
        if (val < cur->_key) {
            curPre = cur;
            cur = cur->_left;
        } else if (val > cur->_key) {
            curPre = cur;
            cur = cur->_right;
        } else {
            break;
        }
    }

    if (cur == nullptr) return false; // 未找到

    // 2. 处理删除逻辑
    // 情况 A: 0 个或 1 个孩子
    if (cur->_left == nullptr || cur->_right == nullptr) {
        Node* curNext = cur->_left ? cur->_left : cur->_right;

        if (cur != _root) {
            if (curPre->_left == cur)
                curPre->_left = curNext;
            else
                curPre->_right = curNext;
        } else {
            _root = curNext; // 删除的是根节点
        }
        delete cur;
    }
    // 情况 B: 2 个孩子
    else {
        // 找右子树最小节点
        Node* rightMin = cur->_right;
        Node* rightMinPre = cur;
        while (rightMin->_left) {
            rightMinPre = rightMin;
            rightMin = rightMin->_left;
        }

        // 值覆盖
        cur->_key = rightMin->_key;

        // 删除 rightMin 节点(此时 rightMin 最多只有右孩子)
        Node* rightMinNext = rightMin->_right;
        if (rightMinPre->_left == rightMin)
            rightMinPre->_left = rightMinNext;
        else
            rightMinPre->_right = rightMinNext;

        delete rightMin;
    }

    return true;
}

完整代码实现

下面是整合后的完整类定义及测试用例,包含了析构函数和中序遍历打印功能,方便验证结果。

#pragma once
#include <iostream>
#include <vector>
using namespace std;

template<class K>
struct BSTNode {
    BSTNode<K>* _left;
    BSTNode<K>* _right;
    K _key;

    BSTNode(const K& val) : _left(nullptr), _right(nullptr), _key(val) {}
};

template<class K>
class BSTree {
public:
    typedef BSTNode<K> Node;

    BSTree() : _root(nullptr) {}

    ~BSTree() {
        destroy(_root);
    }

    Node* 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 break;
        }
        return cur;
    }

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

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

        if (cur->_left == nullptr || cur->_right == nullptr) {
            Node* curNext = cur->_left ? cur->_left : cur->_right;
            if (cur != _root) {
                if (curPre->_left == cur) curPre->_left = curNext;
                else curPre->_right = curNext;
            } else {
                _root = curNext;
            }
            delete cur;
        } else {
            Node* rightMin = cur->_right;
            Node* rightMinPre = cur;
            while (rightMin->_left) {
                rightMinPre = rightMin;
                rightMin = rightMin->_left;
            }
            cur->_key = rightMin->_key;
            Node* rightMinNext = rightMin->_right;
            if (rightMinPre->_left == rightMin) rightMinPre->_left = rightMinNext;
            else rightMinPre->_right = rightMinNext;
            delete rightMin;
        }
        return true;
    }

    void inOrder() {
        _inOrder(_root);
        cout << endl;
    }

private:
    Node* _root;

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

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

void testBST() {
    BSTree<int> bst;
    vector<int> arr = {8, 3, 10, 1, 6, 14, 5, 7, 13};
    cout << "=== 创建二叉搜索树 ===\n";
    for (const auto& e : arr) bst.insert(e);
    cout << "中序遍历结果:";
    bst.inOrder();

    cout << "=== 查找数据 ===\n";
    cout << "查找 14: " << (bst.find(14) ? "存在" : "不存在") << endl;
    cout << "查找 28: " << (bst.find(28) ? "存在" : "不存在") << endl;

    cout << "=== 删除数据 ===\n";
    cout << "删除前:";
    bst.inOrder();
    bst.erase(6); // 双子节点
    cout << "删除 6 后:";
    bst.inOrder();
    bst.erase(1); // 叶子节点
    cout << "删除 1 后:";
    bst.inOrder();
}

典型应用场景

理解了增删查,我们来看看 BST 在实际中能做什么。

1. K 模型(Key Model)

只存储 Key,用于判断某个元素是否存在。

  • 词库校验:将字典里的单词作为 Key 存入 BST,输入单词时查询是否存在,快速判断拼写错误。
  • 门禁系统:记录授权人员 ID,判断是否在名单内。

2. KV 模型(Key-Value Model)

每个 Key 对应一个 Value,形成键值对。

  • 英汉词典:Key 是英文单词,Value 是中文释义。
  • 频率统计:Key 是单词,Value 是出现次数。遍历文本时,若 Key 存在则 Value+1,否则插入并置为 1。
// KV 模型示例:统计单词频率
template<class K, class V>
struct BSTNodeKV {
    K _key;
    V _value;
    BSTNodeKV<K, V>* _left;
    BSTNodeKV<K, V>* _right;
    BSTNodeKV(const K& k, const V& v) : _key(k), _value(v), _left(nullptr), _right(nullptr) {}
};

// 使用逻辑类似,只需在节点中增加 value 字段,并在插入/更新时处理 value 的增减

总结

二叉搜索树通过简单的规则实现了高效的查找能力。只要记住三个关键点:

  1. 有序性:左 < 根 < 右。
  2. 删除策略:双子节点时,用后继(右子树最小)或前驱(左子树最大)替换。
  3. 性能瓶颈:数据有序会导致退化,实际工程中常配合平衡机制使用。

掌握了 BST,你就迈进了高级数据结构的大门,后续的 AVL 树和红黑树本质上都是在解决 BST 不平衡的问题。

目录

  1. C++ 二叉搜索树:原理与增删查实现详解
  2. 理解二叉搜索树
  3. 核心操作解析
  4. 1. 查找操作
  5. 2. 插入操作
  6. 3. 删除操作
  7. 情况一:待删除节点是叶子节点(无孩子)
  8. 情况二:待删除节点只有一个孩子
  9. 情况三:待删除节点有两个孩子
  10. 完整代码实现
  11. 典型应用场景
  12. 1. K 模型(Key Model)
  13. 2. KV 模型(Key-Value Model)
  14. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI Agent 新范式:基于 FastGPT 与 MCP 协议构建工具增强型智能体
  • lwIP 实战解析:基于 CGI 与 SSI 的嵌入式 WebServer 开发
  • AI 大模型研究热点:RAG、Agent、Mamba、LoRA 与 MoE 技术解析
  • 2026 年 3 月全球 AI 前沿动态与技术突破
  • Docker CE 在 Kali/Ubuntu 系统上的安装与配置指南
  • 小厂架构师 AI Agent 落地实战:从全能幻想到最小可用场景
  • AI 鸿蒙 App 开发:从页面到能力系统的架构变革
  • Elden Ring AI 绘画指南:基于 Stable Diffusion 的黑暗奇幻创作
  • Python 版本与环境管理:虚拟环境实战指南
  • C++ 红黑树实现详解:规则、结构与核心操作
  • 人形机器人躯干系统设计:结构方案、驱动布局与刚度要求
  • OpenWebUI 结合 SearXNG 实现本地大模型联网搜索(含百度/360 配置)
  • Stable Diffusion 并行计算优化技巧与 GPU 性能提升指南
  • FLUX.1-dev 工作流:Midjourney 迁移指南与 Prompt 工程适配
  • AI 系统告警机制原理与代码实战
  • FPGA 开发常用软件对比:Vivado、Quartus 与 ModelSim
  • 接口测试用例模板详细说明
  • 哈希表核心原理与 C++ 实战实现
  • AI 助力六花直装 V8.3.9 自动化开发与测试
  • 攻防世界 Web 题解(七):SQL 注入、文件上传与命令执行

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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