c++ 实现二叉搜索树

二叉搜索树的概念

二叉搜索树 (BST,Binary Search Tree),也称二叉排序树或二叉查找树。它要么是一颗空树,要么是满足以下性质的二叉树:

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

在这里插入图片描述

如上图,左侧的二叉树不是一颗二叉搜索树,右侧的二叉树是一颗二叉搜索树。

**为什么叫二叉搜索树(二叉查找树)呢?**因为二叉搜索树擅长搜索和查找。如下图的二叉搜索树,我们要查找 10,首先与根节点比较,10 大于 9,那么就去他的右子树查找。右子树的根节点为 13 大于 10,那么就去他的左子树查找。左子树的根节点为 10 刚好等于 10,查找成功!如果节点不存在那么就是查找到树的叶节点还没有找到目标节点。

在这里插入图片描述

理想状态下,二叉搜索树查找的时间复杂度为:O(logN)。但是理想很丰满,现实很骨干。有一种情况能使得二叉搜索树变成链表,从而使得查找的时间复杂度变为 O(N)。

如下图,当依次向一颗空的二叉搜索树中插入有序的节点。那么这颗二叉搜索树就会变成链表。

在这里插入图片描述

**为什么叫二叉排序树呢?**那是因为一颗二叉搜索树的中序遍历的结果是有序的!中序遍历就是在递归遍历二叉树的时候先访问左子树,在访问根节点,最后才是右子树。不会中序遍历不要紧,等我们实现了一颗二叉搜索树,中序遍历一次你就知道他为啥叫二叉排序树了!

二叉搜索树的实现

二叉搜索树的基本结构

定义二叉搜索树的基本结构:

  • 首先,我们需要定义一个节点的类,表示二叉树的一个节点,成员变量就是左子树的节点指针,右子树的节点指针以及当前节点存储的值。构造函数的话,就是传入一个 key 用来初始化节点的 _key 就行啦!指针全部初始化为 nullptr
  • 然后就要定义二叉搜索树的类啦,里面会封装各种操作的函数。至于成员变量,当然就是根节点的指针啦!至于构造函数,一开始是一颗空的二叉搜索树嘛,将根节点的指针初始化为 nullptr 就行啦!
template<class K>
struct BSTNode
{BSTNode<K>* _left;BSTNode<K>* _right;K _key;BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}
};template<class K>
class BSTree
{typedef BSTNode<K> Node;
public:BSTree():_root(nullptr){}private:Node* _root;
};

bool insert(const K& key)

在我们实现的二叉搜索树中,一颗树中是没有相同节点的。

插入的过程其实很简单呢?将待插入的节点 key 与根节点的 _key 作比较:

  • 如果待插入节点的 key 大于根节点的 _key,那么继续与右子树根节点的 _key 比较。
  • 如果带插入节点的 key 小于根节点的 _key,那么继续与左子树根节点的 _key 比较。
  • 如果遇到根节点为 nullptr,那么直接插入这个节点,从而完成二叉搜索树的插入操作。

在这里插入图片描述

bool insert(const K& key)
{Node* newNode = new Node(key); //构造新插入的节点if(_root == nullptr) //如果是一颗空的二叉搜索树,修改根节点就行了{_root = newNode;}else{Node* cur = _root; //新插入的节点每次都与 cur 比较,判断其插入位置Node* parent = nullptr; //记录上父节点,方便最后的插入while(cur){if(key > cur->_key) //key 大于 cur->_key 去右子树{parent = cur;cur = cur->_right;}else if(key < cur->_key) //key 小于 cur->_key 去左子树子树{parent = cur;cur = cur->_left;}else //相等的情况不存在,我们规定二叉搜索树中不存在 _key 值相同的节点,插入失败{return false; }}//确定插入的位置if(key > parent->_key)parent->_right = newNode;elseparent->_left = newNode;}//插入成功return true;
}

为了方便测试插入的结果是否正确,我们需要写一个中序遍历的函数,中序遍历在 C 语言阶段都是写过了的!忘记了的 uu 可以去复习复习。

C语言数据结构初阶(10)----二叉树的实现-CSDN博客

写成成员函数,通过类的对象调用,我们需要传参根节点的指针,但是这个成员变量是私有的!外面拿不到,你可以写一个函数获取根节点的指针,但是这里会有一个更加优雅的写法:

