数据结构与算法-B(B-)树的简单实现

B(B-)树定义

B树(或B-tree)是一个在计算机科学中广泛使用的数据结构,它是一种自平衡的树,能够保持数据有序。

以下是B树的特性

  1. 每个节点最多右m个孩子,二叉树是B-树的特例,其有2个孩子。
  2. 除了叶节点和根节点以外,每个内部节点至少有Math.ceil(m/2)个孩子。
  3. 如果根不是叶节点,则至少有两个孩子。
  4. 所有叶节点都出现在一层,也就是说从根到叶节点点的度(层,高度)一样。
  5. 具有K个孩子的非叶节点包含 K-1个数据,在节点内部的键值是递增的。

B树模型

B-树节点

B树的阶: B树的节点(除根节点外) 最多 有多少个孩子结点(子树),一般用字母 M 表示阶数。

如果所示节点可以连接4个树,此树为四阶B树。

每个节点中存储的数据最多不超过M-1 = 3;

image-20240619142939740

B-树的一般结构

image-20240619114028879

查询

查询在B-树中数据,如果能找到我们需要知道节点对象和节点中哪个位置(每个节点内存储多个数据)

如果没有查询到我们需要记录当前的节点对象及位置为-1(表示不存在)。

NodeBind类

定义一个NodeBind类包装节点和数据的位置。

/*** 绑定数据在节点中位置类*/
public class NodeBind {private Node node;  //当前节点private int index; //当前节点中数据的位置public Node getNode() {return node;}public void setNode(Node node) {this.node = node;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}public NodeBind(Node node, int index) {this.node = node;this.index = index;}@Overridepublic String toString() {return "Pair{" +"node=" + node +", index=" + index +'}';}
}

查询实现

从根节点开始遍历,先从根节点开始检查是否存有要查找的数据,如果找到则返回Pair对象,如果没有则记录最后一个扫描到的对象,将其包装为Pair对象返回。

/*** 查询data在B树的节点位置** @param data* @return*/
public Pair search(int data) {Node curr = root;Node parent = null;while (curr != null) {int rowIndex = 0;while (rowIndex < curr.size) {List<Integer> datas = curr.datas;int index = datas.indexOf(data);if (index != -1) {return new Pair(curr,index);} else if (data > datas.get(rowIndex)) {rowIndex++;} else {break;}}parent = curr;curr = curr.children.get(rowIndex);}return new Pair(parent,-1);
}

添加数据

我们采用的策略是先暂时存储数据,当存入后数据超出其长度进行分裂处理。

辅助方法指定节点添加数据

private void insertBySort(Node node, int data) {int index = node.size;for (int i = 0; i < node.datas.size(); i++) {if (data < node.datas.get(i)) {index = i;break;}}node.datas.add(index, data);node.size++;
}

添加新数据

我们向B树中添加数据{12,45,9,78,80,5,3,79,20},跟踪B树的添加过程。

添加第一个元素12

创建新节点即为root节点,在datas中存入数据12。

image-20240622143104011
if (root == null) {//如果根节点不存在root = new Node();root.datas.add(data);root.size++;return;
}

添加数据45

在根节点的第二个位置添加45,找到要添加的节点。 节点的数据安装升序排序存放。

image-20240622143256238
/*
从B-树中查找节点是否存在*/
Pair findNode = search(data);
//数据已经在B树中,只更新不插入
if (findNode.getIndex() != -1) return;/*如果没有找到则插入节点- 当前要插入的节点没有满- 当前要插入的节点已满,需要分裂节点*/
Node curr = findNode.getNode();insertBySort(curr, data);if (curr.size <= M - 1) { //如果当前节点的数据没有满,最大存储M-1数据return;
}

添加数据9

查找位置添加节点,代码和4.2.3一致。

image-20240622143545722

添加数据78

重复添加过程,需要判断当前节点容量是否已满,当前节点的数据容量: size<=M-1

如果超过容量则需要分裂当前节点。

image-20240622143845811
....添加节点代码
if (curr.size <= M - 1) { //如果当前节点的数据没有满,最大存储M-1数据return;
} else { //当前节点已满System.out.println("当前节点已满,准备分裂...");split(curr);
}

