C++进阶 | [3] 搜索二叉树

摘要:什么是搜索二叉树,实现搜索二叉树(及递归版本)


什么是搜索二叉树

搜索二叉树/二叉排序树/二叉查找树·BST(Binary Search Tree):特征——左小右大(不允许重复值)。即取搜索二叉树中任一结点为根往下看,总满足左子树所有结点的值都小于根的值,右子树所有结点的值都大于根结点的值。

搜索二叉树图例如下:

搜索二叉树-图例

实现搜索二叉树

(不需要 namespace 封装,因为这不是模拟实现,不会和库里产生冲突)

1. 创建结点_Node

class K 的这个 K 是搜索二叉树的一个命名惯例(Convention),即 key(关键词)。

如下这个图例,首先我们一定要明确一个结点的内容包括哪些部分,将这些看作整体,而不是分离的各部分。示例代码如下。

搜索二叉树结点-图例
template<class K>
struct BSTreeNode
{BSTreeNode(const K key = K()):_key(key), _left(nullptr), _right(nullptr){}K _key;BSTreeNode<K>* _left;BSTreeNode<K>* _right;
};

(补充:上面我们解决了结点的创建,对于二叉树的创建,我们只需要用一个根结点的指针 Node* 来实现即可,具体见下面代码BSTree的成员变量。) 

2. 插入数据_Insert

思路:比较大小,按搜索二叉树的排列规则插入。

注意:①结点与新结点之间的链接问题;②注意树为空的情况。

(这部分很简单不多赘述,直接看代码。另外,注意细节,并测试这段代码的准确性后再接着往下写)

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public:bool Insert(const K& key){if (_rootp == nullptr){_rootp = new Node(key);return true;}Node* curp = _rootp;Node* parent_p = curp;while (curp){if (curp->_key > key){parent_p = curp;curp = curp->_left;}else if (curp->_key < key){parent_p = curp;curp = curp->_right;}elsereturn false;}Node* newp = new Node(key);//注意结点之间的链接问题if (parent_p->_key > key){parent_p->_left = newp;}else{parent_p->_right = newp;}return true;}
private:Node* _rootp = nullptr;
}

3. 中序遍历_InOrder

复习一下中序遍历:即 左子树(递归往下拆) 根 右子树(递归往下拆) 的顺序。

注意:这个函数肯定需要传递参数,即某个结点的指针。然后该函数将从这个传递来的参数的结点指针为根结点向下遍历,一般我们需要从整个树的根开始遍历,但是该搜索二叉树的根结点 (_rootp) 私有成员无法访问。这个问题可以通过多种方式解决,我们这里采用“套一层”的方式。具体看代码实现。

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:void _InOrder(Node* rootp){if (rootp == nullptr)return;_InOrder(rootp->_left);std::cout << rootp->_key << " ";_InOrder(rootp->_right);}void InOrder(){return _InOrder(_rootp);//"套一层"👉类内可以随意访问成员}private:Node* _rootp = nullptr;
};

4. 查找结点_Find

根据搜索二叉树的排列特性去找,要找的这个值比当前结点值小就去左子树找,比当前结点大就去右子树找。这个函数实现起来很简单,不多赘述,具体实现可参考下面代码。

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:bool Find(const K& key){if (_rootp == nullptr)return false;Node* curp = _rootp;while (curp){if (curp->_key > key){curp = curp->_left;}else if (curp->_key < key){curp = curp->_right;}elsereturn true;}return false;}private:Node* _rootp = nullptr;
};

5. 删除结点_Erase(难点⭐)

先找到结点,再删除。下面分析删除的思路。ps.以下对于 要被删除的结点 简称为 del.

思路:1.对于没有叶子结点的结点我们可以直接删除;
           2.没有右子树的情况:我们可以让 del 的 parent 结点来接管 del 的 leftchild。同时,我们还需要判断 del 为 parent 的 rightchild 还是 leftchild。图解如下,下图中“断开链接”只是形象的说法,实现的时候只需要将 parent 的 child结点的指针直接覆盖即可。(注意 del 为 整个树的根结点 的情况→我们需要选择一个结点为新的根节点)

           3.没有左子树的情况:同理(同没有右子树的分析)。(注意 要被删除的结点 是 整个树的根结点 的情况)

           4.左右子树都有的情况找左子树的最大结点或者右子树的最小节点与要删除的结点交换。左子树的最大结点即该子树的最右叶子结点,右子树的最小结点即该子树的最左叶子结点。 
