跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
|注册
博客列表

目录

  1. 何为二叉搜索树
  2. 性能分析
  3. 搜索树的结构
  4. 插入和查找 (非冗余版本)
  5. 中序遍历
  6. 删除
  7. key/value
  8. 使用场景
  9. 代码实现
C++算法

C++ 二叉搜索树原理与实现

本文介绍了 C++ 二叉搜索树(BST)的定义、性质及实现。BST 左子树节点值小于等于根,右子树大于等于根,中序遍历有序。分析了其时间复杂度,最优 O(logN),最差 O(N)。详细阐述了插入、查找、中序遍历及删除节点的逻辑与代码实现,包括处理左右孩子为空、双亲节点等情况。最后展示了 Key/Value 版本的实现及应用场景,如字典翻译和词频统计。

MqEngine发布于 2026/3/29更新于 2026/4/131 浏览
C++ 二叉搜索树原理与实现

何为二叉搜索树

二叉搜索 (排序) 树具有如下特征:

若它的左子树不为空,则**左子树上所有结点的值都小于等于根结点的值;若它的右⼦树不为空,则右子树上所有结点的值都大于等于根结点的值;它的左右⼦树也分别为二叉搜索树;二叉搜索树的中序遍历是有序的;**了解二叉搜索树是为了之后为 map/set/multimap/multiset 的关联式容器打下基础。特别地,二叉搜索树分为冗余和不冗余版本。(冗余即有相等值也可插入)

文章配图

在冗余版本的二叉搜索树中,会出现如下两种情况,那这两都是正确的吗?

是的,并不影响搜索二叉树的性质。

文章配图

性能分析

可以发现在该二叉搜索树较为平衡的情况下,即在最优情况下,找到指定结点的情况下,只需要访问高度 k 次,即高度为:log₂N;但在最差情况下,高度会达到 N。(在红黑树章节会有详解如何平衡高度差)

综合来讲,二叉搜索树增删查改时间复杂度为:O(N)。

二分查找也能达到 log₂N 级别的查找效率,为何不使用二分查找呢?二分查找有以下缺陷:**需要存储在支持下标随机访问的结构中,并且有序。插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。**二叉搜索树在最差情况下能达到 O(N) 级别,那它能起到什么作用呢?二叉搜索树的引入是为了后续为平衡二叉树 AVL 树和红⿊树作铺垫,之后引入了旋转的概念,能够好维持二叉树的结构。

搜索树的结构

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

插入和查找 (非冗余版本)

插入时分为以下情况:

  1. 树为空时,申请一个新结点赋值给根结点_root;
  2. 树不为空时,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。(如若支持冗余,则可往左走,也可往右走,确保逻辑一致性)

文章配图

{ 
    
     (_root == ) { 
        _root =  (key); 
         ; 
    } 
    Node* parent = ; 
    Node* cur = _root; 
    
     (cur) { 
         (cur->_key < key) { 
            parent = cur; 
            cur = cur->_right; 
        }   (cur->_key > key) { 
            parent = cur; 
            cur = cur->_left; 
        }  { 
             ; 
        } 
    } 
    cur =  (key); 
     (parent->_key > key) { 
        parent->_left = cur; 
    }  { 
        parent->_right = cur; 
    } 
     ; 
}
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • ERNIE-4.5-0.3B 轻量模型部署指南与性能测评
  • 9.4k stars!手中就有一整个 AI 团队:agency-agents 深度解析手中就有一整个 AI 团队:agency-agents 深度解析!
  • Linux 系统权限概念与管理命令详解
  • 嵌入式 C/C++ 核心知识点整理
  • 本地训练专属大模型:DeepSeek-R1 微调实战指南
  • ERNIE-4.5-0.3B 轻量模型部署与性能测试指南
  • Faster-Whisper 本地部署实时语音转文本教程

相关免费在线工具

  • 加密/解密文本

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