分裂节点

以当前节点的中间节点为中心将左右分裂为两个节点

  • 原节点保留左侧数据
  • 新建节点保留右侧数据
  • 如果当前节点为根节点,需要新创建节点为根节点,存储中间节点数据。

image-20240622144624956

public void split(Node curr) {int mid = curr.size / 2; //找到中间节点int midVal = curr.datas.get(mid); //记录中间节点的值Node parent = curr.parent; //记录当前节点的父节点/**  创建新节点,用来存储分裂的右侧节点*/Node newNode = new Node();for (int i = mid + 1; i < curr.size; i++) {newNode.datas.add(curr.datas.get(i)); //新节点循环添加mid+1开始的节点newNode.size++; //新节点数据大小更新curr.datas.remove(i); //删除右侧数据(右侧节点放中间朝右的数据)i--; //防止引起集合索引的移动(由于删除了数据,集合大小会改变,当前位置也会调整,回退到删除为止)curr.size--; //当前节点的数据-1处理}:
}

如果当前节点是根节点,分裂两个子树,实际上当前节点和分裂节点的每一个parent要连接新的parent,parent的每一个孩子节点的孩子节点要引用这两个节点(可以自行完成,繁琐但不难)。

/**  判断当前节点是否为根节点*/
if (parent == null) {//父节点为空,当前节点为根节点root = new Node();//产生一个新的父节点root.datas.add(midVal); //拷贝当前节点的中间值到父节点中curr.datas.remove(Integer.valueOf(midVal)); //从当前对象中移除已经添加到root上的数据curr.size--;root.size++; //父节点长度更新root.children.add(curr); //父节点的孩子节点连接子节点curr.parent = root; //当前节点的父节点变成新的父节点root.children.add(newNode); //添加右侧的子节点到父节点的孩子节点中newNode.parent = root; //右接点的父节点连接新父节点return;
}

添加80

image-20240622145021014

添加5

image-20240622145120905

添加3

image-20240622145150175

再次分裂

  • 左侧分裂时检查父节点是否有空位,如果有把中间节点(9)放到父节点去
  • 分裂出一下新节点存储中间节点的右侧数据 12
  • 递归向上操作(如果父节点已满,继续向上操作直到父节点为根节点结束)

image-20240622151412419

else {//如果parent不是根节点,且当前节点已满//将中间值拷贝到parent节点中insertBySort(parent, midVal);curr.datas.remove(Integer.valueOf(midVal));curr.size--;parent.children.add(newNode); //将新parent.children.sort((o1, o2) -> o1.datas.get(o1.size - 1) - o2.datas.get(0)); //对孩子节点安装正序排序if (parent.size >= M) {split(parent);   //低估操作父节点}
}

添加数据20

image-20240622151756555

完整代码实现

B(B-)树节点

public class Node {public List<Integer> datas; //记录数据public List<Node> children; //子节点public Node parent; //父节点public int size; //节点内存储的值数量public Node() {datas = new ArrayList<>();children = new ArrayList<>();}@Overridepublic String toString() {return "Node{" +"datas=" + datas +", size=" + size +'}';}
}

B(B-)树的实现

辅助类NodeBind

辅助类NodeBind用来绑定在查询节点时,将节点和查询到B-树位置绑定。

如果节点没有查到,绑定当前节点,-1;如果查找到节点绑定当前节点,数据在节点中的位置。

/*** 绑定数据在节点中位置类*/
public class NodeBind {private Node node;  //当前节点private int index; //当前节点中数据的位置public Node getNode() {return node;}public void setNode(Node node) {this.node = node;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}public NodeBind(Node node, int index) {this.node = node;this.index = index;}@Overridepublic String toString() {return "Pair{" +"node=" + node +", index=" + index +'}';}
}

添加节点和分裂节点的实现

