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

C++ 二叉搜索树详解:增删查改与 key/value 场景实现

二叉搜索树是一种基于节点大小关系组织数据的结构,左子树小于根,右子树大于根。其查找、插入和删除的平均时间复杂度为 O(logN),最坏情况下退化为链表则为 O(N)。相比二分查找,BST 在保持有序性的同时优化了动态插入和删除的效率。本文详细阐述了 BST 的核心概念、性能瓶颈及三种典型操作逻辑,重点讲解了单孩子节点与双孩子节点的删除策略,并区分了仅存储 Key 的集合场景与 Key-Value 映射场景的实现差异,提供了完整的 C++ 代码示例供参考。

1951018925发布于 2026/3/27更新于 2026/10/24107 浏览
C++ 二叉搜索树详解:增删查改与 key/value 场景实现

正文

1. 二叉搜索树的概念

二叉搜索树(Binary Search Tree, BST)又称二叉排序树。它要么是一棵空树,要么满足以下性质:

  • 若左子树不为空,则左子树上所有结点的值都小于等于根结点的值。
  • 若右子树不为空,则右子树上所有结点的值都大于等于根结点的值。
  • 左右子树也分别为二叉搜索树。

关于相等值的处理,具体取决于使用场景的定义。后续学习的 std::set/std::map 不支持插入相等值,而 std::multiset/std::multimap 支持插入相等值。

二叉搜索树示例

2. 二叉搜索树的性能分析

在最优情况下,二叉搜索树接近完全二叉树,其高度为 log₂N;而在最差情况下,树退化为单支树(类似链表),高度为 N。

因此,综合而言,二叉搜索树增删查改的时间复杂度介于 O(logN) 到 O(N) 之间。

单纯的二叉搜索树效率在某些极端数据下无法满足要求,这也是后续衍生出平衡搜索二叉树(如 AVLTree、红黑树 RBTree)的原因,它们通过特殊设计将效率稳定在 log₂N 级别。

大家可能有个疑问:既然有了时间复杂度为 logN 的二分查找算法,为什么还要设计搜索二叉树?

二分查找的缺陷:

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

正是二分查找的这些缺陷体现了二叉搜索树的价值。

最优二叉搜索树 最差二叉搜索树

3. 二叉搜索树的插入

插入过程遵循以下逻辑:

  1. 若树为空,则直接新增结点,赋值给 root 指针。
  2. 若树不空,按二叉搜索树性质比较:插入值比当前结点大往右走,小往左走。
  3. 找到空位置后插入新结点。
  4. 如果支持插入相等的值,插入值跟当前结点相等时可以往右或往左走,但需保持逻辑一致性。
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

基于上述数据的二叉搜索树结构如下:

插入前结构

此时再插入 16 和 13:

插入后结构

4. 二叉搜索树的查找

从根开始比较:

  • 若 x 比根的值大,则往右边查找。
  • 若 x 比根的值小,则往左边查找。
  • 最多查找高度次,走到空节点还没找到,说明该值不存在。

如果不支持插入相等值,找到 x 即可返回;如果支持插入相等值,意味着有多个 x 存在,一般要求查找中序的第一个 x。

遍历方式包括前序(根左右)、中序(左根右)、后序(左右根)。

查找示例

5. 二叉搜索树的删除

首先查找元素是否在树中,如果不存在则返回 false。如果存在,根据要删除的结点 N 的孩子情况分四种处理:

  1. 要删除结点 N 左右孩子均为空。
  2. 要删除的结点 N 左孩子为空,右孩子不为空。
  3. 要删除的结点 N 右孩子为空,左孩子不为空。
  4. 要删除的结点 N 左右孩子均不为空。

在代码实现上,其实可以划分为两种情况:

  • 没有孩子和只有一个孩子:视为一种情况,因为没有孩子可视作只有一个孩子是 nullptr。
  • 有两个孩子:无法直接删除,需要使用替换法。

解决方案:

第一种情况:以只有右孩子为例,把 N 结点的父亲对应孩子指针指向 N 的右孩子,直接删除 N 结点。没有孩子时,N 结点的父亲对应孩子的指针指向 nullptr,符合逻辑。

