Java 实现 B树(通俗易懂)

目录

一.概念

二.节点定义

三.插入操作

1.查找位置

2.插入

3.分裂

四.B+树和B*树

1.B+树

2.B*树


一.概念

B树是一颗多叉平衡树,空树也是多叉平衡树。

一颗M阶的B树要满足以下条件:

1.根节点至少有两个孩子;

2.每个非根节点至少\frac{M}{2}-1(上取整)个关键字,至多M-1个关键字,并且以升序排列;

3.每个非根节点至少\frac{M}{2}(上取整)个孩子,至多M个孩子;

4.key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间;

5.所有的叶子节点都在同一层

解读一下上面的条件:一颗M阶树的意思是一个节点可以存储多个值,最多存储M-1个,像二叉树使用right和left来存储子树的位置,B树用sub来存储位置,不过这个要存储很多个,最多是M个。就是说每一个存在节点的值都有一个左孩子和右孩子,他们遵从条件4。像下面这个图,连起来的黑线表示是父子关系。

二.节点定义

B树与二叉树不同的是其有多个值,且有多个孩子,这些要开一个数组储存;同时我们还要记录节点的父节点是那个;我们还需要存储数组已使用的空间的数量,方便我们后序操作。

关于数组大小的定义,正常来说一个根能存储的关键字(值)是M-1个的,能存储的地址是M个,这里为什么都多出一个呢?这里是为了后面的分裂操作做准备。等到了后面的分裂操作大家就能感受到这么设计的巧妙之处了。

补充:flag是用于后面判断是否找到要插入位置时使用的,m是M阶B树的M

static class TreeNode{public int[] key;public TreeNode[] sub;public TreeNode parent;public int usedSize;public TreeNode(){this.key=new int[m];this.sub=new TreeNode[m+1];usedSize=0;}
}
TreeNode root;
boolean flag=false;
public static final int m=3;

三.插入操作

1.查找位置

要正确插入首先要找到位置。

B树的查找与二叉树还是相似的。先找当前节点内的值,如果我们要插入的val值比数组中的某个值大,那么我们要继续往后比较;如果val比数组中的某个值小,那么我们就进入它的左子树中继续比较;如果val等于数组中的某个值,那么就无法插入。

总结:1.val<key[i] 循环继续;2.val>key[i] 循环结束进入子树;3. val=key[i] 直接返回。

在定义节点的时候我们定义了一个flag,当我们在遇到情况3时,可以让flag=true。这个技巧在很多地方能够用到,如算法题。

public TreeNode find(int val){TreeNode cur=root;TreeNode parent = null;flag=false;while(cur!=null){int i=0;while(i< cur.usedSize){if(cur.key[i]>val){break;}else if(cur.key[i]<val){i++;}else{flag=true;return null;}}parent=cur;cur=cur.sub[i];}return parent;
}

为什么cur=cur.sub[i]呢?下图可以解释:

比如我们要插入4,4<5,我们在i=1时要停下来进入左子树了,刚好sub[i]是5的左子树。

2.插入

找到位置后我们就要进行插入操作了。

首先我们先判断根节点是不是空的,如果是空的我们要先插在根上。

如果我们定义的flag是true的话,说明这个点已经有了无法插入,直接返回即可。

插入操作比较简单,就是一个直接插入排序,插入完不要忘记更新usedSize值(重要)。

插入完我们要判断usedSize是不是已经满了,如果满了我们要进行分裂操作。