package com.ffyc.tree;import com.ffyc.tree.node.Node;
import com.ffyc.tree.node.NodeBind;import java.util.*;public class BTree {private Node root;// M表示B-树的阶数// 根节点的数据数: [1,M]之间private final int M = 4; //4阶B-树/*** 添加节点数据* @param data*/public void add(int data) {if (root == null) {//如果根节点不存在root = new Node();root.datas.add(data);root.size++;return;}/*从B-树中查找节点是否存在*/NodeBind findNode = search(data);//数据已经在B树中,只更新不插入if (findNode.getIndex() != -1) return;/*如果没有找到则插入节点- 当前要插入的节点没有满- 当前要插入的节点已满,需要分裂节点*/Node curr = findNode.getNode();insertBySort(curr, data);if (curr.size <= M - 1) { //如果当前节点的数据没有满,最大存储M-1数据return;} else { //当前节点已满System.out.println("当前节点已满,准备分裂...");split(curr);}}/*** B树的节点进行分裂* @param curr 当前要分裂的节点*/public void split(Node curr) {int mid = curr.size / 2; //找到中间节点int midVal = curr.datas.get(mid);Node parent = curr.parent;/**  创建新节点,用来存储分裂的右侧节点*/Node newNode = new Node();//newNode.datas = curr.datas.subList(mid + 1, curr.size);for (int i = mid + 1; i < curr.size; i++) {newNode.datas.add(curr.datas.get(i));newNode.size++;curr.datas.remove(i);i--;curr.size--;}/**  判断当前节点是否为根节点*/if (parent == null) {//父节点为空,当前节点为根节点root = new Node();//产生一个新的父节点root.datas.add(midVal); //拷贝当前节点的中间值到父节点中curr.datas.remove(Integer.valueOf(midVal)); //从当前对象中移除已经添加到root上的数据curr.size--;root.size++; //父节点长度更新root.children.add(curr); //父节点的孩子节点连接子节点curr.parent = root; //当前节点的父节点变成新的父节点root.children.add(newNode); //添加右侧的子节点到父节点的孩子节点中newNode.parent = root; //右接点的父节点连接新父节点return;} else {//如果parent不是根节点,且当前节点已满//将中间值拷贝到parent节点中insertBySort(parent, midVal);curr.datas.remove(Integer.valueOf(midVal));curr.size--;parent.children.add(newNode); //将新parent.children.sort((o1, o2) -> o1.datas.get(o1.size - 1) - o2.datas.get(0));if (parent.size >= M) {split(parent);}}}/*** 在节点中按顺序插入数据* @param node* @param data*/private void insertBySort(Node node, int data) {int index = node.size;for (int i = 0; i < node.datas.size(); i++) {if (data < node.datas.get(i)) {index = i;break;}}node.datas.add(index, data);node.size++;}/*** 查询data在B树的节点位置** @param data* @return*/public NodeBind search(int data) {Node curr = root;  //记录根节点,从根节点开始遍历Node parent = curr.parent;  //记录父节点while (curr != null) {   //从root遍历所有节点int rowIndex = 0;  // 每个节点中数据的存储位置索引while (rowIndex < curr.size) { //扫描节点中所有数据List<Integer> datas = curr.datas;int index = datas.indexOf(data); //判断节点中的数据汇总是否包含要查找的数据if (index != -1) { //数据存在,返回终止查找return new NodeBind(curr, index);} else if (data > datas.get(rowIndex)) { //数据大于节点中的数据,继续查找下一个数据rowIndex++;} else {//找到比节点中数据小的位置跳出循环,表示查找完毕没有找到break;}}parent = curr;try {curr = curr.children.get(rowIndex);} catch (IndexOutOfBoundsException e) {curr = null;}}return new NodeBind(parent, -1);}public void print() {Queue<Node> queue = new LinkedList<>();queue.add(root);while (!queue.isEmpty()) {   //从root遍历所有节点Node curr = queue.poll();for (int i = 0; i < curr.size; i++) {System.out.print(curr.datas.get(i) + "\t");}List<Node> children = curr.children;for (Node node : children) {queue.offer(node);}}}public Node getRoot() {return this.root;}/*** 测试代码*/public static void main(String[] args) {BTree bTree = new BTree();bTree.add(12);bTree.add(45);bTree.add(9);bTree.add(78);bTree.add(80);bTree.add(5);bTree.add(3);bTree.add(79);bTree.add(20);bTree.add(1);bTree.print();}
}

运行结果

image-20240622154318947

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

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

相关文章

c++中从父类继承的属性在子类内存中如何显示?