第二种情况:无法直接删除 N 结点,因为 N 的两个孩子无处安放,只能用替换法删除。找 N 左子树的值最大结点 R(最右结点)或者 N 右子树的值最小结点 R(最左结点)替代 N。因为这两个结点中任意一个放到 N 的位置,都满足二叉搜索树的规则。替代 N 的意思就是 N 和 R 的两个结点的值交换,转而变成删除 R 结点。

删除手绘图

6. 二叉搜索树 key 和 key/value 使用场景

6.1 key 搜索场景

只有 key 作为关键码,结构中只需要存储 key 即可。关键码即为需要搜索到的值,搜索场景只需要判断 key 在不在。

key 的搜索场景实现的二叉搜索树支持增删查,但是不支持修改,修改 key 会破坏搜索树结构。

简单举例:

  • 车牌抬杆识别系统,判断车牌字符是否存在(后台将车牌号录入搜索二叉树)。
  • 检查一篇英文文章是否有错(词典录入二叉树)。
  • 编译器的语法检查(将库、自定义、关键词放入二叉树)。

总之,key 的搜索二叉树一般用于快速查询数据是否存在。

6.2 key/val 搜索场景

每一个关键码 key,都有与之对应的值 value,value 可以是任意类型对象。树的结构中除了需要存储 key 还要存储对应的 value,增/删/查还是以 key 为关键字走二叉搜索树的规则进行比较,可以快速找到 key 对应的 value。

key/value 的搜索场景实现的二叉搜索树支持修改 value,但是不支持修改 key,修改 key 会破坏搜索树性质。

简单举例:

  • 检查中英文互译系统(key 即英文,value 即中文)。
  • 商场车库系统(收费自助类,通过存车票号和入场时间计算租金)。
  • 读取一篇文章中英文出现的次数。

7. 二叉搜索树的实现代码

7.1 key 模型代码实现

这里的 set 不是完全 set 的底层封装,本代码只是二叉搜索树 key 模型的实现代码。

#pragma once
#include <assert.h>
#include <iostream>
using namespace std;

// 去重版 set
namespace twg {
    template<class K>
    struct BSTnode {
        K _key;
        BSTnode* left;
        BSTnode* right;

        BSTnode(const K& key = K()) : _key(key), left(nullptr), right(nullptr) {}
    };

    template<class K>
    class set {
        typedef BSTnode<K> Node;
    public:
        bool Insert(const K& key) {
            if (_root == nullptr) {
                _root = new Node(key);
            }
            Node* cur = _root;
            Node* parent = nullptr;
            while (cur) {
                if (key < cur->_key) {
                    parent = cur;
                    cur = cur->left;
                } else if (key > cur->_key) {
                    parent = cur;
                    cur = cur->right;
                } else {
                    return true; // 已存在
                }
            }
            cur = new Node(key);
            if (parent->right == nullptr) {
                parent->right = cur;
            } else {
                parent->left = cur;
            }
            return true;
        }

        bool Erase(const K& key) {
            Node* cur = _root;
            Node* parent = nullptr;
            while (cur) {
                if (key < cur->_key) {
                    parent = cur;
                    cur = cur->left;
                } else if (key > cur->_key) {
                    parent = cur;
                    cur = cur->right;
                } else {
                    if (cur->left == nullptr) {
                        if (parent == nullptr) {
                            _root = cur->right;
                        } else if (parent->left == cur) {
                            parent->left = cur->right;
                        } else {
                            parent->right = cur->right;
                        }
                        delete cur;
                        return true;
                    } else {
                        Node* rightMinf = cur;
                        Node* rightMin = cur->left;
                        while (rightMin->left) {
                            rightMinf = rightMin;
                            rightMin = rightMin->left;
                        }
                        if (parent->left == cur) {
                            parent->left = rightMin;
                        } else {
                            parent->right = rightMin;
                        }
                        rightMinf->left = rightMin->right;
                        rightMin->left = cur->left;
                        rightMin->right = cur->right;
                        delete cur;
                        return true;
                    }
                }
            }
            cout << "没有该节点" << endl;
            return false;
        }

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

        void Inorder() {
            assert(_root);
            inorder(_root);
        }

