搜索二叉数(c++)

前言

在学习数据结构的时候我们学习过二叉树,那啥是搜索二叉树呢?我们知道单纯的二叉树没有增删查改的实际意义,因为没有任何限制条件的二叉树其实用处很局限。但是堆就不一样了,他就是一个二叉树加上了大小堆的限制条件,在排序方面很有作用,比如堆排序,时间复杂度是n*logN,效率挺高的。那搜索二叉数是加了上限制条件呢?搜索二叉数:本质是一颗二叉树+左子树的所有节点都小于根节点的值(左子树不为空的话)+右子树的所有结点的值都大于根节点的值(右子树不为空)+左右子树也分别为搜索二叉树。

总结下:搜索二叉树

  • 若他的左子树不为空,左子树的所有节点的值都小于根节点的值
  • 若他的右子树不为空,右子树的所有结点的值都大于根节点的值
  • 他的左右子树也分别为搜索二叉树

我们管搜索二叉树也叫二叉搜索树,或者二叉排序树。这里提问下,他为啥可以叫二叉搜索树呢?

我们看一下这两个,SBTree(搜索二叉树),BSTree(二叉搜索树),你觉得SB好听呢?还是BSTree,让你更舒服点呢?这里开玩笑,其实叫啥都行,毕竟都有b树这种名字(听着就像在骂人)。可见取名字的人水平不是很高呀。好了,回到正文。

正文

在实现之前我们先看一颗搜索二叉树,如下图:

这棵树就是一颗二叉搜索树,你用中序遍历一遍会发现他是有序的,这也是他的实际用处。搜索二叉数主要运用在一些Kye搜索模型,快速判断在不在的场景,比如小区车辆出入系统。还有就是Key/Value模型,比如大名鼎鼎的高铁实名认证系统。

搜索二叉数的实现

基本代码

//结点
template<class K>
struct BSTreeNode
{BSTreeNode<K>* left;BSTreeNode<K>* right;K _key;BSTreeNode(const K& key):left(nullptr), right(nullptr), _key(key){}
};//二叉搜索树
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://构造BSTree():_root(nullptr){}//析构~BSTree(){Destroy(_root);}//拷贝构造BSTree(const BSTree<K>& t){_root = Copy(t._root);}//赋值重载 现代写法BSTree<K>& operator=(BSTree<K>& t){std::swap(_root, t._root);return *this;}//中序遍历void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->left);cout << root->_key << " ";_InOrder(root->right);}private:
//拷贝 
Node* Copy(Node* root)
{if (root == nullptr){return nullptr;}Node* copyRoot = new Node(root->_key);copyRoot->left = Copy(root->left);copyRoot->right = Copy(root->right);return copyRoot;
}
//后序删除 引用置空 方便很多
void Destroy(Node*& root)
{if (root == nullptr){return;}Destroy(root->left);Destroy(root->right);delete root;root == nullptr;
}Node* _root;
};

这里实现析构,拷贝构造,都是采用套娃的思路,定义一个私有方法,使用私有方法完成代码的逻辑,在public的方法就是调用这些方法,这样逻辑很清晰,库里很多代码都是这样实现。如果你学过一点java,会发现java里全是这样的设计。这里的赋值重载是用的现代写法,直接交换根节点,返回this指针,这也是推荐的写法,传统的一个一个赋值的写法,太麻烦了,这里不推荐。其实这里拷贝构造这样设计还有一个原因就是,你无法直接传一个_root,只能传一个树,但是我们有需要_root,因此只能采取这种方式,上传_root参数,实现拷贝效果。下面讲的是搜索二叉数里的核心代码,有两个版本,都要掌握。

非递归版

这里介绍了,查找,插入,删除,返回值都是bool值,表示成功与否。

//插入
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->left = cur;}else{parent->right = cur;}}
//查找
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(替换法)
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->right == cur){parent->right = cur->right;}else{parent->left = cur->right;}}	}//右为空else if (cur->right == nullptr){if (cur == _root){_root = cur->left;}else{if (parent->right == cur){parent->right = cur->left;}else{parent->left = cur->left;}}				}//替换法else{//cur = 8;Node* leftParent = cur;Node* leftMax = _root->left;while (leftMax->right){leftParent = leftMax;leftMax = leftMax->right;}std::swap(leftMax->_key, cur->_key);if (leftParent->left == leftMax){leftParent->left = leftMax->left;}else{leftParent->right = leftMax->left;}cur = leftMax;}delete cur;return true;}}return false;
}
Find实现

