数据结构初阶 · 二叉搜索树

目录

前言:

二叉搜索树的实现

二叉搜索树的基本结构

中序遍历


前言:

在最初学习二叉树的时候,就提及到过单独用树来存储数据是既不如链表也不如顺序表的,二叉树的用处可以用来排序,比如堆排序,也可以用来搜索数据,这是二叉树的用处,用来排序可以实现堆,用来搜索数据可以实现二叉搜索树,即今天实现的一种结构。

那么什么是二叉搜索树呢?

即左孩子比根小,右孩子比根大,且所有的子树都满足这个特点,这就是二叉搜索树,那么是如何实现搜索数据的呢?

搜索数据就是判断大小,最多走高度次个语句就可以找到数据了。

那么找数据的时间复杂度是不是O(logn)呢?很显然不是,万一存在只有左子树或者只有右子树有节点的树呢?那样的话时间复杂度就是O(N)了,所以时间复杂度是O(logN ~ N)。

话不多说,现在开始实现。

二叉搜索树的实现

二叉搜索树的基本结构

template <class T>
struct BSTreeNode
{BSTreeNode<T>* _left;BSTreeNode<T>* _right;T _key;BSTreeNode(const T& key):_left(nullptr), _right(nullptr), _key(key){}
};template <class T>
class BSTree
{
public:typedef BSTreeNode<T> Node;private:Node* _root = nullptr;};

这是二叉搜索树的基本结构,每个节点都是一个结构体,好奇的人可能会问为什么值不是val而是key?这是因为二叉搜索树有两个模型,一个是key模型,一个是key-value模型,在key模型中,是不能修改数据的,因为一旦修改了数据整个树的结构就很容易被打乱,在key-value模型中,就可以修改数据,比如有一个数据集合,每个节点都有key和value,每存在一个key,value就++,所以key-value模型中能修改数据,但是修改的是value,即值出现的次数,总结就是能修改的数据就是对整棵树的结构没有影响的数据。

增的基本逻辑就是,如果比当前位置的值大,就走右子树,如果比当前位置的值小,就走左子树,如果该树是一个空树,那么这个值就充当根节点。当走到空了,我们就应该考虑连接的部分了,连接的时候,我们需要父节点,判断该值和父节点的大小,再使父节点的左右指针指向这个节点,既然需要父节点,我们这个时候就需要存储父节点的位置,每当走下个节点的时候,就存储一下父节点的位置,基本逻辑就这么多:

bool Insert(const T& val)
{if (_root == nullptr){_root = new Node(val);return true;}Node* root = _root;Node* parent = nullptr;//判断部分while (root){if (val > root->_key){parent = root;root = root->_right;}else if (val < root->_key){parent = root;root = root->_left;}else{return false;}}Node* newnode = new Node(val);//连接部分 开始判断大小关系if (parent->_key > val){parent->_left = newnode;}else{parent->_right = newnode;}return true;
}

当然,为了方便,我们都写成了成员函数。

这里有个问题就是,如果存在两个相同的数据怎么办?

实际来说二叉搜索树是不允许存在相同的数据的,这样导致了数据冗余,就像字典里面,存在相同的两个单词吗?不会的是吧,所以我们就不考虑多种相同数据的情况,代码里面返回的就是false。

查就很简单了,查就是增的部分代码,遍历一遍,比较有没有这个值就行,遍历多简单,小就走左子树,大就走右子树,相等就返回true:

//查
Node* FindKey(const  T& val)
{Node* root = _root;while (root){if (val > root->_key){root = root->_right;}else if (val < root->_left){root = root->_left;}else{return root;}}return nullptr;
}

中序遍历

数据加上了,也可以查数据了,我们现在想把数据打印出来看一下怎么办呢?这里推荐使用中序遍历,左子树根右子树这样的顺序打印,因为二叉搜索树的特性,这里打印出来就是升序,看着较为顺眼:

//中序遍历
void InOrder()
{_InOrder(_root);cout << endl;
}
private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}

