【STL】二叉搜索树模拟实现

BinarySearchTree模拟实现

  • 1 什么是二叉搜索树
  • 2 二叉搜索树的插入
    • 2.1 插入的流程
    • 2.2 插入的代码
  • 3 二叉搜索树的查找
    • 3.1 查找的流程
    • 3.2 查找的代码
  • 4 二叉搜索树的中序遍历
    • 4.1 中序遍历流程
    • 4.2 中序遍历代码
  • 5 二叉搜索树的删除
    • 5.1 没有孩子 | 有右孩子
    • 5.2 没有右孩子
    • 5.3 有两个孩子
    • 5.4 删除的全部代码
  • 6 拷贝构造
    • 6.1 函数头
    • 6.2 copy(Node* root)
  • 7 赋值运算符重载
  • 8 全部代码

1 什么是二叉搜索树

  • 若它的左子树不空,则 左子树上所有结点的值均 小于 根结点的值。
  • 若它的右子树不空,则 右子树上所有结点的值均 大于 根结点的值。
  • 它的左、右树又分为⼆叉搜索树

例如下面这一颗树:
在这里插入图片描述

左孩子严格小于根,右孩子严格大于根
不会有相同的值

1.二叉搜索树有什么特别的优势?

当使用 中序遍历的时候,遍历的结果是 从小到大排好序的。

速度很快,因为是特殊的树状结构,和二分查找类似,因此时间复杂度为 O(log N)

2 二叉搜索树的插入

2.1 插入的流程

2.上面这棵树的插入过程是怎样的?
在这里插入图片描述

  • 当插入的值小于当前值,就往左边遍历
  • 当插入的值大于当前值,就往右边遍历
  • 直到找到一个合适的位置,将值插入进去

2.2 插入的代码

bool Insert(const T& key)
{if (_root == nullptr){_root = new Node(key);return true;}//不为空,要找到能插入的位置Node* parent = nullptr;Node* cur = _root;while (cur){//当前节点比key小,key往右边走if (cur->_data < key){parent = cur;cur = cur->_right;}else if (cur->_data > key){parent = cur;cur = cur->_left;}else{//走到这里说明有一个相同的的数了return false;}}//走到这里,说明找到合适的插入位置了//要判断插入到当前cur的左边还是右边cur = new Node(key);if (parent->_data < key){parent->_right = cur;}else{parent->_left = cur;}return true;
}

3.为什么要添加一个parent结点?

因为如果不添加parent结点,不知道插入的值是父节点的左孩子还是右孩子

3 二叉搜索树的查找

3.1 查找的流程

根据二叉搜索树的性质, 如果要查找4,查找的流程如下:
在这里插入图片描述

从这个查找的路线来看,也能发现, 查找的方式类似二分查找,比线性查找快很多。

3.2 查找的代码

bool Find(const T& key) const
{if (_root == nullptr)return false;Node* cur = _root;while (cur){//key大,往右边找if (cur->_data < key){cur = cur->_right;}else if (cur->_data > key){cur = cur->_left;}else{return true;}}return false;
}

4 二叉搜索树的中序遍历

这里只讲中序遍历,是因为,只有中序遍历是有意义的。中序遍历的结果是从小到大排好序的。

4.1 中序遍历流程

  • 树的遍历是有深度优先和宽度优先。
  • 这里采用宽度优先,也就是递归的方式。
  • 中序遍历顺序:左子树,根,右子树。

4.2 中序遍历代码

		void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_data << " ";_Inorder(root->_right);}

4.中序遍历需要传一个参数为Node* root,但是在类中这个成员变量一般是private,如果直接访问会报错,需要怎么解决这个问题?

第一种方法:直接将成员变量的访问权限改为public

第二种方法:像下面这样。
在这里插入图片描述
将中序遍历的代码在public中做一个封装,这样就不需要传递参数了。

5 二叉搜索树的删除

二叉搜索树的删除较为复杂,分为三种情况:

  • 没有孩子
  • 有一个孩子:只有左孩子 | 只有右孩子
  • 有两个孩子

根据这三种情况,写代码的时候可以分情况讨论:

if (没有左孩子) //没有左孩子可以表示:一个孩子都没有 | 没有左孩子(有右孩子)
...
else if (没有右孩子) //没有左孩子不满足,但是满足没有右孩子的条件 --> 只有左孩子
...
else  //有两个孩子
...

