【C++】二叉搜索树 - 从基础概念到代码实现

article/2025/8/15 5:58:54

📌 个人主页: 孙同学_
🔧 文章专栏:C++
💡 关注我,分享经验,助你少走弯路

在这里插入图片描述

文章目录

    • 1. 二叉搜索树的概念
    • 2. 二叉搜索树的性能分析
    • 3. 二叉搜索树的插入
    • 4. 二叉搜素树的查找
    • 5. 二叉搜索树的删除
    • 6.二叉搜索树的代码实现
    • 7. 二叉搜索树的key和key/value对的使用场景
      • 7.1 key搜索场景
      • 7.2 key/value对的场景
      • 7.3 核心区别总结
      • 7.4 如何选择?
      • 7.5 key/value对的代码实现

1. 二叉搜索树的概念

二叉搜索树又称二叉排序树,它要么是一棵空树,要么是具有一下性质的二叉树

  • 若它的左子树不为空,则左子树上所有节点的值都小于根结点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根结点的值
  • 它的左右子树分别为二叉搜索树
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体要看使用场景的定义,map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值

在这里插入图片描述

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

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为:log2 N
最差情况下,二叉搜索树退化为单指树,其高度为:N
所以综合而言二叉搜索树增删查改的时间复杂度为:O(N)
在这里插入图片描述

这样的效率显然是无法满足我们的需求的,后面的平衡二叉搜索树AVL树和红黑树才能适用于才内存中存储和搜索数据。

  • 二分查找也能实现O(log2N)级别的查找效率,但是二分查找有两大缺陷:
  1. 需要存储在支持下标随机访问的结构中,并且有序
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除需要挪动数据。

3. 二叉搜索树的插入

插入的具体过程如下:

  1. 树为空,则直接新增结点,赋值给root指针。
  2. 树不为空,则按照二叉搜索树的性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置插入新结点。
  3. 如果支持插入相等的值,插入值和当前结点的值相等,可以往左走也可以往右走,找到空位置,插入新结点。(注意:插入时要保证逻辑的一致性,不要一次往左走,一次往右走)

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

在这里插入图片描述
在这里插入图片描述

4. 二叉搜素树的查找

  1. 从根结点开始比较,查找x,x比根结点的值小则往左走,x比根结点的值大则往右走。
  2. 最多查找高度次,走到为空,还没有找到,则这个值不存在。
  3. 不支持插入相等的值,找到x,即可返回。
  4. 如果支持插入相等的值,意味着有多个x存在,一般要求查找中序的第一个x,如下图,查找3,要
    找到1的右孩子的那个3返回。
    在这里插入图片描述

5. 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回false
如果查找的元素存在,则分一下四种情况进行处理:(假设要删除的结点为N)

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

上述以上四种情况的解决方案:

  1. N结点的父亲对应的孩子指针指向空,直接删除N结点(情况1可以当成情况2或3处理,效果都是一样的)
  2. N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点
    在这里插入图片描述
  3. N结点的父亲对应的孩子指针指向N的左孩子,直接删除N结点在这里插入图片描述
  4. 无法直接删除N结点,因为N的两个孩子无处安放,只能用替代法删除。找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)代替N,因为这两个结点的任意一个,放到N的位置都满足二叉搜索树的规则。替代N的意思就是NR两个结点的值进行交换,转而变成删除R结点,R结点符合情况2或者情况3,可以直接删除。

在这里插入图片描述

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