public boolean insert(int val){//如果根节点是空的if(root==null){root=new TreeNode();root.key[0]=val;root.usedSize++;return true;}TreeNode parent=find(val);if(flag==true){//已经有这个点了return false;}//开始插入int i = parent.usedSize-1;for (; i >= 0;i--) {if(parent.key[i] >= val) {parent.key[i+1] = parent.key[i];}else {break;}}parent.key[i+1] = val;parent.usedSize++;if(parent.usedSize<m){//没满return true;}else{split(parent);return true;}}

3.分裂

先说一下怎么分裂:

我们先找到中间位置,中间位置的坐标是mid=M/2。这个中间位置的节点就是要上提的,这个节点的左面数组部分就变成了它的左子树,这个节点右面数组部分就变成了它的右子树。

例子:一颗4阶B树,下图

我们可以将分裂分为两步,先单独建立一个节点,将中间值右面部分复制到新节点里;再将中间值上提到原来数组里的父节点里。

第一步,复制右半部分,不仅要复制值,还要复制孩子的地址。这里要记住,复制了孩子的地址仅仅只是父节点连接上了孩子,孩子的parent也要进行更改。还要注意一点是,孩子地址的数组比值的数组多一个,最后还要加上。复制完不要忘记更新数据。

第二步,上提。这里分为两种情况:是根,不是根。

先说是根,如果当前节点是根节点,那么上提的话要新建一个根,如果再进行复制更新。

如果不是根,将中间值插入到数组后,然后进行直接插入排序。

最后都处理完判断上提到的数组是不是满了,如果满了要继续进行分裂。

public void split(TreeNode cur){//处理右半部分TreeNode node=new TreeNode();int mid= cur.usedSize/2;int i=mid+1;int j=0;for(;i<cur.usedSize;i++){node.key[j]=cur.key[i];node.sub[j]=cur.sub[i];if(node.sub[j]!=null){node.sub[j].parent=node;}j++;}node.sub[j]=cur.sub[i];if(node.sub[j]!=null){node.sub[j].parent=node;}//更新数据node.usedSize=j;cur.usedSize= cur.usedSize-j-1;node.parent=cur.parent;//特判:cur是根节点if(cur==root){root=new TreeNode();root.key[0]=cur.key[mid];root.sub[0]=cur;root.sub[1]=node;node.parent=root;cur.parent=root;root.usedSize++;return;}//上提TreeNode parent=cur.parent;int end=parent.usedSize-1;int val=cur.key[mid];for (; end >= 0;end--) {if(parent.key[end] >= val) {parent.key[end+1] = parent.key[end];parent.sub[end+2] = parent.sub[end+1];}else {break;}}parent.key[end+1] = val;parent.sub[end+2] = node;parent.usedSize++;if(parent.usedSize>=m){split(parent);}
}

四.B+树和B*树

1.B+树

B+树的B树的变种,再B树的基础上加入新的规则:

1.关键字的数量和孩子的数量相同

2.非叶子节点的子树指针p[i],指向关键字值属于[ k[i],k[i+1] )的子树(都是右子树);

3.为所有叶子节点增加一个链指针

4.所有关键字都在叶子节点出现。

2.B*树

B*树的B+树的变种,在B+树的非根和非叶子节点再增加指向兄弟节点的指针。

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

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

相关文章

机械学习—零基础学习日志(如何理解线性代数2)

零基础为了学人工智能&#xff0c;正在快乐学习&#xff0c;每天都长脑子 引言 在平面中&#xff0c;直线的定义可以理解为&#xff0c;任意缩放同一个平面向量得到所有点的集合。 所以要得到一个三维空间中的直线&#xff0c;只需要将这个向量改成三维向量即可。 什么是线…

uniapp加载第三方字体方案对比(附原生微信小程序方案)

文章目录 官方文档uniapp文档微信小程序文档 下载字体包引入方案限制微信小程序限制uniapp的限制 方案对比方案1&#xff1a;CSS本地加载方案2&#xff1a;CSS远程加载方案3&#xff1a;转换为base64&#xff0c;然后通过css引入方案4&#xff1a;使用uni.loadFontFace() 页面使…

(Jmeter、Fiddler)脚本转换Loadrunner脚本

背景&#xff1a;公司政治任务、各种体系文档要留档&#xff0c;但有些不在体系内的工具生成的脚本需要转化到体系内以备留档。 一、Loadrunner代理设置 开始录制配置&#xff1a; Record->Remote Application via LoadRunner Proxy LoadRrunner Proxy listens on port-…

米联客-FPGA程序设计Verilog语法入门篇连载-19 Verilog语法_低功耗设计

软件版本&#xff1a;无 操作系统&#xff1a;WIN10 64bit 硬件平台&#xff1a;适用所有系列FPGA 板卡获取平台&#xff1a;https://milianke.tmall.com/ 登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑&#xff01; 1概述 本小节讲解Verilog语法的低功…

Spark MLlib 特征工程(下)

Spark MLlib 特征工程(下) 前面我们提到&#xff0c;典型的特征工程包含如下几个环节&#xff0c;即预处理、特征选择、归一化、离散化、Embedding 和向量计算&#xff0c;如下图所示。 在上一讲&#xff0c;我们着重讲解了其中的前 3 个环节&#xff0c;也就是预处理、特征选…

java---概念

一.配置环境&#xff08;三个变量&#xff09; 1.JAVA_HOME&#xff08;记录Java安装文件的路径&#xff09; 2.PATH&#xff08;系统直找的路径&#xff09; 3.CLASSPATH&#xff08;Java程序路径&#xff09; .;%JAVA_HOME%\lib 二.第一个Java程序 源代码&#xff1a; so…

使用kimi快速完成论文仿写的提示词,我帮你总结好了

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 在完成论文写作时&#xff0c;很多人都会想到“仿写”&#xff0c;但正确的做法是借鉴而非复制。今天我们将分享如何利用Kimi智能助手来提高论文写作的效率和质量&#xff0c;同时确保原…

Kubernetes快速入门

一、容器集群管理概述 1.1背景概述 容器技术的诞生虽解决了应用打包和发布的难题&#xff0c;但单一的容器技术工具并无 法支持起生产级大规模容器部署的场景。针对这一场景&#xff0c;容器管理与编排成为了容器技术发展的关键。Kubernetes 便是在这样的大背景下诞生的。 1.2…

【博客23】缤果Android_XXX调试助手模板(3款)V1.0(中级篇)

超级好用的Android_XXX调试助手模板 ( Android Studio Java) 备注: 仅模板无通信协议 开发工具: android-studio-2024.1.1.12-windows.exe 目录 一、软件概要&#xff1a; 二、软件界面&#xff1a; 1.App演示 2.其他扩展展示 2.1 自定义指令集 2.2 修改自定义指令集 …

IAM 编程访问和 AWS CLI

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; IAM 编程访问&#xff08;欢迎来到雲闪世界。&#xff09; IAM 编程访问是指使用访问密钥通过 API 和命令行工具访问 AWS 服务和资源。 当您为 IAM 用户启用编程访问时&#xff0c;您将生成可用于验证和…

Java-自定义注解中成员变量是Class<?>

在Java中,自定义注解可以包含各种类型的成员变量,包括 Class<?> 类型。这种类型的成员变量 通常用于表示某个类的类型信息。下面我将详细介绍如何定义一个包含 Class<?> 类型成员变量的 自定义注解,并给出一些示例代码。 1. 定义自定义注解 定义一个自定义…

行业大模型:信用评分大模型、生产优化大模型、库存管理大模型、物流行业大模型、零售行业大模型

金融行业大模型&#xff1a;信用评分大模型 信用评分模型在金融行业中扮演着至关重要的角色&#xff0c;它通过对个人或企业的信用状况进行评估&#xff0c;帮助金融机构有效控制风险&#xff0c;提高业务效率。以下是信用评分模型的特点及案例介绍&#xff1a; 信用评分模型…

高阶数据结构——B树

1. 常见的搜索结构 以上结构适合用于数据量相对不是很大&#xff0c;能够一次性存放在内存中&#xff0c;进行数据查找的场景。如果数据量很大&#xff0c;比如有100G数据&#xff0c;无法一次放进内存中&#xff0c;那就只能放在磁盘上了&#xff0c;如果放在磁盘上&#xff0…

[Unity]在场景中随机生成不同位置且不重叠的物体

1.前言 最近任务需要用到Unity在场景中随机生成物体&#xff0c;且这些物体不能重叠&#xff0c;简单记录一下。 参考资料:How to ensure that spawned targets do not overlap ? 2.结果与代码 结果如下所示&#xff1a; 代码如下所示&#xff1a; using System.Collec…

Java练习模拟考试答题系统小程序源码

&#x1f31f;高效备考秘籍&#xff01;如何玩转练习模拟考试答题系统✨ &#x1f4da; 引言&#xff1a;为什么选择模拟考试&#xff1f; 嘿小伙伴们&#xff0c;是不是每次临近大考都紧张得不行&#xff1f;别怕&#xff0c;今天就来揭秘一个备考神器——练习模拟考试答题系…

React使用useRef ts 报错

最近在写自己的React项目&#xff0c;我在使用useRef钩子函数的时候发现 TS2322: Type MutableRefObject<HTMLDivElement | undefined> is not assignable to type LegacyRef<HTMLDivElement> | undefined Type MutableRefObject<HTMLDivElement | undefined&g…

C++STL初阶(10):list的简易实现(下)

在上一文中我们完成了链表的多数基本接口&#xff0c;本文主要围绕构造函数进行补充 1. 链表的拷贝 在前文中我们没有手动实现拷贝构造&#xff0c;所以使用的就是编译器自动生成的浅拷贝 先使用一下编译器自动生成的浅拷贝&#xff1a; 我们在打印li2之前给li1加入一个数据&…

excel导入

Excel数据导入 使用easyexcel和hutool-poi实现excel导入 1、pom依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version></dependency><dependency><groupId…

【笔记】Swin-Transformer 的计算量与Transformer的计算量的对比:前者通过使用新颖的窗口技巧,将后者的高阶项变为低阶,大大降低了计算量

补充1&#xff1a; 局部窗口内的自注意力&#xff08;W-MSA&#xff09;: 在 Swin Transformer 中&#xff0c;输入特征图被划分为多个小的窗口&#xff08;例如 7x7 的窗口&#xff09;。在每个窗口内&#xff0c;计算自注意力机制&#xff08;W-MSA, Window-based Multi-Head…

基于LQR算法的机器人轨迹跟踪控制详解

本文摘要 本文详细介绍了基于线性二次型调节器&#xff08;LQR&#xff09;算法的机器人轨迹跟踪控制方法。首先&#xff0c;文章通过建立基于运动学模型的离散状态方程&#xff0c;来描述机器人的当前状态与目标状态之间的关系&#xff0c;并利用此模型进行状态误差的计算。接…