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

C++ 二叉搜索树详解:核心原理与完整实现

本文深入解析 C++ 中二叉搜索树的定义、性能分析及核心操作。涵盖插入、删除、查找的中序遍历逻辑,重点剖析删除节点时的复杂情况处理。通过 Key 及 Key-Value 两种结构实现,展示其在字典、词频统计等实际场景中的应用价值,并对比平衡树优化必要性。

ArchDesign发布于 2026/3/27更新于 2026/10/2482 浏览
C++ 二叉搜索树详解:核心原理与完整实现

C++ 二叉搜索树详解:核心原理与完整实现

一、概念与性能分析

1.1 什么是二叉搜索树

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

  • 若左子树不为空,则左子树上所有节点的值均小于或等于根节点的值。
  • 若右子树不为空,则右子树上所有节点的值均大于或等于根节点的值。
  • 左右子树本身也必须是二叉搜索树。

关于相等值的处理,取决于具体场景定义。例如 std::map/std::set 底层不支持插入相等值,而 multimap/multiset 支持。我们在实现时通常默认不支持重复键,但逻辑上可调整。

1.2 性能分析

由于中序遍历二叉搜索树会得到一个升序序列,因此它也被称为二叉排序树。

时间复杂度:

  • 最优情况:树接近完全二叉树,高度为 $O(\log_2 N)$。此时增删查效率较高。
  • 最差情况:树退化为单支树(类似链表),高度为 $O(N)$。此时效率等同于线性查找。

综合而言,普通二叉搜索树的平均时间复杂度为 $O(\log_2 N)$,最坏为 $O(N)$。当数据量较大且分布不均时,性能可能无法满足需求,这时就需要引入平衡二叉搜索树(如 AVL 树、红黑树)来优化高度。

虽然二分查找也能达到 $O(\log_2 N)$ 的查找效率,但它要求数据存储在支持随机访问的有序结构中,且插入删除需要大量移动数据。相比之下,二叉搜索树在动态增删操作上更具优势。

二、核心功能实现

这里我们实现一个通用的二叉搜索树类,包含基本结构、拷贝构造、赋值重载、析构以及增删查操作。注意:修改 Key 会破坏树的结构,因此本实现仅支持增删查,不支持直接修改 Key。

2.1 基础结构与内存管理

首先定义节点结构,使用 struct 方便默认公有访问。我们需要手动管理资源,因此必须实现深拷贝和析构逻辑。

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

    BSTreeNode(const K& key)
        : _left(nullptr), _right(nullptr), _key(key) {}
};

template<class K>
class BSTree {
    typedef BSTreeNode<K> Node;
public:
    // 默认构造
    BSTree() = default;

    // 拷贝构造
    BSTree(const BSTree& other) {
        _root = (other._root);
    }

    
    BSTree& =( BSTree& other) {
         ( != &other) {
            (_root);
            _root = (other._root);
        }
         *;
    }

    
    ~() {
        (_root);
    }

:
    
    {
         (!root)  ;
        Node* newNode =  (root->_key);
        newNode->_left = (root->_left);
        newNode->_right = (root->_right);
         newNode;
    }

    
    {
         (!root) ;
        (root->_left);
        (root->_right);
         root;
    }

:
    Node* _root = ;
};
Copy
// 赋值重载
operator
const
if
this
Destroy
Copy
return
this
// 析构函数
BSTree
Destroy
private
// 辅助函数:递归复制
Node* Copy(Node* root)
if
return
nullptr
new
Node
Copy
Copy
return
// 辅助函数:递归销毁
void Destroy(Node* root)
if
return
Destroy
Destroy
delete
private
nullptr

2.2 插入操作

插入逻辑遵循 BST 性质:比当前节点大往右走,小往左走,直到找到空位。

bool Insert(const K& key) {
    if (_root == nullptr) {
        _root = new Node(key);
        return true;
    }

    Node* cur = _root;
    Node* parent = nullptr;
    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);
    if (parent->_key < key) {
        parent->_right = cur;
    } else {
        parent->_left = cur;
    }
    return true;
}

2.3 查找操作

利用 BST 的有序性,从根节点开始比较,路径唯一。

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;
}

2.4 删除操作(难点)

删除是最复杂的操作,需分情况讨论:

  1. 无左孩子:用右孩子替代。
  2. 无右孩子:用左孩子替代。
  3. 左右孩子均存在:无法直接删除,需找右子树中的最小节点(或左子树最大节点)替换当前节点值,然后递归删除那个最小节点。
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 {
            // 找到目标节点,准备删除
            // 情况 1:左孩子为空
            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;
            }
            // 情况 2:右孩子为空
            else if (cur->_right == nullptr) {
                if (cur == _root) {
                    _root = cur->_left;
                } else {
                    if (parent->_left == cur) {
                        parent->_left = cur->_left;
                    } else {
                        parent->_right = cur->_left;
                    }
                }
                delete cur;
            }
            // 情况 3:左右孩子均存在
            else {
                Node* pMinRight = cur;
                Node* minRight = cur->_right;
                // 找到右子树中最小的节点
                while (minRight->_left) {
                    pMinRight = minRight;
                    minRight = minRight->_left;
                }
                // 交换值
                std::swap(cur->_key, minRight->_key);
                // 删除原来的最小节点(此时它一定没有左孩子)
                if (pMinRight->_left == minRight) {
                    pMinRight->_left = minRight->_right;
                } else {
                    pMinRight->_right = minRight->_right;
                }
                delete minRight;
            }
            return true;
        }
    }
    return false;
}