bool Insert(const K& key)
// 树为空的情况
if
nullptr
new
Node
return
true
nullptr
// 树不为空的情况
while
if
else
if
else
return
false
new
Node
if
else
return
true
  1. 查找指定结点时,和插入结点的逻辑是类似的,新插入结点的值比当前结点大则往右走,反之往左走,找到后返回即可。
  2. 那如果是冗余版本的二叉搜索树呢?意味着存在多个相等的值,而我们一般要查找中序遍历后的第一个相等的值。迭代器也可以确认其相等的值的范围等等。

非冗余版本

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

中序遍历

这样设计的目的是让我们调用 InOrder 的时候不需要传_root 根节点 (根本原因是在类外不能访问 private 私有的_root,只能在类里访问)

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

删除

在二叉搜索树中删除的逻辑最为致命,它分为多种情况,需要考虑很多细节问题。

  1. 删除结点无左右孩子
  2. 删除结点有右孩子
  3. 删除结点有左孩子
  4. 删除结点有左右孩子

在情况 1 当中,把该结点的⽗亲对应孩⼦指针指向空,直接删除该结点,并不需要考虑删除结点是在左在右的情况。(情况 1 其实可以归类为情况 2 或情况 3 之中,本章节归类为情况 2)

文章配图

在情况 2 中:把 N 结点的⽗亲对应孩⼦指针指向 N 的右孩⼦,直接删除 N 结点

需要额外判断 parent 是左指针还是右指针指向 cur 的右孩子。

文章配图

在情况 3 中:把 N 结点的⽗亲对应孩⼦指针指向 N 的左孩⼦,直接删除 N 结点,依旧处理 parent 指向的问题。

文章配图

在情况 4 中:不能直接删除该结点且 parent 会不道指向 cur 的左还是右,这种情况需要找另一个结点来替代。为了保持二叉搜索树的性质,因此替代的结点必须是当前结点左子树的最大结点或者右子树的最小结点,交换值后,释放替代的结点资源。(本章节以找右子树最小结点为例)

这里埋下了一个坑,如果删除的是根结点,不能让 parentreplace 开始置为 nullptr,否则会出现 nullptr 解引用的问题。

文章配图