我们前面讲过,搜索二叉数的特点,适合排序。因此,我们在查找值的时候,可以根据左右子树大于/小于根这一特性,实现查找,这样查找的效率就不会是全部遍历一遍,如果比根小,那我们就可以直接在左子树里找,所以这里的时间复杂度就是高度次。我们这里非递归实现就是使用while循环cur指针,if else if分支语句,控制查找方向,在循环内找到,就进入else语句。如果循环解释还没找到,这时cur指针为空,那说明就没有,直接返回false 。提问下,这里的时间复杂是,O(n)还是logN,答案是O(n),不是查找高度次吗,二叉树的高度不是logN吗?这里二叉搜索树不一定就很满足完全二叉树的条件,他甚至可以这样,如下图

这种情况是肯定存在的,在这种情况几乎就是O(n),所以这里二叉搜索树,也没有很厉害,因此我们后面还要学习红黑树,他的底层就是二叉搜索树。这里我没有套值进去,大家可以自己套一下,这种情况是包存在的。

Insert实现

插入的代码的遍历树和查找是一个逻辑的,不同就是,插入是要循环外,当cur指针为空的时候再插入结点,所以这里需要new 一个结点,但是这并没有真正改变链表里的指向,我们需要引入一个父节点,来链接这里的关系。这里在根指针为空的时候,我们直接特殊处理,new结点赋值给root,然后返回true就行。这里注意二叉搜索树是不允许重复的值,这也符合他的特性。因此这里插入失败就是发现了重复的值。这里在用父节点,改变链接关系的时候,需要保证插入后还是一个二叉搜索树,因此,还需要判断一下他与父节点值的大小,再决定插入在左边还是右边。

入上图,这里插入-1还是2就是不同的结果,或者插入可能就会改变了这棵树,导致这棵树不再是二叉搜索树。

Erase实现

这里把这个删除放在最后讲,是因为删除是最难的一个实现,而且考的话也应该就是考这个(题目),这里删除是要分好几个情况

第一种情况:没有孩子或者一个孩子-->托孤

对于第一种情况,我们需要定义一个父节点,改变链接关系,那这里父节点是赋值给空指针呢还是赋值给cur指针呢,这里只能赋值cur指针,因为有一种情况会导致空指针引用问题,如下图

第二种情况:两个及两个以上的孩子-->替换法

这里也是需要一个父节点,再替换后,改变链接关系,但是注意这里是需要做一下判断的,不一定就是右子树改变关系,如下图

递归版

	//递归实现bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseE(const K& key){return _EraseE(_root, key);}

递归是需要上传一个root节点,但是我们只能上传一个个树,所以这里采取内部上传根节点,调用私有方法实现。

