【数据结构取经之路】二叉搜索树的实现

目录

前言

二叉搜索树

概念

性质

二叉搜索树的实现

结点的定义

插入

查找

删除

二叉搜索树完整代码


前言

首先,二叉搜索树是一种数据结构,了解二叉搜素树有助于理解map和set的特性。

二叉搜索树

概念

二叉搜索树又称二叉排序树,它也可能是一棵空树,走中序遍历得到的结果是有序的。

性质

二叉搜索树如果不为空,它应满足一下性质:

每一个节点都有一个键值,而且每一个键值唯一标识一个节点(即二叉搜索树所有节点中没有重复的值)

● 假设根节点的值为X,那么它的左子树上的所有节点的值都小于X,右子树上的所有的结点的值都大于X

● 根的左右子树都是二叉搜索树(递归性质)

二叉搜索树的实现

二叉搜索树的基本操作有插入、查找、删除,其中插入和查找好说,最麻烦的是它的删除操作。

结点的定义

template <class K>
struct BSTNode
{BSTNode<K>* _left;BSTNode<K>* _right;K _key;BSTNode(const K& key) :_left(nullptr), _right(nullptr), _key(key) {}
};

插入

例如要插入0,0比8小,往左走,遇到3,0比3小,往左走,遇到1,0比1小,往左走,遇到空, 此时可以进行插入了。 总结起来就是:遇到空就可以插入。

直接上代码,后边再总结细节~

bool Insert(const K& key)
{//如果根节点为空,第一个插入的结点就充当根节点if (_root == nullptr){Node* newNode = new Node(key);_root = newNode;return true;}Node* cur = _root;Node* parent = nullptr;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->_key == key,该值插入进入会破坏键值的唯一性,故插入失败}}//不知道上述代码是往左走到空还是往右走到空,//所以不确定是插入左边还是右边,需要判断Node* newNode = new Node(key);if (parent->_key > key)parent->_left = newNode;elseparent->_right = newNode;return true;
}

细节:

● 要记录当前结点的父节点,否则当前结点走到空了新插入的结点无法链接上

● 如果要插入的元素在树中已存在,应该禁止插入,以免破坏键值的唯一性

● 如果根节点为空,则第一个插入的结点充当根节点

● 在插入前,要判断插入左边还是右边,因为我们不知道是往左走到空还是往右走到空而跳出循环来到执行插入的代码的

查找

● 从根开始比较,比根大的往右走查找,比根小的往左走查找

● 最多查找高度次,走到空,还没找到,说明这个值不存在

代码:


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;elsereturn true;}return false;
}

删除

上面的都好说,删除才是重头戏。

首先,查找所删除的结点是否在二叉搜索树中,如果不存在则返回false,如果存在就有以下几种情况。

1)要删除的节点为叶子结点

2)要删除的结点只有左孩子或只有右孩子

3)要删除的结点左右孩子均存在

针对第一种情况,直接删除就好,没什么好说的。

针对第二种情况,请看下面分析。

例如要删除的结点为14,根据上图,我们只需要将结点10的指向调整一下,让它指向13就可以了。当然,我们还要记录被删除结点的父节点才可以正确调整指向。仔细想想,就会返现,情况1和情况2是可以一起处理的。

第三种情况:

 

例如要删除结点3,根据上图,如果我们直接删除肯定是不对的,得用替换法来删除。用谁来替换是有讲究的,我们可以选择使用节点3的左子树的最大值或者是节点3右子树的最小值,简记:左的最大,右的最小。处理成下面这样:

 处理成这样后,删除掉rightMin节点即可。当然,rightMin节点也可能有右孩子(它不可能有左孩子,因为我们找的是右子树的最小值,必然是右子树中的最左节点),我们在删除的时候考虑进去就好了,这个并不难解决。下面,我们按照上面的逻辑来把代码写出来~


bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;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 (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;delete cur;return true;}else if (cur->_right == nullptr){if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;delete cur;return true;}else{Node* rightMin = cur->_right;Node* rightMinParent = nullptr;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}//可以交换,也可以直接赋值cur->_key = rightMin->_key;rigntMinParent->_left = rightMin->_right;delete rightMin;return true;}}}return false;
}

我试着用上述代码把所有插入的结点都删掉,但是程序崩了~有以下几个原因。

在这种情况下,分析删除值为8的节点。该节点的parent为空, 程序执行下面的代码:

if (cur->_left == nullptr)
{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;delete cur;return true;
}

这时候就是对空指针的解引用了,程序当然会崩掉~所以需要对parent为空的情况单独处理。把上述代码改成这样:

if (cur->_left == nullptr)
{//左为空的情况习下,parent为可能空,即单支的树,所以需要判断if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;delete cur;return true;
}

上面分析的是只有左支的情况,只有右支的情况同样如此,需要做单独处理。

解决了只有单支的情况后,我又试着删除所有节点,程序还是崩了~原因如下:

