当前位置: 首页 > news >正文

c++实现B树(上)

 哈喽啊!好久不见,甚是想念!失踪人口要回归了,时隔一个多月小吉我终于要更新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个小时🤣)

相关文章:

c++实现B树(上)

哈喽啊&#xff01;好久不见&#xff0c;甚是想念&#xff01;失踪人口要回归了&#xff0c;时隔一个多月小吉我终于要更新blog了&#x1f389;。在停更的一个多月中&#xff0c;小吉也有在好好学习提升自己&#xff0c;立志给大家呈现好文章。  现在让我们进入正题吧&#xf…...

【机器学习】深度强化学习–RL的基本概念、经典场景以及算法分类

引言 深度强化学习&#xff08;Deep Reinforcement Learning, DRL&#xff09;是机器学习的一个分支&#xff0c;它结合了深度学习&#xff08;Deep Learning&#xff09;和强化学习&#xff08;Reinforcement Learning, RL&#xff09;的技术 文章目录 引言一、深度强化学习–…...

【git】将本地文件上传到github

安装git 选择一个文件夹作为git仓库&#xff0c;cd到文件夹输入 git init文件夹出现.git文件夹&#xff0c;该文件夹默认为隐藏文件夹&#xff0c;设置为不隐藏 在cmd中输入 ssh-keygen -t rsa -C "xxxxxx.com"该邮箱为github邮箱&#xff0c;然后一路enter出现以…...

安卓应用开发学习:手机摇一摇功能应用尝试--摇骰子和摇红包

一、引言 前几天&#xff0c;我发布的日志《安卓应用开发学习&#xff1a;查看手机传感器信息》记录了如何查看手机传感器的信息&#xff0c;通过上述的方法&#xff0c;可以看到我的OPPO手机支持19种传感器。本篇日志就记录一下常见的加速度传感器的典型应用——“摇一摇”功…...

HTML中的<fieldset>标签元素框的使用

HTML 提供的 <fieldset> 标签用于在表单中分组相关元素。 <fieldset> 标签会在相关元素周围绘制一个框。 <legend> 标签为 fieldset 元素定义标题。 语法如下&#xff1a; <fieldset><legend>标题</legend><!-- 元素内容... -->…...

Linux驱动入门实验班——SR501红外模块驱动(附百问网视频链接)

目录 一、工作方式 二、接口图 三、编写思路 1.构造file_operations结构体 2.实现read函数 3.编写入口函数 4.编写中断处理函数 5.编写出口函数 6.声明出入口函数以及协议 四、源码 五、课程链接 一、工作方式 SR501人体红外感应模块有两种工作模式&#xff1a; …...

windows C++- Com技术简介(上)

在介绍C和winrt与COM组件技术的关系之前&#xff0c;有必要介绍一下com组件技术&#xff0c;这项技术比较古老&#xff0c;但是它一直作为windows的基石存在。COM 是一类独立于平台且面向对象的分布式系统&#xff0c;用于创建可交互的二进制软件组件。 COM 技术是 Microsoft O…...

Jenkins持续集成工具学习

一、从装修厨房看项目开发效率优化 二、持续集成工具 三、JavaEE项目部署方式对比 四、JenkinsSVN持续集成环境搭建 五、JenkinsGitHub持续集成环境搭建...

Redis:查询是否包含某个字符/字符串之三

上一篇&#xff1a;Redis&#xff1a;查询是否包含某个字符/字符串之二-CSDN博客 摘要&#xff1a; 遍历key&#xff0c;在跟进value的类型遍历value是否包含指定字符串 search_strings &#xff0c;这里使用redis-py库&#xff0c;默认只能处理utf-8编码&#xff0c;如果存在…...

【Redis】数据类型详解及其应用场景

目录 Redis 常⻅数据类型预备知识基本全局命令小结 数据结构和内部编码单线程架构引出单线程模型为什么单线程还能这么快 Redis 常⻅数据类型 Redis 提供了 5 种数据结构&#xff0c;理解每种数据结构的特点对于 Redis 开发运维⾮常重要&#xff0c;同时掌握每种数据结构的常⻅…...

PARA-Drive:设计并行模型实现端到端自动驾驶

