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

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

二叉搜索树(BST)是一种高效的查找结构,其左子树节点值小于根节点,右子树大于等于根节点。中序遍历可得升序序列。平均时间复杂度为 O(logN),最坏退化为 O(N)。本文详细解析了 BST 的概念、性能分析,并提供了基于 C++ 的完整增删查改代码实现,包括拷贝构造、析构及平衡性讨论,同时对比了 Key 与 Key-Value 两种应用场景。

宁静发布于 2026/3/21更新于 2026/10/2494 浏览
C++ 二叉搜索树原理与实现

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

概念与性能分析

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

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

关于相等值的处理,具体取决于使用场景。例如 std::map/std::set 不支持插入相等值,而 multimap/multiset 支持。这完全由底层实现定义。

性能表现

由于中序遍历的结果一定是升序序列,BST 非常适合用于查找和排序。其时间复杂度高度依赖于树的形态:

  • 最优情况:树接近完全二叉树,高度为 $\log_2 N$,操作效率为 $O(\log N)$。
  • 最差情况:树退化为单支树(类似链表),高度为 $N$,操作效率降为 $O(N)$。

这种不稳定性促使我们引入平衡二叉搜索树(如 AVL 树、红黑树)。虽然二分查找也能达到 $O(\log N)$,但它要求数据存储在支持随机访问的有序结构中,且插入删除需要大量移动数据。相比之下,平衡二叉搜索树在动态增删改查方面更具优势。

基本结构实现

节点定义

为了方便访问成员,通常使用 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<K>& t) {
        _root = Copy(t._root);
    }

    // 赋值重载
    BSTree<K>& operator=(const BSTree<K> t) {
        swap(_root, t._root);
        return *this;
    }

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

private:
    // 深拷贝辅助函数
    Node* Copy(Node* root) {
        if (root == nullptr) return nullptr;
        Node* copy = new Node(root->_key); // 注意:必须新开辟内存
        copy->_left = Copy(root->_left);
        copy->_right = Copy(root->_right);
        return copy;
    }

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

private:
    Node* _root = nullptr;
};

核心操作实现

插入操作

插入逻辑遵循 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;
}

查找操作

从根节点开始比较,路径唯一,最多遍历树的高度次。

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

删除操作

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

  1. 无孩子或仅一个孩子:直接将父节点的指针指向孩子的另一侧(或直接指向 null)。
  2. 有两个孩子:不能直接删除,否则无法安置两个子树。通常用右子树的最小值(中序后继)或左子树的最大值替换当前节点的值,然后递归删除那个被替换的节点(此时该节点必为情况 1 或 2)。

这里采用交换键值的方法简化指针操作:

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 (cur == parent->_left) {
                        parent->_left = cur->_right;
                    } else {
                        parent->_right = cur->_right;
                    }
                }
                delete cur;
            } else if (cur->_right == nullptr) {
                // 右孩子为空,处理左孩子
                if (cur == _root) {
                    _root = cur->_left;
                } else {
                    if (cur == parent->_left) {
                        parent->_left = cur->_left;
                    } else {
                        parent->_right = cur->_left;
                    }
                }
                delete cur;
            } else {
                // 两个孩子,找右子树最小值替代
                Node* pMinRight = cur;
                Node* minRight = cur->_right;
                while (minRight->_left) {
                    pMinRight = minRight;
                    minRight = minRight->_left;
                }
                // 交换键值
                std::swap(cur->_key, minRight->_key);
                // 删除 minRight 节点(此时它必然没有左孩子)
                if (pMinRight->_left == minRight) {
                    pMinRight->_left = minRight->_right;
                } else {
                    pMinRight->_right = minRight->_right;
                }
                delete minRight;
            }
            return true;
        }
    }
    return false;
}

中序遍历

利用递归特性,先左后根再右,即可得到有序序列。

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

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

Key-Value 结构变体

在实际应用中,往往需要存储键值对(Key-Value)。除了存储 key 外,还需增加 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) {}
};

// 类定义逻辑同上,仅需调整构造函数及打印输出
// ... (省略部分重复代码,逻辑一致)
};

典型应用场景

Key 模式

  • 车牌识别系统:录入业主车牌号,车辆进入时扫描比对,存在则放行。
  • 拼写检查:将词库存入 BST,读取文章单词进行快速查找匹配。

Key-Value 模式

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

以上便是二叉搜索树的核心原理与 C++ 实现细节。理解这些基础结构,对于后续学习更复杂的平衡树(如红黑树)至关重要。

目录

  1. C++ 二叉搜索树原理与实现
  2. 概念与性能分析
  3. 性能表现
  4. 基本结构实现
  5. 节点定义
  6. 类框架与生命周期管理
  7. 核心操作实现
  8. 插入操作
  9. 查找操作
  10. 删除操作
  11. 中序遍历
  12. Key-Value 结构变体
  13. 典型应用场景
  14. Key 模式
  15. Key-Value 模式

更多推荐文章

查看全部
  • Ubuntu 24.04 更换阿里云镜像源指南
  • PostgreSQL 18 默认密码修改与安全加固指南
  • 鸿蒙金融理财全栈项目:运维监控、性能优化与安全加固
  • Clang Power Tools:在 Visual Studio 里做 C++ 静态分析
  • 大模型基本概念详解:定义、发展、特点与分类
  • RAG 实战:利用 Langchain 和 Milvus 构建本地知识库
  • 快速排序核心原理与多版本实现详解
  • C++ 模板编程基础:泛型编程入门与实践
  • 大模型技术从基础入门到实战应用指南
  • 文心一言 4.5 开源版本地化部署实测与性能分析
  • 使用 cpolar 实现 Open-Lovable 远程访问与网页克隆
  • C++ 结构体基础:定义、成员函数与排序
  • ThreadLocal 原理、使用场景及内存泄漏问题详解
  • Git 基础教程:从零理解版本控制与团队协作
  • 基于腾讯云 CVM 部署 Clawdbot 实现 Telegram 智能回复
  • World Monitor:AI 驱动的全球情报仪表盘与态势感知平台
  • 向量数据库:HNSW 算法详解
  • 数值分析:前向误差与后向误差,好算法为何不怕输入错一点
  • Linux 匿名管道通信:原理与代码实现
  • 2019 人工智能发展报告发布,涵盖 13 个子领域与人才分析

相关免费在线工具

  • 加密/解密文本

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