void inorder()
{_inorder(_root);cout << endl;
}void _inorder(Node* root)
{if(root == nullptr)return;_inorder(root->_left);cout << root->_key << " ";_inorder(root->_right);
}

中序遍历写好了,就方便我们测试一颗二叉树是不是二叉搜索树啦!二叉搜索树的中序遍历是有序的,中序遍历有序的树也是二叉搜索树。

#include<iostream>
using namespace std;
#include"BSTree.h"int main()
{BSTree<int> bst;int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};for(auto e : a)bst.insert(e);bst.inorder();return 0;
}

在这里插入图片描述

我们看到中序遍历的结果的确是有序的呢!那我们的插入函数就是没有问题的!

bool find(const K& key)

find 写起来比 insert 还简单哈!

  • 如果待查找的 key 比根节点的 _key 大,那么就去右子树查找。
  • 如果待查找的 key 比根节点的 _key 小,那么就去左子树查找。
  • 如果待查找的 key 与根节点的 _key 相等,那么查找成功。
  • 如果根节点为 nullptr 还没有查找成功,那么查找失败。
bool find(const K& key)
{Node* cur = _root;while(cur){if(key > cur->_key)cur = cur->_right;else if(key < cur->_key)cur = cur->_left;elsereturn true;}return false;
}

void erase(const K& key)

erase 接口是二叉搜索树中最难实现的接口呢!

删除二叉搜索树中的节点可以分为以下情况:

  • 如果删除节点的度为 0,即删除的节点没有左右孩子。那么直接删除这个节点,然后改变父节点指针的指向就可以啦!

    在这里插入图片描述

  • 如果删除节点的度为 1,即删除的节点有一个孩子。那么,删除这个节点之后,令父节点相应的指针指向该删除节点的孩子即可。

    什么是父节点的相应指针?

    • 如果待删除的节点是其父节点的右孩子,那么删除这个节点之后,令父节点的 _right 指针指向该删除节点的孩子即可。
    • 如果待删除的节点是其父节点的左孩子,那么删除这个节点之后,令父节点的 _left 指针指向该删除节点的孩子即可。

    在这里插入图片描述

  • 如果待删除节点的度为 2,即待删除的节点有两个孩子。此时我们选择使用替换法,即选择待删除节点的左右子树中的某一个节点来替代待删除节点的位置,最后删除那个被选择用来替代删除节点的节点即可!

    选择哪一个节点来替代待删除的节点呢?选择的依据就是,替代之后的树应满足二叉搜索树嘛!因此就会有两种选择的方式

    • 选择待删除节点的左子树中最大的那个节点。

    • 选择待删除节点的右子树中最小的那个节点。

      在这里插入图片描述

    例如:如上图,我们要删除 3 这个节点,可以选择左子树中最大的节点 1 来代替 3,或者选择右子树中最小的节点 4 来代替 3。这两种选法均可使得删除后的二叉树满足二叉搜索树的性质。

    在这里插入图片描述

    替换之后呢?要删除的节点的特性要么满足情况 1,要么满足情况 2。就很好办啦!

    怎么找到二叉搜索树中的最大节点与最小节点呢?

    • 二叉搜索树的最大节点位于整棵树的最右侧。
    • 二叉搜索树的最小节点位于整棵树的最左侧。

其实经过仔细的观察,我们发现第一种情况和第二种情况是可以合并的!第一种情况是删除后父节点指针置空;第二种情况是删除后,父节点指向不为空的那个节点!

那么合并之后就是:如果待删除的节点的左孩子为空,那么令父节点指向右孩子就可以啦,否则,令父节点指向左孩子。或者如果待删除的节点的右孩子为空,那么令父节点指向左孩子,否者指向右孩子。

一定要看代码中的注释哦!还有一部分的细节在注释里面提到了。