目录 一、继承概念 二、示例 三、结论 一、继承概念 在C中&#xff0c;继承是面向对象编程的一个重要特性&#xff0c;它允许一个类&#xff08;称为派生类或子类&#xff09;继承另一个类&#xff08;称为基类或父类&#xff09;的成员&#xff08;包括数据成员和成员函数…

springboot + Vue前后端项目(第十七记)

项目实战第十七记 写在前面1. 个人信息1.1 Person.vue1.2 设置路由并改动Header.vue1.3 动态刷新头像1.3.1 在保存个人信息时&#xff0c;触发方法1.3.2 父组件Manage.vue1.3.3 再将user以prop方式传递给子组件Header.vue1.3.4 Header.vue使用user 1.4 效果图 2. 修改密码2.1 前…

[RK-Linux] RK3562 I2C驱动TP芯片GSL3680

TP芯片GSL3680 1、简介 GSL3680是一款电容屏多点触摸控制单芯片,其芯片内部框架图如下所示: GSL3680采用了独特的互电容感应技术,可以在1ms内测量多达192个节点,支持广泛的传感器选择,包括单层或双层ITO,玻璃或薄膜,采用了先进的抗电磁干扰信号处理技术,使其对各种干…

C++初学者指南第一步---12.引用

C初学者指南第一步—12.引用 文章目录 C初学者指南第一步---12.引用1. 功能&#xff08;和限制&#xff09;1.1 非常量引用1.2 常量引用1.3 auto引用 2.用法2.1 范围for循环中的引用2.2 常量引用的函数形参2.3 非常量引用的函数形参2.4 函数参数的选择&#xff1a;copy / const…

git拉取gitee项目到本地

git安装等不做赘述。 根据需要选择不同操作 1.只是单纯拉取个项目&#xff0c;没有后续的追踪等操作 不需要使用git init初始化本地文件夹 新建一个文件夹用于存储项目&#xff0c;右键选择 git bash here 会出现命令行窗口 如果像我一样&#xff0c;只是拉取个项目作业&…

若以框架学习(3),echarts结合后端数据展示,暂时完结。

前三天&#xff0c;参加毕业典礼&#xff0c;领毕业证&#xff0c;顿时感到空落落的失去感&#xff0c;没有工作&#xff0c;啥也没有&#xff0c;总感觉一辈子白活了。晚上ktv了一晚上&#xff0c;由于我不咋个唱歌&#xff0c;没心情&#xff0c;听哥几个唱了一晚上周杰伦&am…

Redis实战—Redis分布式锁

本博客为个人学习笔记&#xff0c;学习网站与详细见&#xff1a;黑马程序员Redis入门到实战 P56 - P63 目录 分布式锁介绍 基于Redis的分布式锁 Redis锁代码实现 修改业务代码 分布式锁误删问题 分布式锁原子性问题 Lua脚本 编写脚本 代码优化 总结 分布式锁介绍…

express+vue在线im实现【三】

往期内容 expressvue在线im实现【一】 expressvue在线im实现【二】 本期示例 本期总结 支持各种类型的文件上传&#xff0c;常见文件类型图片&#xff0c;音频&#xff0c;视频等&#xff0c;上传时同步获取音频与视频的时长&#xff0c;以及使用上传文件的缓存路径来作为vi…

WDF驱动开发-DMA(一)

在 Windows 7 及更早版本上&#xff0c;Kernel-Mode Driver Framework (KMDF) 仅支持 (DMA) 设备的总线-主直接内存访问。 此类设备包含其自己的 DMA 控制器。 在片上系统 (SoC) 上运行Windows 8及更高版本的平台上&#xff0c;该框架还支持系统模式 DMA&#xff0c;其中多个设…

视频讲解|基于模型预测算法的含储能微网双层能量管理模型【mpc】

1 主要内容 该讲解视频对应的免费程序链接为【防骗贴】基于模型预测算法的含储能微网双层能量管理模型&#xff0c;主要做的是一个微网双层优化调度模型&#xff0c;微网聚合单元包括风电、光伏、储能以及超级电容器&#xff0c;在微网的运行成本层面考虑了电池的退化成本&…

快捷方式(lnk)--加载HTA-CS上线

