c++实现B树(下)

news2024/12/23 14:19:28

 书接上回小吉讲的是B树的搭建和新增方法的实现(blog传送门🚪:B树实现上)(如果有小可爱对B树还不是很了解的话,可以先看完上一篇blog,再来看小吉的这篇blog)。那这一篇主要讲的是B树中删除操作的实现。
 在看小吉的数据结构与算法(c++实现)系列博客中,各位小可爱们应该能发现,其实在实现一个数据结构时,删除操作普遍比新增操作要难。小吉浅浅预告一下B树的删除操作可以说是天花板级别的难。但是各位小可爱们不要怕,跟着小吉的博客看下去B树删除易如反掌(哈哈,夸张了🤣)

在实现B树删除操作之前,小吉先在这声明一下:B树的删除是删除某个节点的key并不是将节点删除(无需使用delete)

九个成员方法

 在正式分析B树删除之前,先要实现9个方法(不要害怕,这九个方法都非常简单),这九个方法在后期实现删除操作代码时大有用处(可以小小期待一下)

int removeKey(int index);//移除指定index处的key
int removeRightmostKey();//移除最右边的key
Node* removeChild(int index);//移除指定index处的child
Node* removeLeftmostChild();//移除最左边的child
Node* removeRightmostChild();//移除最右边的child
Node* childLeftSibling(int index);//index孩子处左边的兄弟
Node* childRightSibling(int index);//index孩子处右边的兄弟
void moveToTarget(Node* target);//复制当前节点的所有key和child到target

这些方法都定义在Node节点类中,这些方法都比较简单,小吉在这就不详细讲解了,直接上代码。

int Node::removeKey(int index)
{
	int t = _keys[index];
	_keys.erase(_keys.begin() + index);
	KeyNumber--;
	return t;
}

int Node::removeLeftmostKey()
{
	return removeKey(0);
}

int Node::removeRightmostKey()
{
	return removeKey(KeyNumber - 1);
}

Node* Node::removeChild(int index)
{
	Node* t = _children[index];
	_children.erase(_children.begin() + index);
	return t;
}

Node* Node::removeLeftmostChild()
{
	return removeChild(0);
}

Node* Node::removeRightmostChild()
{
	return removeChild(KeyNumber - 1);
}

Node* Node::childLeftSibling(int index)
{
	return index > 0 ? _children[index - 1] : nullptr;
}

Node* Node::childRightSibling(int index)
{
	return index == KeyNumber ? nullptr : _children[index + 1];
}

void Node::moveToTarget(Node* target)
{
	int start = target->KeyNumber;
	if (!this->_leaf)
	{
		for (int i = 0; i <= this->KeyNumber; i++)
		{
			target->_children[start + i] = this->_children[i];
		}
	}
	for (int i = 0; i < this->KeyNumber; i++)
	{
		target->_keys[target->KeyNumber++] = this->_keys[i];
	}
}

搭建remove删除方法的架子

首先明确一点要删除节点的前提是要先找到节点才能进行删除操作,所以remove方法最初的雏形就是找要删除的节点(采用递归进行查找)

 找节点的思路在上一篇blog新增节点时有体现,小吉在这也不详细讲解了,直接上代码👇

void BTree::remove(int key)
{
	doremove(_root, key,0);
}

void BTree::doremove(Node* node, int key)
{
	int i = 0;
	while (i < node->KeyNumber)
	{
		if (node->_keys[i] >= key)
		{
			break;
		}
		i++;
	}

	if (node->_leaf)//叶子节点
	{
		if (i < node->KeyNumber && node->_keys[i] == key)//i找到:代表待删除key的索引
		{
			
		}
		else//i没找到
		{
			return;
		}
	}
	else//非叶子节点
	{
		if (i < node->KeyNumber && node->_keys[i] == key)//i找到:代表待删除key的索引
		{
			
		}
		else//i没找到:代表第i个孩子继续查找
		{
			doremove(node,node->_children[i], key,i);
		}
	}
}

remove删除情况分类

主要分为以下4种情况:
case1:当前节点是叶子节点,没找到
case2:当前节点是叶子节点,找到了
case3:当前节点是非叶子节点,没找到
case4:当前节点是非叶子节点,找到了