    private:
        void inorder(const Node* root) {
            if (root == nullptr) return;
            inorder(root->left);
            cout << root->_key << endl;
            inorder(root->right);
        }
        Node* _root = nullptr;
    };
}

// 模拟 unordered_set 即不去重版
namespace Twg {
    template<class K>
    struct BSTnode {
        K _key;
        BSTnode* left;
        BSTnode* right;

        BSTnode(const K& key = K()) : _key(key), left(nullptr), right(nullptr) {}
    };

    template<class K>
    class set {
        typedef BSTnode<K> Node;
    public:
        bool Insert(const K& key) {
            if (_root == nullptr) {
                _root = new Node(key);
                return true;
            }
            Node* cur = _root;
            Node* parent = nullptr;
            while (cur) {
                if (key <= cur->_key) {
                    parent = cur;
                    cur = cur->left;
                } else {
                    parent = cur;
                    cur = cur->right;
                }
            }
            cur = new Node(key);
            if (parent->right == nullptr) {
                parent->right = cur;
            } else {
                parent->left = cur;
            }
            return true;
        }

        bool Erase(const K& key) {
            Node* cur = _root;
            Node* parent = nullptr;
            while (cur) {
                if (key < cur->_key) {
                    parent = cur;
                    cur = cur->left;
                } else if (key > cur->_key) {
                    parent = cur;
                    cur = cur->right;
                } else {
                    if (cur->left == nullptr) {
                        if (parent == nullptr) {
                            _root = cur->right;
                        } else if (parent->left == cur) {
                            parent->left = cur->right;
                        } else {
                            parent->right = cur->right;
                        }
                        delete cur;
                        return true;
                    } else {
                        Node* rightMinf = cur;
                        Node* rightMin = cur->left;
                        while (rightMin->left) {
                            rightMinf = rightMin;
                            rightMin = rightMin->left;
                        }
                        if (parent->left == cur) {
                            parent->left = rightMin;
                        } else {
                            parent->right = rightMin;
                        }
                        rightMinf->left = rightMin->right;
                        rightMin->left = cur->left;
                        rightMin->right = cur->right;
                        delete cur;
                        return true;
                    }
                }
            }
            cout << "没有该节点" << endl;
            return false;
        }

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

        void Inorder() {
            assert(_root);
            inorder(_root);
        }

    private:
        void inorder(const Node* root) {
            if (root == nullptr) return;
            inorder(root->left);
            cout << root->_key << endl;
            inorder(root->right);
        }
        Node* _root = nullptr;
    };
}
7.2 key/val 代码实现
#pragma once
#include <iostream>
using namespace std;

namespace twg {
    template<class K, class V>
    struct BSTnode {
        K _key;
        V _val;
        BSTnode* left;
        BSTnode* right;

        BSTnode(const K& key, const V& val) : _key(key), _val(val), left(nullptr), right(nullptr) {}
    };

    template<class K, class V>
    class map {
        typedef BSTnode<K, V> Node;
    public:
        map() = default;
        ~map() { Destory(_root); }

        map(const map<K, V>& a) {
            _root = Copy(a._root);
        }

        map<K, V>& operator=(map<K, V> a) {
            std::swap(_root, a._root);
            return *this;
        }

        bool Insert(const K& key, const V& val) {
            if (_root == nullptr) {
                _root = new Node(key, val);
                return true;
            }
            Node* cur = _root;
            Node* parent = nullptr;
            while (cur) {
                if (key <= cur->_key) {
                    parent = cur;
                    cur = cur->left;
                } else {
                    parent = cur;
                    cur = cur->right;
                }
            }
            cur = new Node(key, val);
            if (cur->_key <= parent->_key) {
                parent->left = cur;
            } else {
                parent->right = cur;
            }
            return true;
        }

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