Find实现
	bool _FindR(Node* root, const K& key){if (root == nullptr) return false;if (root->_key < key) {_FindR(root->right, key);}if (root->_key > key){_FindR(root->left, key);}else{return true;

递归版就简单多了,使用二叉搜索树的特性判断,和上面循环的版本是一样的,只不过是吧循环该递归了而已

Insert实现
	//这里引用 起到了自动链接的作用 bool _InsertR(Node*& root, const K& key){//为空链接 if (root == nullptr) {root = new Node(key);return true;}if (root->_key < key) {_InsertR(root->right, key);}if (root->_key > key){_InsertR(root->left, key);}//找不到else{return false;}}

这里递归实现插入也很简单,非递归的版本是需要用父节点来链接,但是递归的版本我们可以不用这样子做,把参数改成引用,这样子改变的就不再是局部变量,而是直接改变本体。

Erase实现
	//递归实现bool _EraseE(Node*& root, const K& key){if (root == nullptr) return false;//这里是if else if 一个执行了 另一个就不能执行 否则 你出错if (root->_key < key){_EraseE(root->right, key);}else if(root->_key > key){_EraseE(root->left, key);}//找到了else{Node* del = root;if (root->left == nullptr){root = root -> right;}else if (root->right == nullptr){root = root->left;}else{Node* leftMax = root->left;while (leftMax->left){leftMax = leftMax->right;}std::swap(root->_key, leftMax->_key);//这里不能传leftMax 指向改变会乱return _EraseE(root->left, key);}delete del;}return true;}

这里递归实现删除相比于非递归的版本就简单多了,删除里面寻找代码的逻辑和查找是一样的,没啥好讲,主要是找到了之后我们该如何处理。上面插入的时候我们使用了引用直接改变了本体,不用再使用父节点改变链接关系,这里也是可以用引用,不用在搞一个父节点。删除的思路和非递归是一致的,也是分两种情况,不过这里有点复杂,因为你递归传的是一个节点的左子树或右子树的指针的引用,因此你是可以直接改变这个本体,所以在判断的时候,直接就用这个引用本身判断他的左子树还是右子树为空,然后在选择赋值给这个引用,就改变链接关系。然后替换也是找到节点交换节点的值,不过这里我们就可以再次递归,而不是直接删除,因为这里你没有父节点,也无法直接删除,否则链接关系也没法改变,我们干脆把递归思想贯彻到底,上传root->left去删除节点就行。注意这里不可以传leftMax,指向会直接改变,这里引用比较复杂,大家画图理解建议,这里刚开始看这里的代码,我也是没想通,但花了图,就想明白了。

这里递归不理解,其实很好理解,画递归展开图,就可以解决大部分问题。

总结

搜索二叉树其实整体上讲还是比较简单的,主要是为后面的红黑树打基础,在学习C语言的时候我们发现有些数据结构很难写,但是学到c++之后,会发现这些数据结构直接放在STL容器里,而且他们的实现也好写多了,像这个二叉搜索树更适用于c++写,其实二叉树的学习,以及到后面的红黑树,都是更适用于c++。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vc MFC在opencv的Mat图像上显示中文:Mat转位MFC的CImage,画图写文字,再转回Mat

vc MFC在opencv的Mat图像上显示中文&#xff1a;Mat转位MFC的CImage&#xff0c;画图写文字&#xff0c;再转回Mat // Step 1 创建CImage获取dc int iImgW matImgSized.cols; int iImgH matImgSized.rows; int iChannel matImgSized.channels(); bool bCon matImgSized.is…

Docker环境部署

目录 一&#xff1a;Docker 概述 1.什么是 Docker 2:Docker 的优势 3.Docker 的应用场景 4:Docker 核心概念 二:Docker 安装 1:本安装方式使用阿里的软件仓库 三:Docker 镜像操作 1:获取镜像 2.查看镜像信息 3.查看镜像详细信息 4.修改镜像标签(老名字新名字) 5:删…

Axios 拦截器实现原理深度剖析:构建优雅的请求处理管道

在构建现代前端应用时&#xff0c;网络请求处理是关键环节。作为最流行的HTTP客户端库之一&#xff0c;Axios通过其拦截器机制&#xff08;Interceptors&#xff09;提供了强大的请求/响应处理能力。本文将深入Axios源码&#xff0c;揭示拦截器背后的精妙设计与实现原理。 一、…

宝塔安装nginx-http-flv-module,音视频直播,第二篇

1&#xff0c;先安装环境安装nginx 先卸载原有nigix nigix 大于等于 1.2.6 cd /www/server # 进入宝塔目录 yum install git -y git clone https://gitee.com/winshining/nginx-http-flv-module.git 使用源码安装nigix 在 自定义模块 区域点击「添加」&#xff0c;填写以下参…

低延迟4G专网:保障关键业务的实时通信

在工业互联网、智慧园区、应急通信等对“实时性”要求极高的场景中&#xff0c;网络延迟的高低&#xff0c;直接决定了业务运行的可靠性与安全性。IPLOOK依托多年核心网研发经验&#xff0c;推出的低延迟4G专网解决方案&#xff0c;正是为此类关键业务打造的“通信专线”&#…

NLP语言发展路径分享

自然语言处理初期发展历程 早期&#xff1a;离散表示 one-hot&#xff08;只表达“有/无”&#xff0c;语义完全丢失&#xff09;→ n-gram&#xff08;局部上下文&#xff0c;但高维稀疏&#xff09;→ TF-IDF&#xff08;考虑词频与权重&#xff0c;但不能表达词关联&#x…

如何将文件从安卓设备传输到电脑?

将文件从 Android 手机传输到 PC 是例行公事吗&#xff1f;想让文件传输更轻松吗&#xff1f;幸运的是&#xff0c;您可以从本文中获得 7 种方法&#xff0c;其中包含详细的步骤&#xff0c;帮助您轻松了解如何将文件从 Android 传输到 PC&#xff0c;涵盖了从无线工具到传统 U…

【经验分享】浅谈京东商品SKU接口的技术实现原理

京东商品 SKU 接口的技术实现原理涉及数据建模、架构设计、接口协议、安全机制及性能优化等多个技术层面。以下从技术角度详细拆解其实现逻辑&#xff1a; 一、SKU 数据模型与存储架构 1. SKU 数据模型设计 核心字段定义&#xff1a; 基础属性&#xff1a;SKU ID、商品名称、…

虚拟机配置node.js(前端环境搭建)

1.在windows下安装node.js&#xff08;以及npm&#xff09; 修改npm镜像为阿里云的 npm install --registryhttps://registry.npmmirror.com 2.在Linux下安装node.js&#xff08;Centos7 只支持16版本之前的&#xff09; wget https://npmmirror.com/mirrors/node/v15.14.0/n…

多模态大语言模型arxiv论文略读(129)

Task Success Prediction for Open-Vocabulary Manipulation Based on Multi-Level Aligned Representations ➡️ 论文标题&#xff1a;Task Success Prediction for Open-Vocabulary Manipulation Based on Multi-Level Aligned Representations ➡️ 论文作者&#xff1a;M…

【Redis】Redis 关于 BigKey 的实践规约

目录 一、BigKey 的概念 1.1 普通 key 的设计规则 1.2 BigKey 的定义 1.3 BigKey 存在的问题 二、BigKey 的发现与解决方案 第一种方式&#xff1a;redis-cli --bigkeys 第二种方式&#xff1a;scan扫描 第三种方式&#xff1a;第三方工具 第四种方式&#xff1a;网络…

Golang 与 C/C++ 交互实践

在软件开发的实际场景中&#xff0c;我们常常会遇到需要将不同语言的优势结合起来的情况。Golang 凭借其高效的并发性能和简洁的语法&#xff0c;在网络编程和系统开发领域备受青睐&#xff1b;而 C/C 则以其强大的底层操作能力&#xff0c;在系统资源管理方面具有独特优势。那…

五子棋流量主小程序单模式多模式开源版

功能和特点&#xff1a; 核心游戏功能&#xff1a; 1515 标准棋盘 黑白棋交替落子 自动判断胜负和平局 悔棋功能 计时功能 UI 设计&#xff1a; 木纹风格棋盘 立体感棋子&#xff08;使用阴影和渐变&#xff09; 响应式布局&#xff0c;适配不同屏幕尺寸 胜利弹窗动画 交互体验…

Python古代文物成分分析与鉴别研究:灰色关联度、岭回归、K-means聚类、决策树分析

原文链接&#xff1a;tecdat.cn/?p42718分析师&#xff1a;Gan Tian 在文化遗产保护领域&#xff0c;古代玻璃制品的成分分析一直是研究中西方文化交流的关键课题。作为数据科学家&#xff0c;我们在处理某博物馆委托的古代玻璃文物保护咨询项目时&#xff0c;发现传统分析方法…

RabbitMQ消息队列实战指南

RabbitMQ 是什么&#xff1f; RabbitMQ是一个遵循AMQP协议的消息中间件&#xff0c;它从生产者接收消息并传递给消费者&#xff0c;在这个过程中&#xff0c;根据路由规则进行消息的路由、缓存和持久化。 AMQP&#xff0c;高级消息队列协议&#xff0c;是应用层协议的一个开放…

用Java将PDF转换成GIF

为什么要将 PDF 文件转换为 GIF 图片&#xff1f; PDF 是一种矢量图像格式&#xff08;因此可以根据指定的尺寸进行渲染&#xff09;&#xff0c;而 GIF 是一种有损的、固定尺寸的位图文件&#xff0c;像素值固定。因此&#xff0c;将 PDF 转换为 GIF 文件时&#xff0c;我们需…

Redis之分布式锁(2)

上一篇文章我们介绍了什么是分布式锁和分布式锁的一些基本概念。这篇文章我们来讲解一下基于数据库如何实现分布式锁。 基于数据库实现分布式锁 基于数据库实现分布式锁可以分为两种方式&#xff0c;分别是基于数据库表和基于数据库排他锁。 基于数据库表 要实现分布式锁&…

智能检测护航电池产业:容量设备如何提升效率与安全?

电池容量是衡量其储能能力的重要指标&#xff0c;直接影响设备续航与使用寿命。电池容量检测设备通过模拟真实使用场景&#xff0c;精准测量电池的充放电性能&#xff0c;为电池生产、质检及回收环节提供关键数据支持&#xff0c;成为保障电池品质与安全的核心工具。 核心功能…

介绍一款免费MES、开源MES系统、MES源码

一、系统概述&#xff1a; 万界星空科技免费MES、开源MES、商业开源MES、市面上最好的开源MES、MES源代码、适合二开的开源MES。 1.万界星空开源MES制造执行系统的Java开源版本。 开源mes系统包括系统管理&#xff0c;车间基础数据管理&#xff0c;计划管理&#xff0c;物料控制…

构建高性能日志系统:QGroundControl日志模块深度解析

引言&#xff1a;日志系统的重要性 在无人机地面站系统中&#xff0c;日志记录是诊断问题、分析性能的关键基础设施。QGroundControl&#xff08;QGC&#xff09;作为领先的开源无人机地面站软件&#xff0c;其日志系统设计值得深入探讨。本文将揭示QGC日志系统的核心技术&…