叶子节点

 分析当前节点是叶子节点的情况,这种情况比较简单,找到就删除当前节点要删除的key值(调用前面实现的九个小方法中的removeKey方法即可),没找到说明B树中没有我们要删除的key值,直接return退出即可。

if (node->_leaf)//叶子节点
	{
		if (i < node->KeyNumber && node->_keys[i] == key)//i找到:代表待删除key的索引
		{
			node->removeKey(i);
		}
		else//i没找到
		{
			return;
		}
	}
非叶子节点

没找到:递归继续寻找
找到:1.找后继的key 2.替换待删除的key 3.删除后继的key
非叶子节点删除

else//非叶子节点
	{
		if (i < node->KeyNumber && node->_keys[i] == key)//i找到:代表待删除key的索引
		{
			//1.找到后继key
			Node* s = node->_children[i + 1];
			while (!s->_leaf)
			{
				s = s->_children[0];
			}
			int skey = s->_keys[0];
			//2.替换待删除key
			node->_keys[i] = skey;
			//3.删除后继key
			doremove(node,node->_children[i + 1], skey,i+1);
		}
		else//i没找到:代表第i个孩子继续查找
		{
			doremove(node,node->_children[i], key,i);
		}
	}

删除完不平衡的处理

  在删除节点完可能存在删除后key数目<下限(根节点除外),导致不平衡的情况
 对平衡的调整又可以分为一下三种情况:
case1:左边富裕,右旋
case2:右边富裕,左旋
case3:两边都不够借,向左合并
 void balance (Node* parent,Node* x,int i) //x为要调整的节点,i为被调整孩子的索引
 注: 为了实现平衡函数的传参,doremove方法还要再加上两个参数void BTree::doremove(Node* parent,Node* node, int key,int index)//index被删除节点的索引

左边富裕,右旋

右旋

右旋:1)父节点中前驱key旋转下来(前驱key是要删除节点的前驱)
2)left中最大的孩子换爹(被调整节点的左兄弟不为叶子节点要执行这一步)
2)left中最大的的key旋转上去

下面👇是代码实现旋转的过程:

void BTree::balance(Node* parent, Node* x, int i)
{
    Node* left = parent->childLeftSibling(i);
	if (left != nullptr && left->KeyNumber > MIN_KEYNUMS)
	{//左边富裕,右旋
		x->insertKey(0, parent->_keys[i - 1]);
		if (!left->_leaf)
		{
			x->insertChild(0, left->removeRightmostChild());
		}
		parent->_keys[i-1]= left->removeRightmostKey();
		return;
	}
}
右边富裕,左旋

左旋:1)父结点中后继key旋转下来(后继key是要删除节点的后继)
2)right中最小的孩子换爹(被调整节点的右兄弟不为叶子节点时要执行这一步)
3)right中最小的key旋转上去

 和左边富裕,右旋的情况类似,小吉这里就不画图了,直接上代码

