【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现

| 高阶数据结构 | 相关知识点 | 可以通过点击 | 以下链接进行学习 | 一起加油! |
|---|---|---|---|---|
本章是高阶数据结构笔记的第一篇文章,将分享二叉搜索树的进阶概念及其高效实现的相关知识,欢迎大家阅读!


🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux
🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 
文章目录
- 一、二叉搜索树概念
- 二、二叉搜索树的创建
- 2.1 二叉搜索树的基本单位
- 2.2 实现二叉搜索树的基本框架
- 2.3 二叉搜索树的查找
- 2.4 二叉搜索树的插入
- 2.5 二叉搜索树的删除(难点)
- 2.5.1 删除该子树根节点情况分析
- 2.5.2 删除第一、二情况节点
- 2.5.3 第三种情况(替换法)
- 2.6 采用中序遍历二叉搜索树
- 三、改造二叉搜索树,进行实际应用
- 四、二叉搜索树的性能分析
- 六、Binary_Search_Tree.h
一、二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
现阶段二叉搜索树没有重复的数据

二、二叉搜索树的创建
2.1 二叉搜索树的基本单位
template<class K>struct BSTreeNode{BSTreeNode(const K& key = K()):_left(nullptr), _right(nullptr), _key(key){}BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;};
2.2 实现二叉搜索树的基本框架
template<class K>class BSTree{public://类型名字太长,不方便typedef BSTreeNode<K> Node;private:Node* _root = nullptr;};

上面图示以物理结构数组int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13}创建出来的逻辑结构二叉搜索树的数据结构。
2.3 二叉搜索树的查找
二叉搜索树查找步骤:
- 规定一个关键值key
- 从根开始开始比较查找,key比根大则往右边走查找,key比根小则往左边走查找
- 最多查找高度次,走到到空,还没有找到,这值不存在
- 在插入接口中,虽然查找合适位置代码逻辑差不多,但是存在个别逻辑差异,注意识别
bool Find(const K& key)
{Node* cur = _root->_key;while (cur){if (key < cur->_key){cur = cur->_left;}else if(key > cur->_key){cur = cur->_right;}else{return true;}}return false;
}
2.4 二叉搜索树的插入
插入具体过程:
- 树为空,则直接新增节点,赋值给root指针
- 树不为空,按二叉搜索树性质插入位置,插入新节点

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;//这里cur是临时变量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 if (parent->_key > key){parent->_left = cur;}return true;
}
插入具体过程细节处理:
- 需要判断树是否为空树,如果为空,创建节点赋值给_root
- 创建两个指针parent和cur保证节点的连接
- 通过不同比较大小,直到cur找到为空的位置,创建节点
- 该节点需要满足二叉搜索树的特性,需要再次判断,选择连接
2.5 二叉搜索树的删除(难点)
2.5.1 删除该子树根节点情况分析
首先查找元素是否在二叉搜索树中,如果不存在则返回false,否则要删除的节点可能分为下面三种情况。先到需要被删除的节点,这里就不重复实现了。
删除节点情况划分:
- 要删除的节点无孩子节点
- 要删除的节点只有一个孩子节点
- 要删除的节点有左、右孩子节点
2.5.2 删除第一、二情况节点
这里第一种和第一种情况可以归类为同一种情况。无论被删除节点是否有无真实存在的孩子节点,都可以看成要删除的节点只有一个孩子节点,将第一种情况看成第二种情况,被删除节点有空孩子节点。

if (cur->_left == nullptr)
{if (parent->_left == cur){parent->_left = cur->_right;}else if (parent->_right == cur){parent->_right = cur->_right;}delete cur;
}
else if (cur->_right == nullptr)
{if (parent->_left == cur){parent->_right = cur->_left;}else if (parent->_right == cur){parent->_left = cur->_left;}delete cur;
}

关于数据结构学习,我们需要借助具体的逻辑结构去实现"抽象"的物理结构。接下我也希望你们可以借助图和文字进行对代码的解读。
- 第一个判断分支决定,parent指向另外一个可能为空的节点。