3为要删除的结点。此时,按照代码逻辑,rightMin为6,rightMinParent为空, 后面执行这句代码:

rigntMinParent->_left = rightMin->_right;

 所以会崩掉~但此时我们可以看到,rightMin(节点6)的父亲结点为3,并不是空,所以rightMinParent初始化为空并不合理,应该初始化为cur。

完善到这步,也还有一个小问题,按照上面修改之后的逻辑,rightMin为6,rightMinParent为3,执行这句代码:

rigntMinParent->_left = rightMin->_right;

 我们可以很容易发现这是错的,应该是rightMinParent->_right = rightMin->_right; 所以我们还需要加判断,具体如下:

if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;
elserightMinParent->_right = rightMin->_right;

完整的修改后的代码如下:


bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;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){//左为空的情况习下,parent为可能空,即单支的树,所以需要判断if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;return true;}else{Node* rightMin = cur->_right;Node* rightMinParent = cur;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}//可以交换,也可以直接赋值cur->_key = rightMin->_key;//不一定是rigntMinParent的左边if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;return true;}}}return false;
}

二叉搜索树完整代码

#include <iostream>
using namespace std;template <class K>
struct BSTNode
{BSTNode<K>* _left;BSTNode<K>* _right;K _key;BSTNode(const K& key):_left(nullptr), _right(nullptr), _key(key) {}
};template <class K>
class BSTree
{typedef BSTNode<K> Node;
public:
bool Insert(const K& key)
{if (_root == nullptr){Node* newNode = new Node(key);_root = newNode;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}Node* newNode = new Node(key);if (parent->_key > key)parent->_left = newNode;elseparent->_right = newNode;return true;
}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;elsereturn true;}return false;}void InOrder(){_InOrder(_root);cout << endl;}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;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){//左为空的情况习下,parent为可能空,即单支的树,所以需要判断if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;return true;}else{Node* rightMin = cur->_right;//Node* rightMinParent = nullptr;Node* rightMinParent = cur;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;//不一定是rigntMinParent的左边if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;return true;}}}return false;}private:void _InOrder(Node* _root){if (_root == nullptr)return;_InOrder(_root->_left);cout << _root->_key << " ";_InOrder(_root->_right);}
private:Node* _root = nullptr;
};

完~

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

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

相关文章

【Caffeine】⭐️SpringBoot 项目整合 Caffeine 实现本地缓存

目录 &#x1f378;前言 &#x1f37b;一、Caffeine &#x1f37a;二、项目实践 2.1 环境准备 2.2 项目搭建 2.3 接口测试 ​&#x1f49e;️三、章末 &#x1f378;前言 小伙伴们大家好&#xff0c;缓存是提升系统性能的一个不可或缺的工具&#xff0c;通过缓存可以避免大…

java基础之接口

接口和抽象类很像&#xff0c;接口是把行为给抽象化&#xff0c;可以理解成一个抽象类抽象到极致的情况下&#xff0c;形成的类&#xff0c;也就是一个抽象类有且只有抽象方法的时候&#xff0c;就可以用接口来写。 一、抽象类与接口在书写上的异同 这是一个抽象类 public abst…

五、 计算机网络(考点篇)

1 网络概述和模型 计算机网络是计算机技术与通信技术相结合的产物&#xff0c;它实现了远程通信、远程信息处理和资源共享。计算机网络的功能&#xff1a;数据通信、资源共享、管理集中化、实现分布式处理、负载均衡。 网络性能指标&#xff1a;速率、带宽(频带宽度或传送线路…

什么是人力资源管理审计

企业管理者可以通过会计审计了解公司的财务状况&#xff0c;对企业同样重要的人力状况如何要怎样了解呢&#xff1f;要怎样提高人力资源部门的运行能力&#xff1f;如何实施各种人力资源功能&#xff1f; 相对与财务、会计审计而言&#xff0c;人力资源审计在我国管理层中还是一…

驱动电机液冷冷却系统

1.自然冷却 自然冷却也可以看作是被动散热&#xff0c;它是依靠驱动电机自身的硬件结构&#xff0c;把热量从里经由金属材料向外散热&#xff0c;所以也就不会造成太多的成本支出&#xff0c;但是整体的散热效果并不太好。 考虑到低成本的原因&#xff0c;自然冷却就不能加装…

【简历】重庆某一本大学:JAVA简历指导,中厂通过率较低

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 简历说明 这是一份重庆某一本大学Java同学的简历。那么因为学校是一个一本的学校&#xff0c;就先要确定就业层次在中厂或者大厂&#xff0c;但是…

串联式 VS 并联式电源连接拓扑

https://download.csdn.net/download/qq_42605300/89538758https://download.csdn.net/download/qq_42605300/89538758串联式电源连接拓扑&#xff1a; 缺点&#xff1a;公共阻抗耦合&#xff0c;引入更多共模干扰。 并联式(星型)电源连接拓扑&#xff1a; 缺点&#xff1a;接地…

