c++实现B树(上)

news2024/12/26 18:15:25

 哈喽啊!好久不见,甚是想念!失踪人口要回归了,时隔一个多月小吉我终于要更新blog了🎉。在停更的一个多月中,小吉也有在好好学习提升自己,立志给大家呈现好文章。
 现在让我们进入正题吧,今天我们这篇blog主要讲用c++实现B树,上一篇blog讲的是二叉搜索树,中间还有avl树和红黑树小吉还没有把博客写出来。希望大家多多关注小吉,在完成这篇blog后,小吉将会出avl树实现的博客(浅浅期待一下吧)。
 老规矩,在正式实现B树之前,我们先来了解了解什么是B树。

B树的概念

B树:B树是一种自平衡的树形数据结构,用于解决磁盘存储器上的数据管理问题,减少磁盘i/o操作的次数,从而提高数据访问的效率。

B树的特征

 在讲B树的特征之前,我们先来了解一下度和阶的概念

度(degree):指树中节点孩子数
阶(order):指所有节点孩子数最大值

在m阶的B树中,即孩子数最大值为m

B树的特征
1.B树的每个节点可以有多个子结点,每个节点最多可以有m个子节点;除根节点和叶子节点外,其他每个节点至少有ceil(m/2)个孩子。(ceil是指向上取整)
2.每个节点的关键字(键值)数量与子节点数量相关,通常比子节点数量少1。每个节点中的关键字数量k满足ceil(m/2)-1<=k<=m-1。
3.B树的所有叶子节点都在同一层
4.B树通过动态调整节点中的关键字数量来保持树的平衡
5.B树中的关键字按照升序排序
6.父节点中第i个关键字小于其第i个子节点的所有关键字,且大于其第i-1个子节点中所有的关键字

B树
(小吉在这里提醒一下:一定要好好理解B树的特征,后面在实现的过程有什么不能理解的地方,一定要回头好好读一下特征,大部分问题都能在看过特征后得到解答(小吉的小经验✨))

B树节点类

_t代表最小度数,也就是最小的孩子数ceil(m/2),因此最大孩子数m可以由2*t表示

节点类代码呈现👇:

class Node
{
public:
	Node(int t, bool leaf = true) :_t(t), _leaf(left), _children(2 * t, nullptr),_keys(2*t-1),KeyNumber(0){}//初始化列表
public:
	vector<int> _keys;//键值
	vector<Node*> _children;//孩子
	int KeyNumber;//有效键值数目
	bool _leaf;//是否为叶子节点
	int _t;//最小度数(最小孩子数)
};

B树节点类成员方法

主要实现3个方法

1.Node* get(int key);//多路查找
2.void insertKey(int index,int key);//向指定索引处插入key
3.void insertChild(int index, Node* child);//向指定索引处插入child

代码呈现👇:

Node* Node::get(int key)
{
	int i = 0;
	while (i < KeyNumber)
	{
		if (_keys[i] == key)
		{
			return this;
		}
		if (_keys[i] > key)
		{
			break;
		}
		i++;
	}
	//比key值大且为叶子节点
	if (this->_leaf)
	{
		return nullptr;
	}
	//非叶子节点(采用递归)
	return _children[i]->get(key);
}
void Node::insertKey(int index,int key)
{
	_keys.insert(_keys.begin() + index, key);
	KeyNumber++;
}
void Node::insertChild(int index, Node* child)
{
	_children.insert(_children.begin() + index, child);
}

B树类

class BTree
{
public:
	BTree(int t) :_t(t), _root(new Node(t)), MAX_KEYNUMS(2 * t - 1), MIN_KEYNUMS(t - 1) { }//初始化列表
public:
	Node* _root;
	int _t;//树中最小度数
	const int MIN_KEYNUMS;
	const int MAX_KEYNUMS;
};

注意:const成员变量必须在构造函数的初始化列表中进行初始化,而不能在构造函数体内部通过赋值语句来初始化

B树类成员方法

bool contains(int _key);//判断节点是否存在

代码实现👇:

//是否存在
	bool contains(int _key)
	{
		return _root->get(_key) != nullptr;
	}

B树put插入节点初实现

put实现思路:
1.首先查找本节点中的插入位置,如果没有空位(key被找到),应该走更新的逻辑
2.如果找到空位,该节点是叶子节点,可以直接插入;该节点是非叶子节点,需要继续在children[i]处继续递归插入

代码架构👇:

void BTree::doput(Node* node, int key,Node* parent,int index)
{
	int i = 0;
	while (i < node->KeyNumber)
	{
		if (node->_keys[i]==key)
		{
			node->_keys[i] = key;//更新
			return;
		}
		if (node->_keys[i] > key)
		{
			break;//找到了插入位置
		}
		i++;
	}
	if (node->_leaf)
	{
		node->insertKey(i, key);
	}
	else
	{
		doput(node->_children[i], key,node,i);//递归
	}
}
void BTree::put(int key)
{
	doput(_root, key,nullptr,0);
}