- 第二个分支判断被删除节点相对parent节点的位置
判断结束后,parent节点进行连接操作进行删除操作。
- 小总结:判断被删除节点位置与被删除节点可能不为空孩子位置,进行连接即可。
草稿说明,上面是优化版本说明:
有了上面两个信息的话,比如通过
parent->_left == cur需要被删除的节点是左节点,并且cur->_left == nullptr该节点左孩子节点为空,那么parent->_left = cur->_right;。parent->_left是根据第一个条件,该parent->_left需要重新连接新节点,那么新节点是谁?通过cur->_left == nullptr判断,该左孩子为空,肯定连接右孩子节点。
2.5.3 第三种情况(替换法)
使用替换法删除,简单回顾
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值

被替换的节点需要满足左子树最大节点或者右字数的最小节点其中之一即可。比如满足左子树最大节点,进行交换,该节点满足比左子树都要大,比右子树都要小。
替换法删除的具体流程:
- 先找到需要被删除节点和被替换节点,进行swap交换数字
- 通过第一、二种情况进行删除操作
- 那么需要设置两个指针去需要被替换节点
Node* RightMinParent = cur;
Node* RightMin = cur->_right;//找到右子树最小的值
while (RightMin->_left)
{RightMinParent = RightMin;RightMin = RightMin->_left;
}
//找到
swap(cur->_key, RightMin->_key);if (RightMinParent->_left == RightMin)
{RightMinParent->_left = RightMin->_right;
}
else
{RightMinParent->_right = RightMin->_right;
}
delete RightMin;
}
return true;
实现该逻辑的具体细节:
- 这里我选择找到右子树的最小节点,那么只需要关注左边的情况就行了,这也是为什么是
while (RightMin->_left)- 首先就是第一、二种情况删除的做法
RightMinParent不能设置为空指针当删除根节点就会有问题,直接设置为cur
以上三种情况都需要考虑需要被删除节点为根节点

2.6 采用中序遍历二叉搜索树
void InOrder()
{_InOrder(_root);cout << endl;
}private:
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}
中序遍历能够按顺序输出树中所有节点的值。从根节点开始,中序遍历的顺序是【左子树 , 根节点 , 右子树】这一过程在BST中恰好能保证节点按从小到大的顺序排列。将内部实现放在private部分,可以避免外部代码错误地调用内部方法,导致程序行为不可预测或出错。通过控制访问权限。外部代码不应该直接操作树的节点,而应该通过公开的接口方法来访问和操作树。
三、改造二叉搜索树,进行实际应用
K模型:K模型即只有key作为关键码,结构种只需要存储key即可,关键码即为需要搜索到的值
使用场景:判断单词是否拼写正确
- 将词库中所有单词集合中的每个单词作为key,构建一颗二叉搜索树
- 在二叉搜索树中查找单词是否存在,存在则为正确,否则错误
KV模型:每一个关键码key,都有与之对应的值Value,即
<K,Value>的键值对使用场景:翻译语言(底层主要还是B树)
- 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文
<word, chinese>就构成一种键值对- 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是
<word, count>就构成一种键值对
// 改造二叉搜索树为KV结构
template<class K, class V>struct BSTNode{BSTNode(const K& key = K(), const V& value = V()): _pLeft(nullptr) , _pRight(nullptr), _key(key), _Value(value){}BSTNode<T>* _pLeft;BSTNode<T>* _pRight;K _key;V _value};
template<class K, class V>class BSTree{typedef BSTNode<K, V> Node;typedef Node* PNode;public:BSTree(): _pRoot(nullptr){}PNode Find(const K& key);bool Insert(const K& key, const V& value)bool Erase(const K& key)private:PNode _pRoot;};
void TestBSTree3()
{// 输入单词,查找单词对应的中文翻译BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("left", "左边、剩余");dict.Insert("right", "右边");dict.Insert("sort", "排序");// 插入词库中所有单词string str;while (cin>>str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret == nullptr){cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;}else{cout << str << "中文翻译:" << ret->_value << endl;}}
}
void TestBSTree4()
{// 统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (const auto& str : arr){// 先查找水果在不在搜索树中// 1、不在,说明水果第一次出现,则插入<水果, 1>// 2、在,则查找到的节点中水果对应的次数++//BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}
四、二叉搜索树的性能分析
插入和删除操作都必须先查找的,查找效率代表了二叉搜索树中各个操作的性能
对于对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜

- 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树)
- 最差情况下,二叉搜索树退化为单支树(或者类似单支)
如果退化成单支树,二叉搜索树的性能就失去了 ,我们后续章节学习的AVL树和红黑树就可以上场了
六、Binary_Search_Tree.h
#pragma once
#include <string>template<class K>struct BSTreeNode{BSTreeNode(const K& key = K()):_left(nullptr), _right(nullptr), _key(key){}BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;};template<class K>class BSTree{public:typedef BSTreeNode<K> Node;//插入操作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 if (parent->_key > key){parent->_left = cur;}return true;}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 if (parent->_right == cur){parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_right = cur->_left;}else if (parent->_right == cur){parent->_left = cur->_left;}}delete cur;}//替换法实现else{Node* RightMinParent = cur;Node* RightMin = cur->_right;//找到右子树最大的值while (RightMin->_left){RightMinParent = RightMin;RightMin = RightMin->_left;}//找到swap(cur->_key, RightMin->_key);if (RightMinParent->_left == RightMin){RightMinParent->_left = RightMin->_right;}else{RightMinParent->_right = RightMin->_right;}delete RightMin;}return true;}}return false;}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 false;}}return true;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root = nullptr;};
感谢大家的观看!以上就是本篇文章的全部内容。我是店小二,希望这些高阶数据结构笔记能为你在学习旅途中提供帮助!