如下图,选择 13 或者 9 与 10 交换都可以。
       9 作为左子树中最大的结点,满足①大于左子树所有结点;②小于右子树所有节点。
       13 作为右子树中最小的结点,满足①大于左子树所有结点;②小于右子树所有节点。

如上图所示,图中 the one 即为我们找到的子树中可以与 要被删除的结点 交换的结点(左子树的最大结点或者右子树的最小节点)

明确整体上的思路之后,我们来看具体怎么实现👇 (如有疑惑请参看注释)(使用swap函数记得声明头文件)

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public:bool Erase(const K& key){if (_rootp == nullptr)return false;Node* curp = _rootp;Node* parent_p = nullptr;while (curp){if (curp->_key > key){parent_p = curp;curp = curp->_left;}else if (curp->_key < key){parent_p = curp;curp = curp->_right;}else{Node* del_nodep = curp;//找到了//进行删除//左右子树都没有的情况不需要单独判断,这个情况可以归属于没有 左子树/右子树 的情况if (del_nodep->_left == nullptr)//没有左子树的情况{if (parent_p == nullptr)_rootp = del_nodep->_right;else if (parent_p->_left == del_nodep)parent_p->_left = del_nodep->_right;elseparent_p->_right = del_nodep->_right;delete del_nodep;}else if (del_nodep->_right == nullptr){if (parent_p == nullptr)_rootp = del_nodep->_left;else if (parent_p->_left == del_nodep)parent_p->_left = del_nodep->_left;elseparent_p->_right = del_nodep->_left;delete del_nodep;}else//左右子树都存在的情况{//找到适合替换要被删除的结点的结点。//这里选择del_nodep的右树的最小结点,即右树的最左结点Node* subLeft_p = del_nodep->_right;Node* Left_parent_p = del_nodep;while (subLeft_p->_left){Left_parent_p = subLeft_p;subLeft_p = subLeft_p->_left;}std::swap(subLeft_p->_key, del_nodep->_key);del_nodep = subLeft_p;if (Left_parent_p->_right == del_nodep)Left_parent_p->_right = del_nodep->_right;elseLeft_parent_p->_left = del_nodep->_left;delete del_nodep;}return true;//删除成功}}return false;}
private:Node* _rootp = nullptr;
};

说明:增删查改的“改”

搜索二叉树由于本身的特性是不可以在结点原本的数值上进行修改的。否则可能会破坏搜索二叉树。因此没有修改的相关函数。

6. 递归版_recursion

下面我们用递归的方式来实现“增删查”。(函数名后带‘R’,用于区分递归版本与非递归版本)

递归实际上是通过参数的改变来达到“向下递归”的效果的,实现递归版本肯定要传递结点的指针,这里我们统一通过“套一层”的方式解决。如下代码。

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:bool FindR(const K& key){return _FindR(_rootp, key);}bool InsertR(const K& key){return _InsertR(_rootp, key);}bool EraseR(const K& key){return _EraseR(_rootp, key);}
private:bool _FindR(Node* rootp, const K& key){//}bool _InsertR(Node*& rootp, const K& key){//}bool _EraseR(Node*& rootp, const K& key){//}
private:Node* _rootp = nullptr;
};

1)_FindR

思路:根据搜索二叉树的特性。首先判断当前结点是不是要找的结点,如果不是,若比这个要找的结点比这个结点小就递归去左树找;若比这个要找的结点大就递归去右树找。找到空结点即为没找到。

bool _FindR(Node* rootp, const K& key)
{if (rootp == nullptr)return false;if (rootp->_key == key)return true;if (rootp->_key > key)return _FindR(rootp->_left, key);else if (rootp->_key < key)return _FindR(rootp->_right, key);
}

2)_InsertR

思路:遵循搜索二叉树的特性。先找到合适位置再插入,要插入的结点比当前结点小就递归到左子树插入,比当前结点大就递归到右子树插入。一直到找到空位置(nullptr)即可插入。(ps.不允许插入重复值)

注意:关于链接的问题,我们可以通过引用传参巧妙地解决。为了方便理解,这里可以把引用传参看作传递了参数本身,而不是参数的一份拷贝。

bool _InsertR(Node*& rootp, const K& key)
{if (rootp == nullptr){rootp = new Node(key);return true;}if (rootp->_key == key)return false;if (rootp->_key > key){return _InsertR(rootp->_left, key);}else if (rootp->_key < key){return _InsertR(rootp->_right, key);}
}