论文链接 https://openaccess.thecvf.com/content/CVPR2024/papers/Weng_PARA-Drive_Parallelized_Architecture_for_Real-time_Autonomous_Driving_CVPR_2024_paper.pdfhttps://openaccess.thecvf.com/content/CVPR2024/papers/Weng_PARA-Drive_Parallelized_Architecture_fo…...

vs2022 x64 C/C++和汇编混编 遇到的坑

vs2022 x64 C/C和汇编混编 遇到的坑 遇到的问题二、问题复现1.出错代码2.问题分析2.1 堆栈对齐问题 3.解决方案 总结奇数和偶数个寄存器的影响为什么 sub rsp, 8 对奇数个寄存器有用&#xff1f;结论 遇到的问题 0x00007FFFFAE24A29 (msvcp140.dll)处(位于 TestCompileConsole…...

PHP概述、环境搭建与基本语法讲解

目录 【学习目标、重难点知识】 什么是网站&#xff1f; 1. PHP 介绍 1.1. PHP 概述 1.1.1. PHP 是什么&#xff1f; 1.1.2. PHP 都能做什么&#xff1f; 1.2. PHP 环境搭建 1.2.1. PhpStudy 2. PHP 基本语法 2.1. PHP 语法入门 2.1.1. 第一个 PHP 程序 2.1.2. PHP …...

实现信创Linux麦克风摄像头录制(源码,银河麒麟、统信UOS)

随着信创国产化浪潮的来临&#xff0c;在国产操作系统上的应用开发的需求越来越多&#xff0c;其中一个就是需要在银河麒麟或统信UOS上实现录制摄像头视频和麦克风声音&#xff0c;将它们录制成一个mp4文件。那么这个要如何实现了&#xff1f; 一. 技术方案 要完成这些功能&a…...

深度学习9--目标检测

1.概念介绍 目标检测不仅可以检测数字&#xff0c;而且可以检测动物的种类、汽车的种类等。例如&#xff0c;自动驾驶车辆需要自动识别前方物体是车辆还是行人&#xff0c;需要自动识别道路两 旁的指示牌和前方的红绿灯颜色。对于自动检测的算法&#xff0c;有两个要求&#xf…...

第131天:内网安全-横向移动Kerberos 攻击SPN扫描WinRMWinRSRDP

案例一&#xff1a;域横向移动-RDP-明文&NTLM RDP利用的三种方式 1.直接在当前被控主机上进行远程连接 2.建立节点进行连接 3.端口转发&#xff0c;&#xff08;访问当前主机的2222端口等于访问目标的3389&#xff09; 第一种方式(动静太大) 直接利用被控主机进行远程连接…...

微信小程序的四种弹窗使用

​ 在做小程序的过程中&#xff0c;弹窗也算是非常实用的功能了&#xff0c;这几天写的几个功能就用到了弹窗&#xff0c;也可能是初学者的问题&#xff0c;比较菜&#xff0c;想找一个可以带图片的自定义的弹窗&#xff0c;&#xff0c;这里简单介绍一下官方封装好的四个弹窗…...

我的第一个CUDA程序

MatAdd算法 实现两个矩阵对应元素相加 #include <stdio.h> #include <stdlib.h>// 矩阵加法函数 void MatAdd(int height, int width) {// 在主机内存中为 A、B 和 C 分配内存float* A (float*)malloc(height * width * sizeof(float));float* B (float*)malloc…...

workerman下的webman路由浏览器跨域的一种问题

软件版本 "php": ">7.2", "workerman/webman-framework": "^1.5.0",问题情景 使用“分组路由”做API接口前后端分离跨域&#xff0c;在接口测试工具调试是能正常获取数据的&#xff1b;但在网页浏览器上调试就遇到了CORS、404的错…...

Windows11 -MASKRCNN-部署测试

文章目录 Detectron2环境配置搭建python 环境安装Cuda \CUDNN 、PyTorch、 torchvision、cudatoolkit1、Cuda \CUDNN2、 PyTorch、 torchvision、cudatoolkit进入python测试&#xff1a;错误信息 3、detectron2环境在安装detecteron中&#xff0c;遇到报错&#xff1a;编译的时…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...