【Python】基础语法(顺序语句、条件语句、循环语句)

一、顺序语句 默认情况下&#xff0c;Python 的代码执行顺序是按照从上到下的顺序&#xff0c;依次执行的。 编程是一件明确无歧义的事情&#xff0c;安排好任务的顺序&#xff0c;计算机才能够正确的进行执行。 二、条件语句 1、什么是条件语句 条件语句能够表达 “如果...&…

架构师机器学习操作 (MLOps) 指南

MLOps 是机器学习操作的缩写&#xff0c;是一组实践和工具&#xff0c;旨在满足工程师构建模型并将其投入生产的特定需求。一些组织从一些自主开发的工具开始&#xff0c;这些工具在每次实验后对数据集进行版本控制&#xff0c;并在每个训练周期后对检查点模型进行版本控制。另…

【qt】 QGridLayout布局管理器怎么用?

QGridLayout是 Qt 中的一个布局管理器&#xff0c;用于在窗口或对话框中创建网格布局。它将控件按照行和列的方式进行排列&#xff0c;使得界面更加整齐和有序。 可以用setSpacing()来设置各个主键之间的间距. 可以设置各组件之间的间隙和与窗口边界的边距. 用addWidget()来添…

AQS之ReentrantLock源码分析

目录 1. LockSupport 类 2. 如何设计一把独占锁&#xff1f; 3. 管程 — Java同步的设计思想 3.1 MESA模型 为什么条件队列的线程需要移到同步队列再唤醒运行&#xff1f; 4. AQS原理分析 4.1 什么是AQS 4.2 AQS核心结构 AQS内部维护属性volatile int state 4.3 AQS定义…

Android 10.0 SystemUI启动流程

1、手机开机后&#xff0c;Android系统首先会创建一个Zygote&#xff08;核心进程&#xff09;。 2、由Zygote启动SystemServer。 3、SystemServer会启动系统运行所需的众多核心服务和普通服务、以及一些应用及数据。例如&#xff1a;SystemUI 启动就是从 SystemServer 里启动的…

[web]-sql注入-白云搜索引擎

ctrlu查看源代码&#xff0c;发现前端有js过滤 <script>function myFunction(){var xdocument.getElementById("number").value;var adocument.getElementById("word").value;var ba.replace(/[\ |\~|\|\!|\|\#|\$|\%|\^|\&|\*|\(|\)|\-|\_|\|\…

通过vm可以访问那些属性——06

1.通过vue实例都可以访问那些属性&#xff1f;&#xff08;通过vm都可以vm.什么&#xff09; vue实例中的属性很多。有的以$开始&#xff0c;有的以_开始。 所有以$开始的属性&#xff0c;可以看做是公开的属性&#xff0c;这些属性是提供给程序员使用的 所有以_开始的属性&…

Redis学习笔记(个人向)

Redis学习笔记(个人向) 1. 概述 是一个高性能的 key-value 数据库&#xff1b;其具有以下三个特点&#xff1a; Redis支持数据的持久化&#xff0c;可以将内存中的数据保存在磁盘中&#xff0c;重启的时候可以再次加载进行使用。Redis不仅仅支持简单的key-value类型的数据&…

《昇思25天学习打卡营第19天|生成式-Pix2Pix实现图像转换》

学习内容&#xff1a;Pix2Pix实现图像转换 1.模型简介 Pix2Pix是基于条件生成对抗网络&#xff08;cGAN, Condition Generative Adversarial Networks &#xff09;实现的一种深度学习图像转换模型&#xff0c;该模型是由Phillip Isola等作者在2017年CVPR上提出的&#xff0c…

【YOLO系列】快速部署YOLOv5(Windows)

引言 在计算机视觉领域&#xff0c;目标检测是至关重要的任务之一&#xff0c;它涉及识别图像或视频中的对象&#xff0c;并将其分类和定位。近年来&#xff0c;**YOLO&#xff08;You Only Look Once&#xff09;**算法因其速度与精度的平衡而变得非常流行。在这篇博文中&…

防火墙NAT智能选举综合实验

目录 实验拓扑 实验要求 实验思路 实验配置 需求7 需求8 需求9 需求10 需求11 实验拓扑 实验要求 7.办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 8.分公司设备可以通过总公司的移动链路和电信链路访问到d…

电表及销售统计Python应用及win程序

暑假每天都要填表算账很烦躁&#xff0c;就整了个小程序来减轻压力 程序可以做到记录输入的每一条数据&#xff0c;并用新数据减去旧数据算新增的量&#xff0c;同时记录填写时间 Python代码 import json import os # 导入os模块 from datetime import datetime from tkint…

yolov8预测

yoloV8 官方地址 预测 -Ultralytics YOLO 文档 1.图片预测 from ultralytics import YOLO #### 图片预测1 ### https://www.youtube.com/watch?vneBZ6huolkg ### https://github.com/ultralytics/ultralytics ### https://github.com/abdullahtarek/football_analysis…