B树split分裂

 无论哪种情况,插入完成后都可能超过节点keys数目限制,应执行节点分裂
分裂
(注:较小的数字代表索引)

void BTree::split(Node* left, Node* parent, int index)
left为待分裂节点,index是该待分裂节点作为孩子的索引

split分裂思路🎇

1.创建right节点(分裂后大于当前left节点的),把t(最小孩子数)以后的key和child都拷贝过去
2.t-1处的key插入到parent的index处(index指left作为孩子时的索引)
3.right节点作为parent的孩子插入到index+1处

 分为三种情况,待分裂节点为叶子节点,非叶子节点和根节点。1️⃣我们先来实现最简单的一种情况,待分裂节点为叶子节点。
代码展示👇:

void BTree::split(Node* left, Node* parent, int index)
{
//1.创建right节点,把left中t之后的key和child移动过去
	Node* right = new Node(_t);
	right->_leaf = left->_leaf;
	right->_keys.assign(left->_keys.begin() + _t, left->_keys.end());
	right->KeyNumber = _t - 1;
	left->KeyNumber = _t - 1;

	//2.中间的key(t-1处)插入到父节点
	int mid = left->_keys[_t - 1];
	parent->insertKey(index, mid);

	//3.right节点作为父节点的孩子
	parent->insertChild(index + 1, right);
}

实现叶子节点的分裂就按照上面的思路就可以了,相对比较简单,我们现在可以小小测试一下代码,这里提供一个小的测试案例

void splitTest()//split分裂方法的测试案例
{
	BTree tree(2);
	Node* root = tree._root;
	root->_leaf = false;
	root->_keys[0] = 2;
	root->KeyNumber = 1;

	root->_children[0] = new Node(2);
	root->_children[0]->_keys = { 1 };
	root->_children[0]->KeyNumber = 1;

	root->_children[1] = new Node(2);
	root->_children[1]->_keys = { 3,4,5 };
	root->_children[1]->KeyNumber = 3;
	
	tree.split(root->_children[0], root, 0);
}

 分裂结果可以参照小吉上面给的图

2️⃣待分裂节点为非叶子节点,这种情况就比叶子节点多一个步骤就是处理孩子
代码实现👇:

void BTree::split(Node* left, Node* parent, int index)
{
//1.创建right节点,把left中t之后的key和child移动过去
	Node* right = new Node(_t);
	right->_leaf = left->_leaf;
	right->_keys.assign(left->_keys.begin() + _t, left->_keys.end());
	//分裂节点是非叶子节点的情况
	if (!left->_leaf)
	{
		right->_children.assign(left->_children.begin() + _t, left->_children.end());
	}
	right->KeyNumber = _t - 1;
	left->KeyNumber = _t - 1;

	//2.中间的key(t-1处)插入到父节点
	int mid = left->_keys[_t - 1];
	parent->insertKey(index, mid);

	//3.right节点作为父节点的孩子
	parent->insertChild(index + 1, right);
}

3️⃣待分裂节点为根节点,这里小吉先画个图来帮小可爱们想象一下
根节点

思路:如果parent==nullptr表示要分裂的是根节点,此时需要创建新根,原来的根节点作为新根的0(索引)孩子

代码实现👇:

if (parent == nullptr)//分裂的是根节点
	{
		Node* newRoot = new Node(_t);
		newRoot->_leaf = false;
		newRoot->insertChild(0, left);
		this->_root = newRoot;
		parent = newRoot;
	}

 三种情况已经全部分析完了🎉🎉🎉,现在小吉把三种情况的代码进行汇总,封装在split方法中

void BTree::split(Node* left, Node* parent, int index)
{
	if (parent == nullptr)//分裂的是根节点
	{
		Node* newRoot = new Node(_t);
		newRoot->_leaf = false;
		newRoot->insertChild(0, left);
		this->_root = newRoot;
		parent = newRoot;
	}

	//1.创建right节点,把left中t之后的key和child移动过去
	Node* right = new Node(_t);
	right->_leaf = left->_leaf;
	right->_keys.assign(left->_keys.begin() + _t, left->_keys.end());
	//分裂节点是非叶子节点的情况
	if (!left->_leaf)
	{
		right->_children.assign(left->_children.begin() + _t, left->_children.end());
	}
	right->KeyNumber = _t - 1;
	left->KeyNumber = _t - 1;

	//2.中间的key(t-1处)插入到父节点
	int mid = left->_keys[_t - 1];
	parent->insertKey(index, mid);

	//3.right节点作为父节点的孩子
	parent->insertChild(index + 1, right);
}