再次说明:Node* rootp 传值传参 是 将要传过来的指针变量的内容拷贝一份给 rootp,它们分别是两个指针变量;Node*& rootp 传引用传参 是 将要穿过的指针变量本身(也可以理解为这个指针变量的别名)传递过来,rootp就是这个指针变量,它们就是同一个指针变量。

插入时的链接问题-图解(例)

3)_EraseR

基本思路:先找到要删除的结点,没找到就直接返回 false,找到了就进行删除。

递归思路:要删除的结点值 key 比当前结点小就递归到左树删除;比当前结点大就递归到右树删除;等于当前结点就对这个结点进行删除。

bool _EraseR(Node*& rootp, const K& key)
{if (rootp == nullptr)return false;if (rootp->_key > key){return _EraseR(rootp->_left, key);}else if (rootp->_key < key){return _EraseR(rootp->_right, key);}else{Node* del_nodep = rootp;if (del_nodep->_left == nullptr)//左子树为空或左右子树为空{rootp = del_nodep->_right;delete del_nodep;}else if (del_nodep->_right == nullptr)//右子树为空{rootp = del_nodep->_left;delete del_nodep;}else//左右子树都不为空{Node* subLeft = del_nodep->_right;while (subLeft->_left){subLeft = subLeft->_left;}std::swap(subLeft->_key, del_nodep->_key);return _EraseR(rootp->_right, key);}}
}

对于左右子树为空/左子树为空/右子树为空的情况:(以下图情况为例分析)

图例

对于左右子树都不为空的情况:(以下图情况为例分析)

图例


END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3029312.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【Vue2】关于response返回数据的错误小记

关于Vue2中response返回数据的一个错误小记 如图&#xff0c;在这里返回的时候&#xff0c;后端是通过List< String >返回的&#xff0c;response接收到的实际上是一个Array数组&#xff0c;但是赋值给searchedTaskList的时候&#xff0c;需要在.then包括的范围里面赋值给…

智能创作时代:AI引领下的内容生产革命与效率提升

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【2022 深圳 ArchSummit 】大数据架构稳定性保障实践

文章目录 一、前言二、现状三、大数据架构的历史变迁&#xff08;一&#xff09;洪荒期&MR&#xff08;二&#xff09;远古期&MPP&#xff08;四&#xff09;近现代&Flink/Spark&#xff08;五&#xff09;现如今&实时数据湖架构 四、架构稳定的关键因素&#…

网络编程--tcp三次握手四次挥手

1、三次握手 &#xff08;1&#xff09;三次握手的详述 首先Client端发送连接请求报文&#xff0c;Server段接受连接后回复ACK报文&#xff0c;并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文&#xff0c;并分配资源&#xff0c;这样TCP连接就建立了。…

vue + element-plus 开发中遇到的问题

1.问题之路由守卫 初写路由守卫&#xff0c;对于next()的理解不是很透彻&#xff0c;就想着都放行&#xff0c;不然看不到效果&#xff0c;结果控制台出现了警告&#xff0c;想着报黄的问题就不是问题&#xff0c;但仔细一看发现他说&#xff0c;如果再生产阶段就会失败&#x…

AI图书推荐:使用FastAPI框架构建AI服务

《使用FastAPI构建生成式AI服务》&#xff08;Building Generative AI Services with FastAPI (Early Release) &#xff09;是一本由Ali Parandeh编写的书籍&#xff0c;计划于2025年3月首次出版&#xff0c;该书以实践为导向&#xff0c;指导读者如何开发具备丰富上下文信息的…

mysql基础概念

文章目录 登录mysqlmysql和mysqld数据库操作主流数据库MYSQL架构SQL分类 登录mysql 登录mysql连接服务器&#xff0c;mysql连接时可以指明主机用-h选项&#xff0c;然后就可以指定主机Ip地址&#xff0c;-P可以指定端口号 -u指定登录用户 -P指定登录密码 查看系统中有无mysql&…

嵌入式C语言高级教程:实现基于STM32的智能照明系统

智能照明系统不仅可以自动调节光源的亮度和色温&#xff0c;还可以通过感应用户的行为模式来优化能源消耗。本教程将指导您如何在STM32微控制器上实现一个基本的智能照明系统。 一、开发环境准备 硬件要求 微控制器&#xff1a;STM32F103RET6&#xff0c;具有足够的处理能力…

Python语言基础学习(上)

目录 一、常量和表达式 二、变量和类型 2.1 认识变量 2.2 定义变量 2.3 变量类型 1、整数 int 2、浮点数&#xff08;小数&#xff09;float 3、字符串 str 4、布尔类型 2.4 类型转换 三、注释 3.1 单行注释 3.2 文档注释&#xff08;或者多行注释&#xff09; …

数字工厂管理系统如何助力企业数据采集与分析

随着科技的不断进步&#xff0c;数字化已成为企业发展的重要趋势。在制造业领域&#xff0c;数字工厂管理系统的应用日益广泛&#xff0c;它不仅提升了生产效率&#xff0c;更在数据采集与分析方面发挥着举足轻重的作用。本文旨在探讨数字工厂管理系统如何助力企业数据采集与分…

[uniapp] 配置ts类型声明

我想引进图片,但是报错 声明一下就行 TypeScript 支持 | uni-app官网 创建tsconfig.json文件,复制官网的配置 然后在随便一个目录下写一个随便名字的.d.ts文件 例如这样 保存就行 因为ts是默认扫描全部的,所以要按照官网的写法 把不必要的排除掉就行,免得浪费性能

数据库的一些知识点

数据模型的组成要素中&#xff0c;描述数据库的组成对象以及对象之间的联系的是&#xff08; &#xff09;。 A 数据结构 B 数据操作 C 数据的完整性约束条件 D 数据的安全性约束条件 2.单选题 (2分) 若关系中的某一组属性的值能够唯一地标识一个元组&#xff0c;而其子集…

ROS实操:通信机制的实现

最近闲来无事&#xff0c;打算重温了一下ROS方面的相关知识。先前的学习都是一带而过&#xff0c;发现差不多都忘了&#xff0c;学习的不够深入。因此&#xff0c;在重温的同时&#xff0c;写下了这篇关于ROS通信实操的学习博客。 上一篇博客的链接为&#xff1a;ROS架构的学习…

OpenCompass大模型评估

作业链接&#xff1a; Tutorial/opencompass/homework.md at camp2 InternLM/Tutorial GitHub 项目链接&#xff1a; GitHub - open-compass/opencompass: OpenCompass is an LLM evaluation platform, supporting a wide range of models (Llama3, Mistral, InternLM2,GPT-…

Modown9.1主题无限制使用+Erphpdown17.1插件

Modown9.1主题无限制使用 1、Erphpdown17.1插件Modown9.1主题 2、送Modown主题详细教程。 1、Erphpdown插件和Modown主题无需激活 2、送的插件均无需激活 3、主题插件均不包更新 4、已亲测可以完美使用。 功能强大&#xff0c;适用于绝大多数虚拟资源站&#xff01;物超所值&a…

远程桌面连接不上怎么连服务器,原因是什么?如何解决?

远程桌面连接不上怎么连服务器&#xff0c;原因是什么&#xff1f;如何解决&#xff1f; 面对远程桌面连接不上的困境&#xff0c;我们有办法&#xff01; 当你尝试通过远程桌面连接服务器&#xff0c;但遭遇连接失败的挫折时&#xff0c;不要慌张。这种情况可能由多种原因引起…

Netty底层数据交互源码分析

文章目录 1. 前题回顾2. 主线流程源码分析3. Netty底层的零拷贝4. ByteBuf内存池设计 书接上文 1. 前题回顾 上一篇博客我们分析了Netty服务端启动的底层原理&#xff0c;主要就是将EventLoop里面的线程注册到了Select中&#xff0c;然后调用select方法监听客户端连接&#xf…

Amesim基础篇-热仿真常用模型库-Air Conditioning-Pipes

前言 基于上文对空调库各个元件的介绍&#xff0c;本文进一步将其中的管路展开。 管路介绍 1 摩擦阻力管&#xff08;R&#xff09;&#xff1a; 具有阻力特性的管路&#xff0c;通过管长以及管截面计算阻力。 2 可调节阻力管&#xff08;R&#xff09;&#xff1a; 只具有…

STM32CubeMX软件使用(超详细)

1、Cube启动页介绍 2、芯片选择页面介绍 3、输入自己的芯片型号&#xff0c;这里以STM32U575RIT6举例 4、芯片配置页码介绍 5、芯片外设配置栏详细说明 6、点击ClockConfiguration进行时钟树的配置&#xff0c;选择时钟树后可以选择自己想使用的时钟源&#xff0c;也可以直接输…

[c++]多态的分析

多态详细解读 多态的概念多态的构成条件 接口继承和实现继承: 多态的原理:动态绑定和静态绑定 多继承中的虚函数表 多态的概念 -通俗的来说&#xff1a;当不同的对象去完成某同一行为时&#xff0c;会产生不同的状态。 多态的构成条件 必须通过基类的指针或者引用调用虚函数1虚…