bool Erase(const K& key) { 
    Node* parent = nullptr; 
    Node* cur = _root; 
    while (cur) { 
        if (cur->_key < key) { 
            parent = cur; 
            cur = cur->_right; 
        } else if (cur->_key > key) { 
            parent = cur; 
            cur = cur->_left; 
        } else { 
            // 删除结点操作 
            // 左节点为空 
            if (cur->_left == nullptr) { 
                if (cur == _root) { 
                    _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 (cur == _root) { 
                    _root = cur->_left; 
                } else { 
                    if (parent->_right == cur) { 
                        parent->_right = cur->_left; 
                    } else { 
                        parent->_left = cur->_left; 
                    } 
                } 
                delete cur; 
            } 
            // 有双亲节点 
            else { 
                Node* parentreplace = cur; 
                Node* replace = cur->_right; 
                while (replace->_left) { 
                    parentreplace = replace; 
                    replace = replace->_left; 
                } 
                swap(replace->_key, cur->_key); 
                if (parentreplace->_left == replace) { 
                    parentreplace->_left = replace->_right; 
                } else { 
                    parentreplace->_right = replace->_right; 
                } 
                delete replace; 
            } 
            return true; 
        } 
    } 
    return false; 
}

key/value

使用场景

在现实生活中会遇到很多 key/value 场景。

如:1.简单中英互译字典,树的结构中 (结点) 存储 key(英⽂) 和 vlaue(中⽂),搜索时输⼊英⽂,则同时 查找到了英⽂对应的中⽂。

2.统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。

代码实现

其实 key/value 的实现和 key 时类似的,需要注意的是 key/value 场景下的 value 是可以冗余的,因为是以 key 为主体进行的。(注意:std 库中 key 和 value 放在了 pair 的结构体当中,是为了达到返回多个值的目的)

template<class K,class V> struct BSTreeNode { 
    K _key; 
    V _value; 
    BSTreeNode<K,V>* _left; 
    BSTreeNode<K,V>* _right; 
    BSTreeNode(const K& key,const V& value) :_key(key) ,_value(value) , _left(nullptr) , _right(nullptr) {} 
}; 
template<class K,class V> class BSTree { 
    typedef BSTreeNode<K,V> Node; 
public: 
    bool Insert(const K& key,const V& value) { 
        // 树为空的情况 
        if (_root == nullptr) { 
            _root = new Node(key,value); 
            return true; 
        } 
        Node* parent = nullptr; 
        Node* cur = _root; 
        while (cur) { 
            if (cur->_key < key) { 
                parent = cur; 
                cur = cur->_right; 
            } else if (cur->_key > key) { 
                parent = cur; 
                cur = cur->_left; 
            } else { 
                return false; 
            } 
        } 
        cur = new Node(key,value); 
        if (parent->_key > key) { 
            parent->_left = cur; 
        } else { 
            parent->_right = cur; 
        } 
        return true; 
    } 
    BSTreeNode<K,V>* Find(const K& key) { 
        Node* cur = _root; 
        while (cur) { 
            if (cur->_key < key) { 
                cur = cur->_right; 
            } else if (cur->_key > key) { 
                cur = cur->_left; 
            } else { 
                return cur; 
            } 
        } 
        return nullptr; 
    } 
    bool Erase(const K& key) { 
        Node* parent = nullptr; 
        Node* cur = _root; 
        while (cur) { 
            if (cur->_key < key) { 
                parent = cur; 
                cur = cur->_right; 
            } else if (cur->_key > key) { 
                parent = cur; 
                cur = cur->_left; 
            } else { 
                // 删除结点操作 
                // 左节点为空 
                if (cur->_left == nullptr) { 
                    if (cur == _root) { 
                        _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 (cur == _root) { 
                        _root = cur->_left; 
                    } else { 
                        if (parent->_right == cur) { 
                            parent->_right = cur->_left; 
                        } else { 
                            parent->_left = cur->_left; 
                        } 
                    } 
                    delete cur; 
                } 
                // 有双亲节点 
                else { 
                    Node* parentreplace = cur; 
                    Node* replace = cur->_right; 
                    while (replace->_left) { 
                        parentreplace = replace; 
                        replace = replace->_left; 
                    } 
                    swap(replace->_key, cur->_key); 
                    swap(replace->_value, cur->_value); 
                    if (parentreplace->_left == replace) { 
                        parentreplace->_left = replace->_right; 
                    } else { 
                        parentreplace->_right = replace->_right; 
                    } 
                    delete replace; 
                } 
                return true; 
            } 
        } 
        return false; 
    } 
    // 析构函数 
    ~BSTree() { 
        Destroy(_root); 
        _root = nullptr; 
    } 
    void Destroy(Node* root) { 
        if (root == nullptr) { 
            return; 
        } 
        Destroy(root->_left); 
        Destroy(root->_right); 
        delete root; 
    } 
private: 
    Node* _root = nullptr; 
};
异构计算:Zynq FPGA+Linux UIO 硬实时加速实践
  • 接入第三方 OpenAI 兼容模型到 GitHub Copilot
  • Flutter Genkit 组件适配鸿蒙系统:AI 流式响应与提示词工程
  • Python 爬虫结合比迪丽 AI 绘画模型自动化采集艺术素材
  • 深入理解 IDE 中 AI 大模型的 Session 机制与管理策略
  • Trae AI IDE 完全上手指南:从安装到熟练应用
  • VS Code 插件推荐:AI 辅助编码与代码注释优化
  • Python AI 大模型部署指南:本地运行、API 服务及 Docker 封装
  • 基于 Rokid 灵珠平台构建 AI Glasses 作业辅导助手
  • Claude-Code 2.1.88 源码结构深度解析:基于 Source Map 还原
  • OpenClaw 厂商全对比:主流 AI 智能体平台深度横评
  • Spring MVC 快速入门(下篇):响应处理与报文设置
  • 人工智能、机器学习与深度学习的概念辨析