2.5 中序遍历

验证 BST 性质的最佳方式是中序遍历,结果应为升序。

void InOrder() {
    _InOrder(_root);
    std::cout << std::endl;
}

private:
void _InOrder(Node* root) {
    if (!root) return;
    _InOrder(root->_left);
    std::cout << root->_key << " ";
    _InOrder(root->_right);
}

三、Key-Value 结构扩展

在实际应用中,我们往往需要存储键值对。只需将节点中的 K _key 改为 K _key; V _value;,其余逻辑保持不变。查找、插入、删除依然以 key 为准,但返回或存储的是对应的 value。

namespace key_value {
template<class K, class V>
struct BSTreeNode {
    BSTreeNode<K, V>* _left;
    BSTreeNode<K, V>* _right;
    K _key;
    V _value;

    BSTreeNode(const K& key, const V& value)
        : _left(nullptr), _right(nullptr), _key(key), _value(value) {}
};

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* cur = _root;
        Node* parent = nullptr;
        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->_right = cur;
        } else {
            parent->_left = cur;
        }
        return true;
    }

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

    // 删除逻辑与 Key 版本一致,此处省略重复代码
    // ... 参考上述 Erase 实现,仅类型变为 K, V

    void InOrder() {
        _InOrder(_root);
        std::cout << std::endl;
    }

private:
    void _InOrder(Node* root) {
        if (!root) return;
        _InOrder(root->_left);
        std::cout << root->_key << ":" << root->_value << std::endl;
        _InOrder(root->_right);
    }

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

    Node* Copy(Node* root) {
        if (!root) return nullptr;
        Node* copy = new Node(root->_key, root->_value);
        copy->_left = Copy(root->_left);
        copy->_right = Copy(root->_right);
        return copy;
    }

private:
    Node* _root = nullptr;
};
}

四、典型应用场景

1. Key 模式

  • 车牌识别系统:录入合法车牌号,车辆进入时扫描比对,存在则放行。
  • 拼写检查:将词库存入 BST,读取文章单词时快速判断是否存在。

2. Key-Value 模式

  • 翻译字典:Key 为英文,Value 为中文,输入即查。
  • 停车场计费:Key 为车牌,Value 为入场时间,出场时计算时长并计费。
  • 词频统计:Key 为单词,Value 为出现次数,遇到新单词初始化计数,已存在则累加。

二叉搜索树是数据结构中的基石之一。理解其增删查的逻辑细节,尤其是删除时的节点替换策略,对于掌握更高级的平衡树至关重要。实际开发中,建议优先使用标准库容器(如 std::map),但在面试或底层学习时,手写实现能帮你深入理解内存管理与指针操作。

目录

  1. C++ 二叉搜索树详解:核心原理与完整实现
  2. 一、概念与性能分析
  3. 1.1 什么是二叉搜索树
  4. 1.2 性能分析
  5. 二、核心功能实现
  6. 2.1 基础结构与内存管理
  7. 2.2 插入操作
  8. 2.3 查找操作
  9. 2.4 删除操作(难点)
  10. 2.5 中序遍历
  11. 三、Key-Value 结构扩展
  12. 四、典型应用场景
  13. 1. Key 模式
  14. 2. Key-Value 模式

更多推荐文章

查看全部
  • Token 管理实践:前端登录凭证的获取、存储与过期处理
  • Arrow 游戏叙事设计工具:基于 Godot 4 的可视化剧情编辑器
  • Midjourney 官网地址及中文支持情况说明
  • AI 智能体辅助 C 语言与前端实训项目生成实战
  • 基于 Git 和 Termux 实现 Obsidian 平板多端同步方案
  • 基于 YOLO 的纺织品缺陷检测系统:Web 端集成与模型训练实战
  • Microsoft Visual C++ 运行库安装与 DLL 缺失修复指南
  • Llama 3.3 70B 模型部署输出异常排查与优化指南
  • 探索 Ovis 多模态大模型量化实战指南
  • 当养老遇上 AI 大模型:ATEC 2023 科技助老赛题解析
  • C++ 类和对象(二):默认成员函数详解
  • 机器人 3D 位姿详解:5 种旋转表示法与机器学习应用
  • C++驱动 spidev0.0 时 read 函数返回 255 的硬件电平分析
  • AI Agent 实战:生产级框架搭建与核心实现
  • JDK 23 本地开发环境搭建指南
  • Python 工程师常见基础面试题
  • 小鹏 VLA 2.0 与 IRON 人形机器人技术解析
  • Linux C++ 多线程编程入门:从核心概念到常用函数详解
  • AMD 显卡部署 llama.cpp 高性能配置实战
  • Flash 存储磨损均衡算法详解

相关免费在线工具

  • 加密/解密文本

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