感觉奇怪吧?为什么InOrder的参数不用Node* root呢?因为存在this指针,this指针是在参数的第一个位置,如果我们传参,传的是根,接受到的还是this指针,就冲突了,所以这里有几个办法,第一个是用一个get set函数,这个方法java比较喜欢使用,第二个方法就是设为私有,套一层函数使用吗,私有函数就没有this指针了。

到现在是不是都感觉二叉搜索树没啥?那是因为还没有到删除部分。删除部分才是二叉搜索树的核心。

给定一个二叉搜索树,删除可以分为以下几种情况,第一种情况是删除7 和 14,第二种情况是删除3 和 8。

第一种情况是属于可以直接删除的情况。

对于直接删除的情况,我们分为左右指针都为空,左指针为空,右指针为空的三种情况,实际上,我们可以只分为两种情况,第一种是左指针为空,第二种是右指针为空,比如7,删除7就是让6指向7的任意左右指针就可以了,删除14,我们需要让10的右指针指向13,有一个点就是为什么10指向的地方一定是比10大的?因为二叉树的特性,如果是9,就一定不会在10的下面。

我们可以总结以下,删除的时候,先判断是左为空还是右为空,然后判断子节点和父节点的位置,这样好让父节点指向下一个指针,连接的主要根据就是判断子节点和父节点相对位置。

如果两个都为空怎么办?我们已知一个节点不为空,另一个节点为不为空我们都指向它,总归是没错的。这点可以反证。当我们删除的是根节点的时候,只需要让根节点指向的内容是空就可以了,所以无论我们把删除根节点的位置放在左为空还是右为空都没问题。

到这里两个都为空的问题也就顺理成章的解决了,两个都为空,来就直接走左为空的场景,判断相对位置,父节点连接子节点的右节点,连接的是空指针,解决了就。

这部分的代码如下:

Node* parent = nullptr;
Node* cur = _root;
//先找到 找到该节点才能删除
while (cur)
{if (cur->_key < val){parent = cur;cur = cur->_right;}else if (cur->_key > val){parent = cur;cur = cur->_left;}//找到了 开始删除else{///第一种情况:左为空 -> 都为空if (cur->_left == nullptr){//删除根节点的时候if (cur == _root){cur = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}///第二种情况:右为空else if (cur->_right == nullptr){if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}
}

第二种情况是删除3 和 8 的情况,这种就要麻烦一点,删除的话得用替换法,因为我们没有办法之直接删除它,那么是怎么个替换法呢?我们从树里面找一个数据,满足大于该节点的左节点,小于该节点的右节点,就算是替换完成了。