template<class K>
struct BSTNode//定义搜索二叉树的结点
{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}
};//class SearchBinaryTree
template<class K>
class BSTree
{typedef BSTNode<K> Node;
public:bool Insert(const K& key){if (_root == nullptr) //如果根节点为空{_root = new Node(key);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);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool Find(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{return true;}}return false;}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->_right){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}else if (cur->_right == nullptr) //右为空,父亲指向我的左{if (cur == _root){_root = cur->_left;}else{if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}else //左右都不为空{//找右子树的最小结点(最左结点)替代我的位置Node* minRightParent = cur;Node* minRight = cur->_right;while (minRight->_left){minRightParent = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;if (minRightParent->_left == minRight){minRightParent->_left = minRight->_right;}else{minRightParent->_right = minRight->_right;}delete minRight;}return true;}}return false;}void InOrder() //外面不能访问私有,但是里面可以{_InOrder(_root);std::cout << std::endl;}private://中序遍历void _InOrder(Node* root) //这里有个问题就是我在外面想传这个根结点,但是根节点是私有的,所以有两种解决方案1.写一个GetRoot2.我们再套一层{if (root == nullptr){return;}_InOrder(root->_left);std::cout << root->_key << " ";_InOrder(root->_right);}Node* _root = nullptr;};

7. 二叉搜索树的key和key/value对的使用场景

7.1 key搜索场景

当数据结构的核心需求是快速判断元素是否存在、维护有序性,或者不需要关联额外数据,仅使用key即可。

典型场景:

  • 集合(Set)的实现: 例如std::set
  • 存在性检查: 如检测某个用户名是否已经被注册。
  • 有序遍历: 直接按key的顺序输出元素(如从下到大遍历)。
  • 去重操作: 自动过滤重复的key,保证唯一性。

7.2 key/value对的场景

当需要通过key关联并管理额外数据时,使用key/value对。此时BST类似于一个有序的字典(Map),例如std::map

典型场景:

  • 字典/映射结构: 存储值对,如学号(key)关联学生信息(value)。
  • 词频统计: 单词(key)关联出现次数(value)。
  • 缓存系统: 通过key快速检索缓存的值。
  • 数据库索引: 通过key(如主键)快速定位记录。

7.3 核心区别总结

特性仅 KeyKey/Value 对
数据结构目的维护有序的唯一元素集合建立键到值的映射关系
典型操作插入、删除、存在性检查插入、删除、按 key 查 value
应用举例用户名的唯一性校验学号关联学生详细信息
库实现TreeSet(Java)TreeMap(Java)

7.4 如何选择?

  • 仅需 key:如果问题仅涉及元素的存在性、有序性或去重。
  • 需要 key/value:如果问题需要通过 key 快速访问或修改关联的复杂数据。

7.5 key/value对的代码实现

template<class K,class V>
struct BSTNode//定义key/value对的结点
{K _key;V _value;BSTNode<K,V>* _left;BSTNode<K,V>* _right;BSTNode(const K& key,const V& value):_key(key),_value(value), _left(nullptr), _right(nullptr){}
};
//class SearchBinaryTree
template<class K,class V>
class BSTree
{typedef BSTNode<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->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(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{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 (cur == parent->_right){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}else if (cur->_right == nullptr) //右为空,父亲指向我的左{if (cur == _root){_root = cur->_left;}else{if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}else //左右都不为空{//找右子树的最小结点(最左结点)替代我的位置Node* minRightParent = cur;Node* minRight = cur->_right;while (minRight->_left){minRightParent = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;if (minRightParent->_left == minRight){minRightParent->_left = minRight->_right;}else{minRightParent->_right = minRight->_right;}delete minRight;}return true;}}return false;}void InOrder() //外面不能访问私有,但是里面可以{_InOrder(_root);std::cout << std::endl;}private://中序遍历void _InOrder(Node* root) //这里有个问题就是我在外面想传这个根结点,但是根节点是私有的,所以有两种解决方案1.写一个GetRoot2.我们再套一层{if (root == nullptr){return;}_InOrder(root->_left);std::cout << root->_key << " " << root->_value << std::endl;;_InOrder(root->_right);}Node* _root = nullptr;
};
int main()
{//1.查找单词BSTree<string, string> dict;//BSTree<string, string> copy = dict;dict.Insert("left", "左边");dict.Insert("right", "右边");dict.Insert("insert", "插入");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << "->" << ret->_value << endl;}else{cout << "无此单词,请重新输入" << endl;}}//2.统计出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (auto& e : arr){//先查找水果在不在搜索树中//不在,则第一次出现,插入到搜索树中//在,则只需要++查找的水果的次数BSTNode<string, int>* ret = countTree.Find(e);if (ret == nullptr){countTree.Insert(e, 1);}else{ret->_value++;}}countTree.InOrder();return 0;}

👍 如果对你有帮助,欢迎:

  • 点赞 ⭐️
  • 收藏 📌
  • 关注 🔔

http://www.hkcw.cn/article/qXGJwAcTlX.shtml

相关文章

C++之类和对象基础

⾯向对象三⼤特性&#xff1a;封装、继承、多态 类和对象 一.类的定义1. 类的定义格式2.类域 二.实例化1.对象2.对象的大小 三.this指针 在 C 的世界里&#xff0c;类和对象构成了面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;的核心框架&…

报错java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not ...解决方法

在运行项目时出现java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualidzz这样的报错 解决方法 1.第一步&#xff1a;在pom文件中将lombok的版本改成最新的 此时1.18.34是新…

2025-03-12 Python深度学习1——安装Anaconda与PyTorch库

文章目录 1 配置 Anaconda1.1 下载1.2 安装1.3 配置环境变量1.4 检查安装 2 安装 PyTorch 库2.1 创建 DL 环境2.2 安装/升级 CUDA2.3 配置环境变量2.4 安装 Pytorch 库方法一&#xff08;不稳定&#xff09;方法二&#xff08;推荐&#xff09; 2.5 检查安装 3 Pycharm Communi…

C++ 关联式容器:map,multimap,set,multiset

目录 引言 一、关联式容器概述 1.1 与序列式容器的区别 1.2 底层结构 二、set容器详解set介绍 2.1 set的特性 2.2 set的模板参数 2.3 set的常用接口 2.4 set使用示例 三、map容器详解map介绍 3.1 map的特性 3.2 map的模板参数 3.3 map的常用接口 3.4 map使用示例 …

从零开始配置Qt+VsCode环境

从零开始配置QtVsCode环境 文章目录 从零开始配置QtVsCode环境写在前面扩展安装及配置Qt Configure配置 VsCode创建Qt工程VsCodeQMakeMinGwVsCodeQMakeMsvcVsCodeCMakeMinGwVsCodeCMakeMsvcQtCreatorQMakeMinGw->VsCodeQtCreatorQMakeMsvc->VsCodeQtCreatorCMakeMinGw-&g…

Matlab/Simulink - BLDC直流无刷电机仿真基础教程(一) - 三相逆变器的搭建

Matlab/Simulink - BLDC直流无刷电机仿真基础教程&#xff08;一&#xff09; - 三相逆变器的搭建 前言一、BLDC电机六步换相简明控制原理二、Simulink中BLDC电机模块的机械连接三、三相逆变电路的搭建四、仿真参数设置与仿真结果验证五、补充内容参考链接 前言 本系列文章分享…

Lapce:一款用 Rust 编写的快速且强大的代码编辑器

Lapce&#xff08;IPA&#xff1a;/lps/&#xff09;是一个使用纯 Rust 编写的开源代码编辑器。通过利用 OpenGL 渲染 GUI&#xff0c;以及 Rust 提供的性能&#xff0c;采用Xi-Editor的Rope Science设计&#xff0c;可实现闪电般的快速计算。 Stars 数35888Forks 数1113 主要…

SpringBoot启动后初始化的几种方式

目录 一、静态代码块 二、构造方法 三、PostConstruct 四、InitializingBean 接口 五、 Bean 注解中的 initMethod 六、 CommandLineRunner 接口 七、ApplicationRunner 接口 八、EventListener事件 九、SmartInitializingSingleton接口 十、ApplicationListener接口…

【MySQL课程学习】:MySQL安装,MySQL如何登录和退出?MySQL的简单配置

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;MySQL课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 MySQL在Centos 7环境下的安装&#xff1a; 卸载…

Node.js下载安装及环境配置教程(保姆级教程)

一、安装程序 &#xff08;安装包放在文章最后需要的友友可自取哦&#xff09; &#xff08;1&#xff09;下载完成后&#xff0c;双击安装包&#xff0c;开始安装Node.js &#xff08;2&#xff09;此位置可修改为自己的安装路径&#xff0c;修改完后点击next &#xff08;3…

com.mysql.cj.jdbc.exceptions.CommunicationsException Communications link failure 问题解决

前言: 一般这个报错大多是网络原因导致的&#xff0c;确保你不是网络问题再往下看 问题 在一个方法上&#xff08;该方法非常复杂执行时间长&#xff09;加了 Transactional(rollbackFor Exception.class)后出现了如下图所示的错误 解决&#xff1a; 经过排查并非网络问…

【解决方案】CloudFront VPC Origins 实践流程深入解析 —— 安全高效架构的实战之道

目录 引言一、VPC Origins 的核心价值&#xff08;一&#xff09;安全性提升&#xff08;二&#xff09;运维效率优化&#xff08;三&#xff09;成本节约&#xff08;四&#xff09;全球分发能力的保留 二、VPC Origins 的架构解析&#xff08;一&#xff09;流量路径设计&…

MySQL性能调优(三):MySQL中的系统库(sys系统库、information_schema)

文章目录 MySQL性能调优数据库设计优化查询优化配置参数调整硬件优化 MySQL中的系统库1.3.sys系统库1.3.1.sys使用须知1.3.2.sys系统库使用1.3.3.查看慢SQL语句慢在哪里1.3.4.小结 1.4.information_schema1.4.1.什么是information_schema1.4.2.information_schema表分类Server层…

MySQL的详细使用教程

目录 1. 连接到MySQL服务器2. 创建和删除数据库2-1创建数据库2-2删除数据库 3. 数据表操作3.1 选择数据库3.2 创建数据表3.3 查询数据表3.4 修改数据表3.5 删除数据表 4. 数据内容操作4.1数据操作1. 插入数据2. 查询数据&#xff08;1&#xff09;like模糊查询&#xff08;%表示…

IDEA编写SpringBoot项目时使用Lombok报错“找不到符号”的原因和解决

目录 概述|背景 报错解析 解决方法 IDEA配置解决 Pom配置插件解决 概述|背景 报错发生背景&#xff1a;在SpringBoot项目中引入Lombok依赖并使用后出现"找不到符号"的问题。 本文讨论在上述背景下发生的报错原因和解决办法&#xff0c;如果仅为了解决BUG不论原…

【中间件】Pulsar集群安装

目录 一、Pulsar介绍 1.1 Pulsar基本介绍 1.2 Pulsar架构 Producer & Consumer Apache Zookeeper Pulsar Brokers Apache Bookkeeper 二、Zookeeper集群安装 三、Pulsar集群安装 3.1 bookie与broker配置 3.1.1 修改bookie配置文件 3.1.2 修改broker配置文件 3…

41-dify案例分享-基于database插件实现Text2sql的数据库查询图表工作流

1 前言 Text2SQL&#xff08;或称NL2SQL&#xff09;是一种自然语言处理技术&#xff0c;旨在将自然语言&#xff08;Natural Language&#xff09;问题转化为关系型数据库中可执行的结构化查询语言&#xff08;Structured Query Language&#xff0c;SQL&#xff09;&#xf…

数据库-MySQL 实战项目——学生选课系统数据库设计与实现(附源码)

一、前言 该项目非常适合MySQL入门学习的小伙伴&#xff0c;博主提供了源码、数据和一些查询语句&#xff0c;供大家学习和参考&#xff0c;代码和表设计有什么不恰当还请各位大佬多多指点。 所需环境 MySQL可视化工具&#xff1a;navicat&#xff1b; 数据库&#xff1a;MySq…

中小型企业大数据平台全栈搭建:Hive+HDFS+YARN+Hue+ZooKeeper+MySQL+Sqoop+Azkaban 保姆级配置指南

目录 背景‌一、环境规划与依赖准备‌1. 服务器规划(3节点集群)2. 系统与依赖‌3. Hadoop生态组件版本与下载路径4. 架构图二、Hadoop(HDFS+YARN)安装与配置‌1. 下载与解压(所有节点)2. HDFS高可用配置3. YARN资源配置‌4. 启动Hadoop集群三、MySQL安装与Hive元数据配置…

从 0~1 保姆级 详细版 PostgreSQL 数据库安装教程

PostgreSQL数据库安装 PostgreSQL官网 【PostgreSQL官网】 | 【PostgreSQL安装官网_Windows】 安装步骤 step1&#xff1a; 选择与电脑相对应的PostgreSQL版本进行下载。 step2&#xff1a; 双击打开刚才下载好的文件。 step3&#xff1a; 在弹出的setup窗口中点击 …