节点的分裂到这里已经全部讲完了🥳🥳🥳,下面就是要把split方法和put方法结合起来

B树put插入节点最终实现

思路:叶子节点加入key时要做分裂的检查,当节点中的有效键值数等于最大键值数时要进行节点的分裂

void BTree::put(int key)
{
	doput(_root, key,nullptr,0);
}

void BTree::doput(Node* node, int key,Node* parent,int index)
{
	int i = 0;
	while (i < node->KeyNumber)
	{
		if (node->_keys[i]==key)
		{
			node->_keys[i] = key;//更新
			return;
		}
		if (node->_keys[i] > key)
		{
			break;//找到了插入位置
		}
		i++;
	}
	if (node->_leaf)
	{
		node->insertKey(i, key);
	}
	else
	{
		doput(node->_children[i], key,node,i);//递归
	}
	if (node->KeyNumber == MAX_KEYNUMS)//进行分裂
	{
		split(node, parent, index);
	}
}

 这篇blog到这里已经全部结束了,浅浅预告一下小吉的下一篇blog讲的是B树的删除操作(又是一个大工程😶‍🌫️),还望大家多多支持小吉❤️
 这篇博客有点长也有点难,希望小可爱们给自己多一点耐心,静下心来慢慢学,还有一定要敲代码,只有敲了代码才能发现问题。

创作不易,还望大家多多支持🌹🌹🌹(小吉写了整整3个小时🤣)

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

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

相关文章

初识C++:开启C++之旅

目录 1.C的第一个程序 2.namesapce命名空间域 2.1namespace的意义 2.2.2namespace的定义 2.3命名空间的使用 3.C输入/输出 4.缺省参数 5.函数重载 6.引用 6.1引用的特性 6.2引用的使用 1.C的第一个程序 c版本&#xff1a; #include<iostream>using std::cout…

kali安装wechart

前言&#xff1a; 突发奇想想在kali安装个wechart&#xff0c;试了下网上的很多&#xff0c;玩坏了一个虚拟机算是找到了一个不错的方法&#xff0c;这里记录下&#xff0c;防迷路 基础配置&#xff1a; 首先修改源&#xff1a; vim /etc/apt/sources.list 注释默认配置&…

EasyCVR视频汇聚平台:打造全栈视频监控系统的基石,解锁可视化管理与高效运维

随着科技的飞速发展&#xff0c;视频监控已成为现代社会不可或缺的一部分&#xff0c;广泛应用于社区、公共场所、工业领域等多个场景。EasyCVR视频汇聚平台&#xff0c;作为一款高性能的视频汇聚管理平台&#xff0c;凭借其强大的视频处理、汇聚与融合能力&#xff0c;在构建全…

centos8 安装zookeeper

1&#xff1a;下载 zookeeper官网 解压&#xff1a;tar -zxvf apache-zookeeper-3.6.3.tar.gz 修改自己想要的文件目录 mv apache-zookeeper-3.6.3 zookeeper_3.6.3 备份一下 配置文件 cp zoo_sample.cfg zoo.cfg vim zoo.cfg 编辑日志文件和端口号

nginx实战演练

目录 一.Nginx架构和安装&#xff08;未完待续&#xff09; <1>.Nginx概述 <2>.Nginx架构和进程 <3>.Nginx模块 <4>.Nginx安装(编译安装) 二.Nginx基础配置 <1>.关闭debug <2>.将nginx软件添加到环境变量 <3>.开机自启动脚…

EmguCV学习笔记 VB.Net 2.5 Mat类、Matrix类和Image类的相互转换

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV学习笔记目录 Vb.net EmguCV学习笔记目录 C# 笔者的博客网址&#xff1a;VB.Net-CSDN博客 教程相关说明以及如何获得pdf教…

基于Vue的MES生产制造执行系统

TOC springboot307基于Vue的MES生产制造执行系统 第1章 绪论 1.1 选题动因 到现在为止&#xff0c;互联网已经进入了千家万户&#xff0c;最普通的平民百姓也有属于自己的智能设备&#xff0c;计算机各种技术的储备也是相当的丰富&#xff0c;并且实现也是没有难度&#xf…

VSCode配置ssh免密连接远程服务器

我配置了免密设置(Windows利用ssh免密码登录Linux)&#xff0c;git bash已经能够正常连接了&#xff0c;但是vscode还是不行&#xff0c;很奇怪。 VSCode报错信息&#xff1a; [17:55:50.360] SSH Resolver called for "ssh-remote106.52.2.19", attempt 5, (Recon…

《机器学习》——运用OpenCV库中的KNN算法进行图像识别

文章目录 KNN算法的简单介绍下载OpenCV库实验内容实验结果完整代码自己手写数字传入模型中测试 KNN算法的简单介绍 一、KNN算法的基本要素 K值的选择&#xff1a;K值代表选择与新测试样本距离最近的前K个训练样本数&#xff0c;通常K是不大于20的整数。K值的选择对算法结果有重…