bool erase(const K& key)
{Node* prev = nullptr;Node* cur = _root;//找到要被删除的那个节点while(cur){if(key > cur->_key){prev = cur;cur = cur->_right;}else if(key < cur->_key){prev = cur;cur = cur->_left;}else //找到了那个要被删除的节点{   //左子树为空,链接右子树if(cur->_left == nullptr){//这里是删除根节点的特殊情况if(cur == _root)_root = cur->_right;else{//确定删除的节点位于其父节点的哪个位置if(prev->_right == cur)prev->_right = cur->_right;elseprev->_left = cur->_right;}}else if(cur->_right == nullptr) //右子树为空链接左子树{//处理删除根节点的特殊情况if(cur == _root)_root = cur->_left;else{//确定删除的节点位于其父节点的哪个位置if(prev->_right == cur)prev->_right = cur->_left;elseprev->_left = cur->_left;}}else{//找到左子树中较大的那个节点,作为替换的节点Node* leftMax = cur->_left;Node* parent = cur;while(leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}//交换swap(leftMax->_key, cur->_key);//处理特殊情况,左子树的最大节点不一定是其父节点的右孩子//当删除的节点的左孩子就是左子树的最大值,这就是那个特殊情况if(parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}//删除节点delete cur;return true;}   }return false;
}

在这里插入图片描述

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

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

相关文章

Ubuntu MySQL客户端功能介绍(mysql-client)mysql命令(mysql客户端命令)数据库导出、数据库导入

文章目录 Ubuntu MySQL客户端(mysql-client)功能介绍MySQL客户端与服务端服务器端&#xff08;MySQL Server&#xff09;客户端&#xff08;MySQL Client&#xff09; 安装MySQL客户端连接到MySQL服务器&#xff08;mysql -h host -u user -p&#xff09;执行SQL查询批处理模式…

利用chatgpt大语言模型来做数据预处理

数据预处理是机器学习中的一个重要步骤&#xff0c;包括数据清洗、数据转换、特征选择等。这些步骤通常需要人工进行&#xff0c;或者使用专门的数据预处理工具和库&#xff0c;如Python的Pandas库、Scikit-learn库等。 今天我们将利用chatgpt(国内版本-小策智能问答)的辅助帮…

Spring - 手写模拟Spring底层原理