相关文章:
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现
高阶数据结构相关知识点可以通过点击以下链接进行学习一起加油! 本章是高阶数据结构笔记的第一篇文章,将分享二叉搜索树的进阶概念及其高效实现的相关知识,欢迎大家阅读! 🌈个人主页:是店小二呀 dz…...
上传本地项目到GitHub远程仓库(极简洁操作版)
第一步:在GitHub创建一个空的仓库 第二步:将仓库克隆(下载)到本地 第三步:将你要上传的所有文件放到这个克隆的仓库文件夹中 第四步:通过git add .将待上传文件添加到暂存区 此时,可以通过git …...
在安卓中使用 `mobile-ffmpeg` 压缩后的视频,浏览器在线播放提示“没有找到支持的视频格式和 MIME 类型”的解决方案
在安卓中使用 mobile-ffmpeg 压缩后的视频,浏览器在线播放提示“没有找到支持的视频格式和 MIME 类型”的解决方案 你可能在安卓开发中使用了 mobile-ffmpeg 进行视频压缩,而当你尝试在浏览器中在线播放压缩后的视频时,看到提示:…...
C语言指针plus版练习
上期我们讲了进阶的指针,本期内容我们来强化一下上期学的内容 一、字符串左旋 实现一个函数,可以左旋字符串中的k个字符。 1.1 分析题目 假设字符串为abcde,左旋一个以后就变成bcdea,就是把第一个字符移到一个新的变量里面&#…...
Kafka 快速入门
目录 介绍 KafKa 相关术语 编辑 Kafka的工作流程 生产者向kafka发送数据的流程 Kafka选择分区的模式 Kafka选择分区的模式 数据消费 kafka的文件存储机制 topic、partition和segment 存储和查找message的过程 数据写入过程 数据查找过程 注意事项 kafka管理UI …...
探索人们最喜爱的AI工具及其应用影响
探索人们最喜爱的AI工具及其应用影响 在科技飞速发展的时代,人工智能(AI)技术正在改变我们的生活和工作方式。越来越多的人开始使用AI工具来提高效率、简化流程和推动创新。那么,在众多的AI工具中,哪些是人们最喜欢的…...
c语言位域详解
一、什么是位域 位域是一种可以让结构体的成员变量以位为单位进行存储和操作的特性。位域允许我们精确控制数据的存储方式,而不像普通的整型变量那样固定使用系统规定的字节大小。 通过位域,我们可以在一个整型数据中指定具体的位数来表示某些信息。比…...
如何修改Spring Boot内置容器默认端口
默认情况下,Spring Boot 应用程序在嵌入式 Tomcat 服务器上启动,并监听默认端口 8080。如果您需要将默认的嵌入式服务器端口更改为其他端口号,可以使用以下几种方法之一: 嵌入式服务器配置命令行参数属性文件 在代码里以编程方式…...
STM32自动下载电路分享及注意事项
文章目录 简介ISP下载启动配置 USB转串口芯片CH340C手动isp下载自动isp下载RTS、DTR电平变化分析注意事项 简介 在嵌入式开发中,使用STM32下载程序,可以通过仿真器下载,也可以通过串口下载。在stm32串口下载时,我们需要手动配置启…...
【深度学习基础模型】极限学习机(Extreme Learning Machines, ELM)详细理解并附实现代码。
【深度学习基础模型】极限学习机(Extreme Learning Machines, ELM)详细理解并附实现代码。 【深度学习基础模型】极限学习机(Extreme Learning Machines, ELM)详细理解并附实现代码。 文章目录 【深度学习基础模型】极限学习机&a…...
把交换机的两个接口连接起来会怎么样?
当把交换机的两个接口连接起来时,可能会产生网络风暴,具体情况如下: 一、形成环路的过程 如果将交换机的两个端口直接连接,就会在网络中形成一个物理环路。例如,假设交换机有端口 A 和端口 B,用一根网线将…...
无人机陆空双模式。
🏆本文收录于《全栈Bug调优(实战版)》专栏,主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&am…...
14. 文档对象模型
打开网页时,浏览器会检索网页的 HTML 文本并对其进行解析,就像第 12 章中的解析器解析程序一样。浏览器会建立一个文档结构模型,并使用该模型在屏幕上绘制页面。这种文档表示法是 JavaScript 程序在沙盒中的玩具之一。它是一种可以读取或修改…...
【计网】【计网】从零开始学习http协议 ---理解http重定向和请求方法
去光荣地受伤, 去勇敢地痊愈自己。 --- 简嫃 《水问》--- 从零开始学习http协议 1 知识回顾2 认识网络重定向3 http请求方法3.1 http常见请求方法3.2 postman工具进行请求3.3 处理GET和POST参数 1 知识回顾 前面两篇文章中我们学习并实现了http协议下的请求与应…...
yolov8/9/10/11模型在中医舌苔分类识别中的应用【代码+数据集+python环境+GUI系统】
yolov8、9、10、11模型在中医舌苔分类识别中的应用【代码数据集python环境GUI系统】 背景意义 目前随着人们生活水平的不断提高,对于中医主张的理念越来越认可,对中医的需求也越来越多。 传统中医的舌诊主要依赖于医生的肉眼观察,仅仅通过这…...
k8s部署安装
k8s部署安装 一 K8s集群环境搭建1.1 k8s中容器的管理方式1.2 k8s集群部署1.2.1 k8s环境部署说明1.2.2 k8s集群环境初始化1.2.2.1 所有节点禁用swap和本地解析1.2.2.2 所有节点安装docker1.2.2.3.所有节点设定docker的资源管理模式为systemd1.2.2.4.所有阶段复制harbor仓库中的证…...
gpt为什么可以依据上下文来回答问题,依据的是什么原理
GPT 可以依据上下文回答问题,主要依据以下几个原理: Transformer 架构: 并行计算与长距离依赖处理:Transformer 架构摒弃了传统的递归神经网络和长短时记忆网络的序列依赖处理方式,具有并行计算的能力。它可以同时处理…...
2023 CCPC哈尔滨 报告
比赛链接:Dashboard - 10.6组队训练赛-2023CCPC哈尔滨站 - Codeforceshttps://codeforces.com/group/w6iGs8kreW/contest/552949 做题数:3 题 三题都是队友写的。所以来补一下 B L J。 B题: B. Memory Little G used to be a participant …...
基于深度学习的手术中的增强现实导航
基于深度学习的手术中的增强现实(AR)导航技术是一种结合了先进的计算机视觉算法、深度学习模型与增强现实技术的创新应用。其主要目的是为外科手术提供实时的、精确的手术指导,帮助医生在复杂的手术过程中更好地理解患者的解剖结构࿰…...
输电线路缺陷图像检测数据集,导线散股,塔材锈蚀两类,分别为581张和1407张,标注为xml和txt格式 1988张
输电线路缺陷图像检测数据集,分为导线散股,塔材锈蚀两类,分别为581张和1407张,标注为xml和txt格式 数据集名称 输电线路缺陷图像检测数据集 (Transmission Line Defect Detection Dataset) 数据集概述 该数据集是一个专门用于训…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