总结:
第一种情况表示:没有孩子或者有右孩子
第二种情况表示:只有左孩子
第三种情况:有两个孩子

5.为什么要这样分情况?

不用特意的去写没有孩子的情况了。第一种情况包含了没有孩子的情况。

接下来根据分的情况进行叙述。

5.1 没有孩子 | 有右孩子

可以表示为删除下图中的4或者10.
在这里插入图片描述

删除4:让父节点6指向4的右孩子(虽然4的孩子为空)
删除14:让父节点8指向10的右孩子

这里有两个关键点:

  • 需要保留父节点
  • 父节点指向要删除结点的右孩子

6.为什么一定是指向删除结点的右孩子?左孩子不行吗?

不行, 因为这里分情况讨论的是没有孩子或者只有右孩子的情况。这种情况里面,要删除的结点没有左孩子。

还需要判断是不是根节点,如果是根结点,删除的逻辑不一样。

如果要删除的结点是下图中的10.
在这里插入图片描述

可以发现一个问题:根节点没有父节点。

所以,如果删除的是根节点,直接让根节点指向根节点的右子树就行了。

5.2 没有右孩子

没有右孩子的处理方式和前面没有左孩子的处理方式是一样的。
在这里插入图片描述

如果要删除上图中的值为4的结点,方法就是将4的父节点指向4的左孩子。(因为4没有右孩子)
当然这里还有一个细节:需要判断4是父亲的左孩子还是右孩子。这涉及到是父节点的左指向4的左孩子还是父节点的右指向4的左孩子
如果删除的是根节点,也需要像上面一样特殊处理。

5.3 有两个孩子

假设要删除的节点是8,它有左孩子还有右孩子
在这里插入图片描述
有两个孩子的情况,需要在树中找到一个符合当前位置的节点,然后将这两个节点交换。

7.哪种节点符合条件?

要满足既比左子树大,右比右子树小。因此只能找左子树中最大的节点或者右子树中最小的节点。

8.找到符合条件的节点并且交换节点之后,该如何操作?
交换完之后树的结构如下图:
在这里插入图片描述
接下来只需要让左子树中最大节点的父节点指向该节点的左孩子即可。
9.为什么只需要指向该节点的左孩子?为什么不指向右孩子?

因为符合条件的节点为左子树中最大的值。根据二叉搜索树的性质,就是从左子树开始一直往右走,最右的那个节点满足条件。因此, 满足条件的节点一定是最右边的,只可能有左孩子(也可能没有孩子),不可能有右孩子

5.4 删除的全部代码

		bool Erase(const T& key){if (_root == nullptr)return false;//分三种情况,没有孩子 | 有一个孩子 | 有两个孩子Node* parent = nullptr;Node* cur = _root;while (cur){//1.找到keyif (cur->_data < key)  //往右找{parent = cur;cur = cur->_right;}else if (cur->_data > key){parent = cur;cur = cur->_left;}else{//走到这里说明找到值为key的结点了if (cur->_left == nullptr){//判断是不是根节点if (cur == _root){_root = cur->_right;}else{//判断cur是parent的左孩子还是右孩子if (parent->_left == cur){//让parent接管cur的右节点->因为左为空parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//判断cur是parent的左孩子还是右孩子if (parent->_left == cur){//让parent接管cur的左节点->因为右为空parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}else{//左右都不为空//找替换结点,左边的最大的元素或者右边的最小的元素Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}//走到这里,说明已经走到左边的最大元素了std::swap(leftMax->_data, cur->_data);  //交换leftMax和cur//判断是parent的左孩子还是右孩子if (parent->_left == leftMax){parent->_left = leftMax->_left;   //这是因为leftMax是左边的最大元素,不可能有右孩子了}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;}

6 拷贝构造

6.1 函数头

		BSTree(const BSTree<T>& t){}

拷贝构造就是用新的类对象构造成传入的类对象的样子。

二叉搜索树是树形结构,因此可以使用递归的形式进行拷贝构造。
创建一个copy()函数,直接调用即可。

6.2 copy(Node* root)

		Node* copy(Node* root){if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_data);copyroot->_left = copy(root->_left);copyroot->_right = copy(root->_right);return copyroot;}