手写Spring 定义配置类AppConfig ComponentScan("com.spring.zsj") public class AppConfig {Beanpublic ApplicationListener applicationListener() {return new ApplicationListener() {Overridepublic void onApplicationEvent(ApplicationEvent event) {System…

Fabric二进制添加排序节点

目录 一、准备orderer11.1、注册orderer11.2、登记orderer11.3、登记orderer1的tls 二、添加orderer1的tls到系统通道三、获取最新的系统通道配置四、启动orderer1五、添加orderer1的endpoint到系统通道六、添加orderer1的tls到应用通道七、添加orderer1的endpoint到应用通道八…

【案例】3D地球(vue+three.js)

需要下载插件 <template><div class"demo"><div id"container" ref"content"></div></div> </template> <script> import * as THREE from three; // import mapJSON from ../map.json; import { Or…

pytorch 笔记:KLDivLoss

1 介绍 对于具有相同形状的张量 ypred​ 和 ytrue&#xff08;ypred​ 是输入&#xff0c;ytrue​ 是目标&#xff09;&#xff0c;定义逐点KL散度为&#xff1a; 为了在计算时避免下溢问题&#xff0c;此KLDivLoss期望输入在对数空间中。如果log_targetTrue&#xff0c;则目标…

【mediasoup-sfu-cpp】4: SfuDemo:join并发布视频创建RTCTransport流程分析

【mediasoup-sfu-cpp】3: SfuDemo:加入会议 有点卡,在本篇进行日志流程分析。demo\controller/RoomsController.hpp 创建router create() config.mediasoup.routerOptions++++++:OnSuccess D:\XTRANS\soup\mediasoup-sfu-cpp\demo\controller/RoomsController.hpp: Line 71: …

数据库高速缓存配置

数据库一般都配置数据高速缓存&#xff0c;并且可以高速缓存中按页大小分不同的缓冲池。 Oracle&#xff1a; db_cache_size是指db_block_size对应的缓冲池&#xff0c;也可以指定非db_block_size的缓冲池&#xff0c;一般也都会再配置一个32K的缓冲池&#xff0c;两个缓冲池加…

(CV)论文列表

CNN卷积神经网络之SKNet及代码 https://blog.csdn.net/qq_41917697/article/details/122791002 【CVPR2022 oral】MixFormer: Mixing Features across Windows and Dimensions 【精选】【CVPR2022 oral】MixFormer: Mixing Features across Windows and Dimensions-CSDN博客

软考考前提醒:准考证打印、注意事项!

一、准考证打印 打印时间&#xff1a;10月30日~11月3日 重要提醒&#xff1a;打印入口即将关闭&#xff0c;还没打印的朋友要抓紧时间&#xff0c;以免无法参加考试&#xff01;&#xff01;&#xff01; 准考证打印流程 登录中国计算机技术职业资格网&#xff0c;点击【报…

新一代构建工具Vite-xyphf

一、什么vite? vite:是一款思维比较前卫而且先进的构建工具,他解决了一些webpack解决不了的问题——在开发环境下可以实现按需编译&#xff0c;加快了开发速度。而在生产环境下&#xff0c;它使用Rollup进行打包&#xff0c;提供更好的tree-shaking、代码压缩和性能优化&…

grafana docker安装

grafana docker安装 Grafana是一款用Go语言开发的开源数据可视化工具&#xff0c;可以做数据监控和数据统计&#xff0c;带有告警功能。目前使用grafana的公司有很多&#xff0c;如paypal、ebay、intel等。 Grafana 是 Graphite 和 InfluxDB 仪表盘和图形编辑器。Grafana 是开…

基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v8.0版已发布

关于MobileIMSDK MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架&#xff0c;超轻量级、高度提炼&#xff0c;一套API优雅支持 UDP 、TCP 、WebSocket 三种协议&#xff0c;支持 iOS、Android、H5、标准Java、小程序、Uniapp&#xff0c;服务端基于Netty编写。 工程…

计算机网络-应用层

文章目录 应用层协议原理万维网和HTTP协议万维网概述统一资源定位符HTML文档 超文本传输协议&#xff08;HTTP&#xff09;HTTP报文格式请求报文响应报文cookie 万维网缓存与代理服务器 DNS系统域名空间域名服务器和资源记录域名解析过程递归查询迭代查询 动态主机配置协议&…

每日刷题_

前k个高频元素 347. 前 K 个高频元素 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 一共有三种不同的题解&#xff1a; 1、把数据存到哈希表中&#xff0c;然后通过哈希表来排序&#xff0c;时间复杂度…

SpringCloud Alibaba Demo(Nacos,OpenFeign,Gatway,Sentinel)

开源地址&#xff1a; ma/springcloud-alibaba-demo 简介 参考&#xff1a;https://www.cnblogs.com/zys2019/p/12682628.html SpringBoot、SpringCloud 、SpringCloud Alibaba 以及各种组件存在版本对应关系。可参考下面 版本对应 项目前期准备 启动nacos. ./startup.c…

数据结构(超详细讲解!!)第十八节 串(堆串)

1.定义 假设以一维数组heap &#xff3b;MAXSIZE&#xff3d; 表示可供字符串进行动态分配的存储空间&#xff0c;并设 int start 指向heap 中未分配区域的开始地址(初始化时start 0) 。在程序执行过程中&#xff0c;当生成一个新串时&#xff0c;就从start指示的位置起&#…

AQS 框架、JUC常见并发包 简述

AQS&#xff08;AbstractQueuedSynchronizer&#xff09;是 Java 中的一个强大的同步框架&#xff0c;为我们提供了实现各种同步器的基础。在本篇博客中&#xff0c;我们将介绍 AQS 框架的基本原理&#xff0c;并探讨几个常见的 AQS 实现&#xff1a;ReentrantLock、CountDownL…

kotlin中集合操作符

集合操作符 1.总数操作符 any —— 判断集合中 是否有满足条件 的元素&#xff1b; all —— 判断集合中的元素 是否都满足条件&#xff1b; none —— 判断集合中是否 都不满足条件&#xff0c;是则返回true&#xff1b; count —— 查询集合中 满足条件 的 元素个数&#x…

python科研绘图:条形图

条形图&#xff08;bar chart&#xff09;是一种以条形或柱状排列数据的图形表示形式&#xff0c;可以显示各项目之间的比较。它通常用于展示不同类别的数据&#xff0c;例如在分类问题中的不同类别、不同产品或不同年份的销售数据等。 条形图中的每个条形代表一个类别或一个数…