电压检测之比较电路

设计这款电路主要是本人在锂电池充电电路中挖了一个坑&#xff0c;对电源显示芯片的数据手册内容撰写不够详细的不好感受&#xff0c;所以自己根据比较电路的思想设计出了电压检测并反馈的电路&#xff0c;亦在提供一种电压检测的思想不需要借助ADC采集&#xff0c;在电路硬件上…

基于hive的海鲜交易数据分析系统设计与实现【hadoop、Flask、某东爬虫、sqoop、flume、mysql、hdfs】商品可换

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍研究背景国内外研究现状研究目的研究意义 关键技术理论介绍数据采集及预处理数据采集字段介绍数据预处理hadoop集群搭建及实现过程hive建表hive大数据分析 可视化展示店铺维度画像分…

AR 眼镜之-开关机定制-实现方案

目录 &#x1f4c2; 前言 AR 眼镜系统版本 开关机定制 1. &#x1f531; 技术方案 1.1 技术方案概述 1.2 实现方案 1&#xff09;开机 Logo 2&#xff09;开机音效 3&#xff09;开机动画 4&#xff09;关机动画 5&#xff09;关机弹窗 2. &#x1f4a0; 开机 Logo…

C++笔试题汇总

C笔试题汇总记录 一、概述二、概念分类1. 结构体1. C 和 C 中 struct 有什么区别&#xff1f;2. C中的 struct 和 class 有什么区别&#xff1f; 2. 类相关1. 类的大小1. 空类的大小2. 一般非空类大小3. 有虚函数类4. 有虚函数类的继承5. 只有虚函数6. 静态数据成员 2. C的三大…

【分享】格力手机色界G0245D 刷REC、root、 救砖、第三方rom教程和资源

开门见山 帮别人弄了一台 格力G0245D&#xff0c;把找到的资源和教程分享一下 教程 这个写的很详细了格力手机色界G0245D-Root-最简指南 不过教程里刷rec这一步漏了加上电源键&#xff0c;加上就行了。 附加参考&#xff1a;格力手机2刷机 格力手机二代刷机 GREE G0215D刷机…

C++ 特殊类设计以及单例模式

目录 1 不能被拷贝 2 只能在堆上创建对象 3 只能在栈上创建对象 4 禁止在堆上创建对象 5 不能被继承的类 6 单例类 特殊类就是一些有特殊需求的类。 1 不能被拷贝 要设计一个防拷贝的类&#xff0c;C98之前我们只需要将拷贝构造以及拷贝赋值设为私有&#xff0c;同时只声明…

在HFSS中对曲线等结构进行分割(Split)

在HFSS中对曲线进行分割 我们往往需要把DXF等其他类型文件导入HFSS进行分析&#xff0c;但是有时需要对某一个曲线单独进行分割成两段修改。 如果是使用HFSS绘制的曲线&#xff0c;我们修改起来非常方便&#xff0c;修改参数即可。但是如果是导入的曲线&#xff0c;则需要使用…

代码随想录训练营 Day31打卡 贪心算法 part05 56. 合并区间 738. 单调递增的数字 968. 监控二叉树

代码随想录训练营 Day31打卡 贪心算法 part05 一、 力扣56. 合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中…

【JavaEE】JVM 内存区域划分,以及 Java 垃圾回收机制引用计数器,可达性分析等

目录 1. JVM执行流程 2. JVM运行时数据区 2.1 堆 2.2 Java虚拟机栈(线程私有) 2.3本地方法栈(线程私有) 2.4 程序计数器 2.5 元数据区 3. JVM的类加载机制 1) 加载 2) 验证 3) 准备 4) 解析 5) 初始化 双亲委派模型 4. java垃圾回收 4.1 死亡对象判断方法 a) …

超精细CG杰作:8K壁纸级官方艺术插画,展现极致美丽与细节的汉服女孩

极致精美的数字艺术杰作&#xff1a;8K壁纸级别的官方插画&#xff0c;展现超高清细节与和谐统一的美感&#xff0c;女孩的精致面容与眼神在光影下熠熠生辉&#xff0c;汉服主题下的超高分辨率作品&#xff0c;文件巨大&#xff0c;细节丰富&#xff0c;令人惊叹。 正向提示词…

内存泄漏之如何使用Visual Studio的调试工具跟踪内存泄漏?

使用Visual Studio的调试工具跟踪内存泄漏是一个系统性的过程&#xff0c;主要包括启用内存泄漏检测、运行程序、分析内存使用情况以及定位泄漏源等步骤。 Visual Studio提供了多种方式来检测内存泄漏&#xff0c;你可以根据自己的需求选择合适的方法。 注意&#xff1a;下面…