7 赋值运算符重载

		BSTree<T>& operator=(BSTree<T> t){std::swap(_root, t._root);return *this;}

这里有一个比较巧妙的地方:
10. 为什么不传引用?

因为如果不传引用而传值,就会 产生临时对象,临时对象是拷贝产生的,而这个拷贝产生的临时对象出了作用域就会销毁。因此只需要 将自己类中根节点和拷贝产生的根节点进行交换,就达成了赋值的目的。出了作用域临时对象还会自动销毁。

这里的具体原理,我在这篇文章:list的模拟实现中画图讲解过。点击链接即可跳转。

8 全部代码

BinarySearchTree.h

#pragma once
#include<iostream>using std::cout;
using std::cin;
using std::istream;
using std::ostream;
using std::endl;namespace zyy
{template<class T>struct BSTreeNode{BSTreeNode<T>* _left;BSTreeNode<T>* _right;T _data;//构造函数BSTreeNode(const T& x):_left(nullptr),_right(nullptr),_data(x){}};template <class T>class BSTree{public:typedef BSTreeNode<T> Node;//默认构造BSTree():_root(nullptr){}//拷贝构造BSTree(const BSTree<T>& t){_root = copy(t._root);}BSTree<T>& operator=(BSTree<T> t){std::swap(_root, t._root);return *this;}~BSTree(){Destory(_root);}bool Insert(const T& key){if (_root == nullptr){_root = new Node(key);return true;}//不为空,要找到能插入的位置Node* parent = nullptr;Node* cur = _root;while (cur){//当前节点比key小,key往右边走if (cur->_data < key){parent = cur;cur = cur->_right;}else if (cur->_data > key){parent = cur;cur = cur->_left;}else{//走到这里说明有一个相同的的数了return false;}}//走到这里,说明找到合适的插入位置了//要判断插入到当前cur的左边还是右边cur = new Node(key);if (parent->_data < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool Find(const T& key) const{if (_root == nullptr)return false;Node* cur = _root;while (cur){//key大,往右边找if (cur->_data < key){cur = cur->_right;}else if (cur->_data > key){cur = cur->_left;}else{return true;}}return false;}bool Erase(const T& key){if (_root == nullptr)return false;//分三种情况,没有孩子 | 有一个孩子 | 有两个孩子Node* parent = nullptr;Node* cur = _root;while (cur){//1.找到keyif (cur->_data < key)  //往右找{parent = cur;cur = cur->_right;}else if (cur->_data > key){parent = cur;cur = cur->_left;}else{//走到这里说明找到值为key的结点了if (cur->_left == nullptr){//判断是不是根节点if (cur == _root){_root = cur->_right;}else{//判断cur是parent的左孩子还是右孩子if (parent->_left == cur){//让parent接管cur的右节点->因为左为空parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//判断cur是parent的左孩子还是右孩子if (parent->_left == cur){//让parent接管cur的左节点->因为右为空parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}else{//左右都不为空//找替换结点,左边的最大的元素或者右边的最小的元素Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}//走到这里,说明已经走到左边的最大元素了std::swap(leftMax->_data, cur->_data);  //交换leftMax和cur//判断是parent的左孩子还是右孩子if (parent->_left == leftMax){parent->_left = leftMax->_left;   //这是因为leftMax是左边的最大元素,不可能有右孩子了}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;}void Inorder(){return _Inorder(_root);}private://中序遍历void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_data << " ";_Inorder(root->_right);}Node* copy(Node* root){if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_data);copyroot->_left = copy(root->_left);copyroot->_right = copy(root->_right);return copyroot;}void Destory(Node*& root){if (root == nullptr)return;Destory(root->_left);Destory(root->_right);delete root;root == nullptr;}Node* _root;};
};

TestBSTree.cpp

#include "BinarySearchTree.h"using namespace zyy;void test1()
{BSTree<int> bst;bst.Insert(1);bst.Insert(3);bst.Insert(4);bst.Insert(6);bst.Insert(0);bst.Insert(2);if (bst.Find(3)){cout << "找到了" << endl;}else{cout << "没找到" << endl;}bst.Inorder();
}void test2()
{BSTree<int> bst;bst.Insert(8);bst.Insert(1);bst.Insert(3);bst.Insert(6);bst.Insert(4);bst.Insert(7);bst.Insert(10);bst.Insert(14);bst.Insert(13);cout << "before: ";bst.Inorder();//1.测试没有孩子//bst.Erase(7);//2.测试有一个孩子//bst.Erase(10);//3.测试有两个孩子bst.Erase(6);cout << "after: ";bst.Inorder();
}
int main()
{test2();return 0;
}

在这里插入图片描述

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

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

相关文章

广州自闭症寄宿学校有哪些?选择最适合孩子的学校

在广州这座繁华而充满人文关怀的城市里&#xff0c;有一群特殊的孩子&#xff0c;他们被称为“星星的孩子”——自闭症儿童。他们生活在自己的世界里&#xff0c;对外界的刺激反应迟钝或过度敏感&#xff0c;社交互动困难&#xff0c;语言表达受限。然而&#xff0c;在广州&…

医学图像处理入门:VS2019+DCMTK3.6.8编译及环境配置

1. 下载DCMTK的源文件包和支持库 首先下载dcmtk软件包&#xff0c;此处我们下载源码和支持库来进行自己编译。下载网址&#xff1a; https://dicom.offis.de/en/dcmtk/dcmtk-software-development/ 如图所示&#xff0c;选择合适的版本进行下载&#xff0c;此处采用VS2019进行…

5款人声分离免费软件分享,从入门到精通,伴奏提取分分钟拿捏!

人声分离通常是音乐制作、混音和卡拉OK中常用的重要技术之一。它的核心是将乐器伴奏从原始音轨中分离出来&#xff0c;使得用户可以单独处理或重混音频&#xff0c;创造出清晰干净的伴奏轨道。若缺乏强大的音频剪辑软件或专业人声分离工具&#xff0c;这一过程往往会比较困难。…

Xinstall品牌揭秘:如何成为App拉新的行业翘楚?

在移动互联网时代&#xff0c;App作为连接用户与服务的桥梁&#xff0c;其重要性不言而喻。然而&#xff0c;随着市场竞争的加剧&#xff0c;App拉新&#xff08;即吸引新用户下载并使用App&#xff09;的难度也在逐渐增大。传统的营销方式往往面临着成本高、效率低、用户留存差…

前端开发攻略---分块加载大数据

一、问题 解决当遇到较大的数据请求&#xff0c;当用户网络较差的时候&#xff0c;需要等很久很久才能拿到服务器的响应结果&#xff0c;如果这个响应结果需要再页面上展示的话&#xff0c;会导致页面长时间白屏的问题 二、实现原理 当发送一个请求时&#xff0c;需要等服务器把…

照片相册SDK解决方案,模板化包装,简化视频制作流程

专业的视频制作往往门槛较高&#xff0c;让许多用户望而却步&#xff0c;美摄科技推出了全新的照片相册SDK解决方案&#xff0c;旨在通过模板化包装方式&#xff0c;让用户轻松上传照片&#xff0c;快速生成一个充满创意和个性化的照片视频&#xff0c;让每个人都能成为自己生活…

Java的TCP通信

Java的TCP通信 TCP发送数据 Java中的TCP通信 Java对基于TCP协议的的网络提供了良好的封装&#xff0c;使用Socket对象来代表两端的通信端口&#xff0c;并通过Socket产生IO流来进行网络通信。Java为客户端提供了Socket类&#xff0c;为服务器端提供了ServerSocket类 构造方法…

使用 Go 语言与 Elasticsearch 实现高效搜索服务

使用 Go 语言与 Elasticsearch 实现高效搜索服务 什么是 Elasticsearch Elasticsearch 是一个基于 Apache Lucene 构建的分布式搜索引擎&#xff0c;能够存储、搜索和分析大量数据。它具有高可扩展性、快速的搜索速度&#xff0c;支持全文检索、多字段查询和近实时数据分析。…

mysql/doris 计算两个时间相差n天n时n分示范

mysql/doris 计算两个时间相差n天n时n分示范 两个时间&#xff1a;so.create_time&#xff0c;so.update_time CONCAT(FLOOR(DATEDIFF(HOUR ,so.create_time,so.update_time)/24),天,DATEDIFF(HOUR ,so.create_time,so.update_time)%24,时,DATEDIFF(MINUTE ,so.create_time,so…

自动猫砂盆“智商税”还是“真香”?2024自动猫砂盆保姆级干货

平时忙着上班&#xff0c;或者一遇到出差就要离家四五天&#xff0c;没办法给毛孩子的猫砂盆铲屎&#xff0c;导致粪便堆积太久。很多铲屎官也了解到有自动猫砂盆这种东西&#xff0c;但是生怕是智商税&#xff0c;总觉得忍忍手铲也可以&#xff0c;要知道&#xff0c;猫咪的便…

如何在阿里云一键部署FlowiseAI

什么是FlowiseAI FlowiseAI 是一个开源的低代码开发工具&#xff0c;专为开发者构建定制的语言学习模型&#xff08;LLM&#xff09;应用而设计。 通过其拖放式界面&#xff0c;用户可以轻松创建和管理AI驱动的交互式应用&#xff0c;如聊天机器人和数据分析工具。 它基于Lang…

网络安全-Morpheus

NVIDIA Morpheus 文章目录 前言一、简介1. NVIDIA Morpheus 是什么?二、优势1. 深入了解 NVIDIA Morpheus 的优势高管借助全面的数据可见性,实时检测威胁利用生成式 AI 提高效率提升性能,降低成本开发者轻松开发和部署功能丰富,灵活性强实时遥测三、用例Morpheus 用例四、A…

通过观测云 DataKit Extension 接入 AWS Lambda 最佳实践

前言 AWS Lambda 是一项计算服务&#xff0c;使用时无需预配置或管理服务器即可运行代码。AWS Lambda 只在需要时执行代码并自动缩放。借助 AWS Lambda&#xff0c;几乎可以为任何类型的应用程序或后端服务运行代码&#xff0c;而且无需执行任何管理。 Lambda Layer 是一个包…

AI绘画,AI生成图片

分享一个可以免费使用的AI生成图片的网站&#xff1a; https://openart.aihttps://openart.ai/create 1、登陆后点击右上角create 2、在创建页面左侧输入描述文案&#xff0c;下面调整生成图片张数&#xff0c;点击create&#xff0c;右边即可生成 我这里输入了在吃麦当劳的超…

笔记||VUE3

侦听器 | Vue.js (vuejs.org) 模板引用 | Vue.js (vuejs.org)

Java初阶~~四种内部类总结

文章目录 1.内部类的分类2.局部内部类2.1.基本语法2.2就近原则的理解 3.匿名内部类3.1基于接口的匿名内部类3.2基于普通类的匿名内部类3.3基于抽象类的匿名内部类3.4匿名内部类的细节3.5匿名内部类实践3.5.1作为实参进行传递3.5.2实践案例 4.成员内部类4.1基本介绍4.2外部类&am…

api测试和接口测试的区别

API测试和接口测试是软件测试中一个非常重要的领域&#xff0c;尤其是在当前Web应用程序和移动应用程序的发展中。虽然它们都测试了Web服务的功能&#xff0c;但是二者在测试方法和测试实施方面存在很大的差异。本文将介绍API测试和接口测试之间的主要区别 API测试的主要关注点…

【LLM论文日更】| BGE经典论文-CPACK

论文&#xff1a;https://arxiv.org/pdf/2309.07597代码&#xff1a;GitHub - FlagOpen/FlagEmbedding: Retrieval and Retrieval-augmented LLMs机构&#xff1a;BAAI领域&#xff1a;embedding model发表&#xff1a;SIGIR 2024 ​ 研究背景 研究问题&#xff1a;这篇文章…

MySQL插入优化-性能对比

插入优化主要包括&#xff1a; 批量插入条数据&#xff0c;而不是单个记录逐条插入。手动提交事务&#xff0c;避免自动提交事务带来的额外开销。使用load命令从本地文件导入。 性能对比 创建数据库表 CREATE TABLE if not exists tb_sku ( id int(20) …

防汛可视化系统:提升应急响应能力

通过图扑可视化系统实时监测水情、雨情和地理数据&#xff0c;辅助防汛决策与调度&#xff0c;提供直观的风险预警信息&#xff0c;从而优化资源分配&#xff0c;提高防汛应急响应效率。