免责声明:本文仅做技术交流与学习... 目录 CS: HTA文档 文件托管 借助mshta.exe突破 本地生成lnk快捷方式: 非系统图标路径不同问题: 关于lnk的上线问题: CS: HTA文档 配置监听器 有效载荷---->HTA文档--->选择监听器--->选择powershell模式----> 默认生成一…

政务大厅引导系统:AR、VR技术革新引领政务服务体验升级

一、传统政务大厅面临的普遍痛点 随着城市的发展和政务服务需求的增长&#xff0c;传统的政务大厅面临着诸多挑战和痛点&#xff1a; 信息不对称&#xff1a;政务大厅内各部门信息分散&#xff0c;群众难以快速获取全面准确的服务信息&#xff0c;导致办事效率低下。 办事流…

计算机视觉 | 基于图像处理和边缘检测算法的黄豆计数实验

目录 一、实验原理二、实验步骤1. 图像读取与预处理2. 边缘检测3. 轮廓检测4. 标记轮廓序号 三、实验结果 Hi&#xff0c;大家好&#xff0c;我是半亩花海。 本实验旨在利用 Python 和 OpenCV 库&#xff0c;通过图像处理和边缘检测算法实现黄豆图像的自动识别和计数&#xff0…

JetBrains GoLand 2024 mac/win版:高效开发,Go无止境

JetBrains GoLand 2024是一款专为Go语言开发者设计的集成开发环境(IDE)&#xff0c;为开发者带来了更加高效、智能和便捷的编程体验。 GoLand 2024 mac/win版获取 在代码编辑方面&#xff0c;GoLand 2024提供了全行代码补全功能&#xff0c;通过利用先进的深度学习模型&#x…

力扣85.最大矩形

力扣85.最大矩形 遍历所有行作为底边 做求矩形面积&#xff08;84. class Solution {public:int maximalRectangle(vector<vector<char>>& matrix) {if (matrix.empty()) return 0;int n matrix.size(),m matrix[0].size();int res0;vector<int> li…

适耳贴合的气传导耳机,带来智能生活体验,塞那Z50耳夹耳机上手

现在大家几乎每天都会用到各种AI产品&#xff0c;蓝牙耳机也是我们必不可少的装备&#xff0c;最近我发现一款很好用的分体式气传导蓝牙耳机&#xff0c;它还带有一个具备AI功能的APP端&#xff0c;大大方便了我们日常的使用。这款sanag塞那Z50耳夹耳机我用过一段时间以后&…

开发指南033-数据库兼容

元芳&#xff0c;你怎么看&#xff1f; 单一数据库自身就有一些不同处理之处&#xff0c;如果一个平台要兼容所有数据库&#xff0c;就是难上加难&#xff0c;像isnull函数各数据库就不同。 对于这类问题&#xff0c;平台采用统一自定义函数解决&#xff0c;例如上面的round函…

模式分解的概念(下)-无损连接分解的与保持函数依赖分解的定义和判断、损失分解

一、无损连接分解 1、定义 2、检验一个分解是否是无损连接分解的算法 输入与输出 输入&#xff1a; 关系模式R&#xff08;U&#xff0c;F&#xff09;&#xff0c;F是最小函数依赖集 R上的一个分解 输出&#xff1a; 判断分解是否为无损连接分解 &#xff08;1&#x…

JAVA同城服务场馆门店预约系统支持H5小程序APP源码

&#x1f4f1;一键预约&#xff0c;畅享无忧体验&#x1f3e2; &#x1f680;一、开启预约新纪元 在繁忙的都市生活中&#xff0c;我们常常因为时间紧张而错过心仪的门店或场馆服务。然而&#xff0c;有了“门店场馆预约小程序”&#xff0c;这些问题都将迎刃而解。这款小程序…

群辉NAS中文件下载的三种方案

目录 一、迅雷套件 1、添加套件来源 2、安装套件 3、手机安装迅雷 二、qBittorrent套件 1、添加套件来源 2、改手工安装 3、更新后的问题 4、最后放弃DSM6 (1)上传文件手工安装 (2)添加套件来源 5、解决登陆报错 6、添加tracker 7、修改下载默认位置 8、手机…