void BTree::balance(Node* parent, Node* x, int i)
{
    Node* right = parent->childRightSibling(i);
    if (right != nullptr && right->KeyNumber > MIN_KEYNUMS)
	{//右边富裕,左旋
		x->insertKey(x->KeyNumber, parent->_keys[i]);
		if (!right->_leaf)
		{
			x->insertChild(x->KeyNumber+1, right->removeLeftmostChild());
		}
		parent->_keys[i] = right->removeLeftmostKey();
		return;
	}
两边都不够借,向左合并

case1:被调整节点有左兄弟,往左兄弟处合并
case2:被调整节点没有左兄弟,父节点和右边的兄弟向自己处合并

case1:
向左合并
case2:
向自己处合并
代码呈现👇:

//两边都不够借,向左合并
	if (left != nullptr)
	{//向左兄弟合并
		parent->removeChild(i);
		left->insertKey(left->KeyNumber, parent->removeKey(i - 1));
		x->moveToTarget(left);
	}
	else
	{//向自己合并
		parent->removeChild(i + 1);
		x->insertKey(x->KeyNumber, parent->removeKey(i));
		right->moveToTarget(x);
	}

到这里,B树删除的所有涉及到的知识点都已经讲完了,下面小吉给大家提供一个B树删除的测试代码。

B树删除的测试代码

void testRemove()
{
	BTree tree(3);
	for (int i = 1; i <= 6; i++)
	{
		tree.put(i);
	}
	tree.remove(2);
	Node* root = tree._root;
	Node* leftchild = root->_children[0];
	cout << root->_keys[0] << endl;
	for (int i = 0; i < leftchild->KeyNumber; i++)
	{
		cout << leftchild->_keys[i] << ' ';
	}
	cout << endl;
}

到这里,这篇blog要结束了,有点小长,也有点小难,可能一次性看完对一些小可爱们有难度,不要急,慢慢看,多给自己一点时间❤️
(其实这篇博客从9月份写到了11月🤣,可以说是小吉已经发表的博客中最长的一篇了,也是最难的一篇,原计划打算九月份写完的,中途去备考了一下软设,所以就一直拖到现在才写完,不好意思耽误了这么久了😖)

&emps;最后,创作不易(这篇blog小吉真的写了很久),还望大家多多支持(点赞收藏关注小吉🌹)

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

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

相关文章

【漏洞分析】Fastjson最新版本RCE漏洞

01漏洞编号 CVE-2022-25845CNVD-2022-40233CNNVD-202206-1037二、Fastjson知多少 万恶之源AutoType Fastjson的主要功能是将Java Bean序列化为JSON字符串&#xff0c;这样得到的字符串就可以通过数据库等方式进行持久化了。 但是&#xff0c;Fastjson在序列化及反序列化的过…

PSRAM,Flash,SRAM,ROM有什么区别

PSRAM、Flash、SRAM 和 ROM 是四种不同类型的存储器&#xff0c;它们在计算机和嵌入式系统中的用途、特性和工作方式各不相同。下面是这四种存储器的区别和各自的特点&#xff1a; ### 1. **SRAM&#xff08;静态随机存取存储器&#xff09;** - **特性**&#xff1a; - **易…

大数据学习13之Scala基础语法(重点)

1. 简介 Scala 是 Scalable Language 的简写&#xff0c;是一门多范式的编程语言。创始人为 Martin Odersky 马丁奥德斯基。 Scala 这个名字来源于 Scalable Language(可伸缩的语言&#xff09;&#xff0c;它是一门基于 JVM 的多范式编程语言&#xff0c;通俗的说&#xff1a;…

django入门【05】模型介绍——字段选项(二)

文章目录 1、null 和 blank示例说明⭐ null 和 blank 结合使用的几种情况总结&#xff1a; 2、choices**choices 在 Django 中有以下几种形式&#xff1a;**&#xff08;1&#xff09; **简单的列表或元组形式**&#xff08;2&#xff09; **字典映射形式**&#xff08;3&#…

微信小程序:vant组件库安装步骤

前言&#xff1a;在微信小程序中引用vant组件报错&#xff0c;提示路径不存在&#xff0c;这很有可能是因为没有安装构建vant组件库导致。下面是我整理的安装vant组件库的步骤: 第一步&#xff1a;安装node.js(执行完第一步请重启小程序) 具体步骤请看链接&#xff1a;node.js…

Python如何根据给定模型计算权值

目录 一、特征权重的重要性 二、线性回归中的特征权重计算 1. 导入必要的库 2. 创建示例数据集 3. 分割数据集 4. 训练线性回归模型并计算权重 三、特征选择方法 四、实际案例&#xff1a;金融科技数据集 五、总结 在机器学习中&#xff0c;特征权重的计算是理解模型如…

过去几年电子学习的趋势

近年来&#xff0c;在技术和不断变化的学习者期望的推动下&#xff0c;电子学习已经发展成为一种适应性强、沉浸式和社会化的教育形式。个性化已成为最具影响力的趋势之一&#xff0c;Coursera和LinkedIn Learning等平台为个人量身定制内容。这些平台使用人工智能来建议课程、跟…

面相小白的php反序列化漏洞原理剖析

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理反序列化漏洞的一些成因原理 建议学习反序列化之前 先对php基础语法与面向对象有个大体的了解 (我觉得我整理的比较细致&#xff0c;了解这俩是个啥就行) 漏洞实战情况 这个漏洞黑盒几乎不会被发现&am…

Flutter中的Material Theme完全指南:从入门到实战

Flutter作为一款热门的跨平台开发框架&#xff0c;其UI组件库Material Design深受开发者喜爱。本文将深入探讨Flutter Material Theme的使用&#xff0c;包括如何借助Material Theme Builder创建符合产品需求的主题风格。通过多个场景和代码实例&#xff0c;让你轻松掌握这一工…

IDC机房服务器托管的费用组成

IDC机房服务器托管的费用&#xff0c;并不是只有我们所想的电费而已&#xff0c;还有一些其它费用组成&#xff0c;详细来看&#xff1a; 1. 机位费用&#xff1a;   - 机位费用是根据服务器的尺寸和占用的空间来计算的。服务器通常按照U&#xff08;Unit&#xff09;的高度来…

032集——圆转多段线(Circle to Polyline)(CAD—C#二次开发入门)

CAD中圆可转为带有凸度的多段线以方便后期数据计算、处理&#xff0c;效果如下&#xff1a; 白色为圆&#xff0c;红色为转换后的多段线&#xff08;为区分&#xff0c;已手工偏移多段线&#xff09; public static void XX(){var curves Z.db.SelectEntities<Entity>…

Nginx更换ssl证书不生效

一.场景 在用的ssl证书要过期了&#xff0c;申请了新的ssl证书下来&#xff0c;在nginx配置上更换上去后&#xff0c;打开系统地址&#xff0c;一依然是使用原来的旧证书&#xff0c;以前有更换过别的域名证书&#xff0c;重启nginx服务后立马就生效了。 这次没生效&#xff…

华为eNSP:MSTP

一、什么是MSTP&#xff1f; 1、MSTP是IEEE 802.1S中定义的生成树协议&#xff0c;MSTP兼容STP和RSTP&#xff0c;既可以快速收敛&#xff0c;也提供了数据转发的多个冗余路径&#xff0c;在数据转发过程中实现VLAN数据的负载均衡。 2、MSTP可以将一个或多个VLAN映射到一个Inst…

Jmeter中的配置原件(二)

5--HTTP请求默认值 用途 设置默认值&#xff1a;为多个HTTP请求设置通用的默认值&#xff0c;如服务器地址、端口号、协议等。简化配置&#xff1a;避免在每个HTTP请求中重复配置相同的参数。 配置步骤 添加HTTP请求管理器 右键点击线程组&#xff08;Thread Group&#xff…

SpringBoot(二十一)SpringBoot自定义CURL请求类

在测试SpringAi的时候,发现springAI比较人性化的地方,他为开发者提供了多种请求方式,如下图所示: 上边的三种方式里边,我还是喜欢CURL,巧了,我还没在Springboot框架中使用过CURL呢。正好封装一个CURL工具类。 我这里使用httpclient来实现CURL请求。 一:添加依赖 不需要…

空空想色?李子柒 想念你们!——早读(逆天打工人爬取热门微信文章解读)

空空想色 引言Python 代码第一篇 李子柒 想念你们&#xff01;第二篇 什么叫个性命双休结尾 引言 又开始新的尝试 最近看了坛经 所以现在佛性满满 看到很多sese的图 现在基本不会有什么想法了 以前看不懂呀 现在是借着王德峰的讲解勉强看懂 后面也会越来越懂 总之就是 空空 …

高频旁路电容选型注意事项

1. 前置频率倍减器 图1是用于1.9GHz频带的PLL信号发生器使用的前置频率倍减器的电路图。在这种高频率中&#xff0c;普通PLL用可编程序计数器不工作&#xff0c;而是把ECL等前置频率倍减器连接在前段后分频。 这种例子的分频比为1/256。例如&#xff1a;1.920GHz的输入信号分…

Android Studio | 修改镜像地址为阿里云镜像地址,启动App

在项目文件的目录下的 settings.gradle.kts 中修改配置&#xff0c;配置中包含插件和依赖项 pluginManagement {repositories {maven { urluri ("https://www.jitpack.io")}maven { urluri ("https://maven.aliyun.com/repository/releases")}maven { urlu…

PDF24:多功能 PDF 工具使用指南

PDF24&#xff1a;多功能 PDF 工具使用指南 在日常工作和学习中&#xff0c;PDF 是一种常见且重要的文档格式。无论是查看、编辑、合并&#xff0c;还是转换 PDF 文件&#xff0c;能够快速高效地处理 PDF 文档对于提高工作效率至关重要。PDF24 是一款免费、功能全面的 PDF 工具…

opencv实时弯道检测

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…