        bool Erase(const K& key) {
            Node* cur = _root;
            Node* parent = nullptr;
            while (cur) {
                if (key < cur->_key) {
                    parent = cur;
                    cur = cur->left;
                } else if (key > cur->_key) {
                    parent = cur;
                    cur = cur->right;
                } else {
                    if (cur->right != nullptr) {
                        if (parent == nullptr) {
                            _root = cur->right;
                        } else if (parent->left == cur) {
                            parent->left = cur->right;
                        } else {
                            parent->right = cur->right;
                        }
                        delete cur;
                        return true;
                    } else {
                        Node* leftMax = cur->right;
                        Node* leftMaxf = cur;
                        while (leftMax->right) {
                            leftMaxf = leftMax;
                            leftMax = leftMax->right;
                        }
                        cur->_val = leftMax->_val;
                        cur->_key = leftMax->_key;
                        leftMaxf->right = leftMax->left;
                        delete leftMax;
                        return true;
                    }
                }
            }
            cout << "没有该节点,删除失败" << endl;
            return false;
        }

        V& operator[](const K& key) {
            Node* cur = _root;
            Node* parent = nullptr;
            while (cur) {
                if (key < cur->_key) {
                    parent = cur;
                    cur = cur->left;
                } else if (key > cur->_key) {
                    parent = cur;
                    cur = cur->right;
                } else {
                    return cur->_val;
                }
            }
            cur = new Node(key, V());
            if (parent == nullptr) {
                _root = cur;
            } else {
                if (key < parent->_key) parent->left = cur;
                else parent->right = cur;
            }
            return cur->_val;
        }

        void Inorder() {
            _inorder(_root);
            cout << endl;
        }

    private:
        Node* Copy(const Node* root) {
            if (root == nullptr) return nullptr;
            Node* newNode = new Node(root->_key, root->_val);
            newNode->left = Copy(root->left);
            newNode->right = Copy(root->right);
            return newNode;
        }

        void Destory(Node* root) {
            if (root == nullptr) return;
            Destory(root->left);
            Destory(root->right);
            delete root;
        }

        void _inorder(const Node* root) {
            if (root == nullptr) return;
            _inorder(root->left);
            cout << root->_key << ": " << root->_val << endl;
            _inorder(root->right);
        }

        Node* _root = nullptr;
    };
}

结语

二叉搜索树的'左小右大'特性背后,是高效数据查询的核心逻辑。它既弥补了二分查找在插入删除上的短板,也为后续学习平衡树(AVL、红黑树)打下基础。掌握双场景适配与增删查改的边界处理后,你已能应对简单的数据存储需求。

目录

  1. 正文
  2. 1. 二叉搜索树的概念
  3. 2. 二叉搜索树的性能分析
  4. 3. 二叉搜索树的插入
  5. 4. 二叉搜索树的查找
  6. 5. 二叉搜索树的删除
  7. 6. 二叉搜索树 key 和 key/value 使用场景
  8. 6.1 key 搜索场景
  9. 6.2 key/val 搜索场景
  10. 7. 二叉搜索树的实现代码
  11. 7.1 key 模型代码实现
  12. 7.2 key/val 代码实现
  13. 结语

更多推荐文章

查看全部
  • 基于 AI 辅助开发的在线智能考试系统实战
  • 基于 DeepSeek 与 Cursor 构建智能代码审查工具实战
  • Microsoft Visual C++ 运行库安装与 DLL 缺失修复指南
  • 35 道常见的前端 Vue 面试题详解
  • 鸿蒙系统 JDK 编译与获取指南
  • AIGC 内容创作:AI 文字、图像、音频和视频的创作流程
  • 2026 年 11 款开源及免费项目管理工具选型对比与优劣分析
  • JavaShop 新零售电商系统架构与核心功能解析
  • Python 爬虫入门:构建第一个简单爬虫
  • 微信小程序原生开发入门:从零构建首个可交互页面
  • Python 爬虫云端部署与定期任务调度方案
  • MiniMax MCP Server:多模态生成服务器,支持视频/语音/图像生成
  • FreeRTOS 退避算法核心逻辑与实现
  • SPA 单页应用无刷新更新部署方案与实战优化
  • Spring Web MVC 入门:从概念到实践
  • TeleGrip 基于 VR 的机械臂遥操作系统源码解析
  • Vue3 H5 端纯前端人脸识别与活体检测方案
  • 视觉 Transformer (ViT) 技术原理及三篇经典论文解析
  • OpenClaw 在 WSL Ubuntu 及 CentOS 虚拟机安装与飞书对接教程
  • 大模型(LLM)在金融投资领域的应用

相关免费在线工具

  • 加密/解密文本

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