那么从哪里找这种适配的数据呢?当然是从该节点的左右子树去找了,我们找右子树的最小值,或者是左子树的最大值都可以满足,右子树的最小值,即比右节点的值小,但是同时比左节点大,这就满足了,找到了该值之后,我们要做的是交换数据,交换了数据之后,我们应该怎么样删除右子树的最小值的节点呢?有人提议说用递归删除,比如删除3,用4进行替换,我们删除得先找到这个数据吧,关键问题是根本找不到这个数据,因为交换了数据之后树的结构算是被轻微破坏了,所以我们想要删除就让它的父节点指向空就可以了,此时也要判断一下相对位置即可,总体删除代码如下:

	bool EraseKey(const T& val){Node* parent = nullptr;Node* cur = _root;//先找到 找到该节点才能删除while (cur){if (cur->_key < val){parent = cur;cur = cur->_right;}else if (cur->_key > val){parent = cur;cur = cur->_left;}//找到了 开始删除else{///第一种情况:左为空 -> 都为空if (cur->_left == nullptr){//删除根节点的时候if (cur == _root){cur = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}///第二种情况:右为空else if (cur->_right == nullptr){if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}///左右都不为空 -> 替换法else{Node* rightMinParent = cur;Node* rightMin = cur->_right;//找右子树的最小值while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}swap(rightMin->_key, cur->_key);if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;}return true;}}return false;}

最后父节点也可以直接指向空的,但是为了代码的美观性,这样写也不是不行。


感谢阅读!

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

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

相关文章

java-数据结构与算法-02-数据结构-05-栈

文章目录 1. 栈1. 概述2. 链表实现3. 数组实现4. 应用 2. 习题E01. 有效的括号-Leetcode 20E02. 后缀表达式求值-Leetcode 120E03. 中缀表达式转后缀E04. 双栈模拟队列-Leetcode 232E05. 单队列模拟栈-Leetcode 225 1. 栈 1. 概述 计算机科学中&#xff0c;stack 是一种线性的…

netty入门-3 EventLoop和EventLoopGroup,简单的服务器实现

文章目录 EventLoop和EventLoopGroup服务器与客户端基本使用增加非NIO工人NioEventLoop 处理普通任务与定时任务 结语 EventLoop和EventLoopGroup 二者大概是什么这里不再赘述&#xff0c;前一篇已简述过。 不理解也没关系。 下面会简单使用&#xff0c;看了就能明白是什么 这…

第124天:内网安全-代理 Sockets协议路由不出网后渗透通讯CS-MSF 控制上线

目录 思维导图 环境配置 案例一&#xff1a;网络通讯&控制上线--CS-路由添加&节点建立&协议生成&正反连接 案例二&#xff1a;网络通讯&控制上线--MSF-路由添加&节点建立&协议生成&正反连接 思维导图 环境配置 这里由于系统内存问题我只设…

Python的人脸识别程序

1.录入人脸&#xff0c;输入ID号 haarcascade_frontalface_default.xml # 导入模块 import os import numpy as np import cv2 as cv import cv2face_detector cv2.CascadeClassifier(rD:\Automation_All_Files\OCR\haarcascade_frontalface_default.xml) # 待更改# 为即将…

Windows10+vs 2017中创建WEB API教程

我们如果需要用到web api怎么办&#xff1f;一般来说可以自己开发和去使用别人开发好的api&#xff0c;今天我们来讲一下Windows10vs 2017中创建web Api的教程。目前本教程当中的方法在Win10 VS2017&#xff08;MVC5&#xff09;win server2016vs2017&#xff0c;vs2013 vs201…

网安人必须人手一份的《Linux私房教程》,GitHub星标286K!

Linux是一套免费使用和自由传播的操作系统内核&#xff0c;是一个基于POSIX和Unix的多用户、多任务支持多线程和多CPU的操作系统内核。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想&#xff0c;是一个性能稳…

【iOS】GCD

参考文章&#xff1a;GCD函数和队列原理探索 之前写项目的时候&#xff0c;进行耗时的网络请求使用GCD处理过异步请求&#xff0c;但对一些概念都很模糊&#xff0c;这次就来系统学习一下GCD相关 相关概念 什么是GCD&#xff1f; Grand Center Dispatch简称GCD&#xff0c;是…

ChatTTS真人文本转语音模型,富有韵律与情感,且免费开源

上期图文教程&#xff0c;我们分享了微软TTS真人转语音大模型&#xff0c;但是微软的TTS模型只有针对新用户免费一年&#xff0c;其他用户都是收费的&#xff0c;虽然微软开源了部分TTS的功能&#xff0c;但是针对真人类似的富有情感的TTS模型并没有进行开源&#xff0c;本期介…

软件测试基础1--功能测试

1、什么是软件测试&#xff1f; 软件是控制计算机硬件运行的工具。 软件测试&#xff1a;使用技术手段验证软件是否满足使用需求&#xff0c;为了发现软件功能和需求不相符合的地方&#xff0c;或者寻找实际输出和预期输出之间的差异。 软件测试的目的&#xff1a;减少软件缺陷…

学习笔记之JAVA篇(0724)

p 方法 方法声明格式&#xff1a; [修饰符1 修饰符2 ...] 返回值类型 方法名&#xff08;形式参数列表&#xff09;{ java语句;......; } 方法调用方式 普通方法对象.方法名&#xff08;实参列表&#xff09;静态方法类名.方法名&#xff08;实参列表&#xff09; 方法的详…

【YashanDB知识库】YashanDB的JDBC/OCI驱动如何设置字符编码

问题现象 Oracle、Mysql数据库链接串&#xff0c;JDBC驱动连接串可以指定客户端的编码格式&#xff1a; jdbc:mysql://hostname:port/database_name?useUnicodetrue&characterEncodingutf8mb4 jdbc:oracle:thin://hostname:port/service_name?NLS_LANGUAGEAMERICAN&am…

【SQL语句大全(MySQL)】

SQL语法 添加删除修改查询基本查询条件查询分组函数/聚合函数分组查询排序分页查询&#xff08;限制查询&#xff09;多表查询连接查询根据年代分类连接查询根据连接方式分类1、内连接2、左外连接3、右外连接 多张表连接的语法格式 嵌套查询 SQL语句书写顺序 添加 INSERT INTO…

构建生成工具cmake的使用(1)

ps:本文是对cmake的基础讲解&#xff0c;掌握后解决70-80%情况是足以应对的&#xff0c;后续会对cmake有进阶内容。 一 前言 CMake 是一个工具&#xff0c;帮助开发者管理和自动化软件项目的构建过程。它使用一个叫做CMakeLists.txt 的文本文件来描述项目的组织结构、编译选项…

oracle数据库下的定时任务,如何创建Jobs

oarcle中写存储过程&#xff0c;上面的文章中已经介绍过了&#xff0c;这次是写好存储过程后&#xff0c;在Jobs文件夹中新建job,达到定时执行任务的目的。 1、在plsql数据库中&#xff0c;找到左边的Jobs,右键点击新建&#xff0c;如下图&#xff1a; 2、按照下图将相应的项添…

【Gitlab】SSH配置和克隆仓库

生成SSH Key ssh-keygen -t rsa -b 4096 私钥文件: id_rsa 公钥文件:id_rsa.pub 复制生成的ssh公钥到此处 克隆仓库 git clone repo-address 需要进行推送和同步来更新本地和服务器的文件 推送更新内容 git push <remote><branch> 拉取更新内容 git pull &…

LINUX高性能服务器框架

1.服务器模型&#xff1a; 1). C/S模型 &#xff1a; 此模型就是一个服务器能给多个客户端提供服务&#xff0c;但所以资源都被服务端所占有&#xff0c;客户端想要获取只能通过请求连接服务端去获取。由于客户端的请求访问是异步的&#xff0c;所以需要一个手段进行此类事件的…

基于微信小程序的课堂考勤系统的设计与实现(论文+源码)_kaic

基于微信小程序的课堂考勤系统的设计与实现 摘 要 在高校教育普及的今天&#xff0c;学生人数日益增多&#xff0c;为保证课堂质量&#xff0c;教师多要在课前进行考勤。因此本设计提出基于微信小程序的课堂考勤系统&#xff0c;增加了定位功能&#xff0c;避免了“假打卡”…

前端开发知识(一)-html

1.前端开发需掌握的内容&#xff1a; 2.前端开发的三剑客&#xff1a;html、css、javascript Vue可以简化JavaScpript流程。 Element&#xff08;饿了么开发的&#xff09; &#xff1a;前端组件库。 Ngix&#xff1a;前端服务器。 3.前端开发工具&#xff1a;vscode 1)按…

基于Java+SpringBoot+Vue的学生心理咨询评估系统

前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 哈喽兄弟们&#xff0c;好久不见哦&#xff5…

乐尚代驾六订单执行一

加载当前订单 需求 无论是司机端&#xff0c;还是乘客端&#xff0c;遇到页面切换&#xff0c;重新登录小程序等&#xff0c;只要回到首页面&#xff0c;查看当前是否有正在执行订单&#xff0c;如果有跳转到当前订单执行页面 之前这个接口已经开发&#xff0c;为了测试&…