高阶数据结构--B树B+树实现原理B树模拟实现--Java

news2024/12/13 3:12:41

目录

一、B-树概念

二、B-树插入分析

1.用序列{53, 139, 75, 49, 145, 36, 101}构建B树的过程如下:

2.插入过程总结

三、B树插入实现

四、B+树

1.B+树概念

2.B+树的特性

 五、B+树应用

1.索引

 2.Mysql索引

3.InnoDB


一、B-树概念

1970 年, R.Bayer E.mccreight 提出了一种适合外查找的树,它是一种平衡的多叉树,称为 B ( 有些地方写的是 B- 树,注意不要误读成"B 减树 ") 一棵 M (M>2) B 树,是一棵平衡的 M 路平衡搜索树,可以是空树 或者满足一下性质:
1. 根节点至少有两个孩子
2. 每个非根节点至少有 M/2-1( 上取整 ) 个关键字 , 至多有 M-1 个关键字,并且以升序排列
例如:当 M=3 的时候,至少有 3/2=1.5 ,向上取整等于 2 2-1=1 个关键字,最多是 2 个关键字
3. 每个非根节点至少有 M/2( 上取整 ) 个孩子 , 至多有 M 个孩子
例如:当 M=3 的时候,至少有 3/2=1.5 ,向上取整等于 2 个孩子。最多有 3 个孩子。
4. key[i] key[i+1] 之间的孩子节点的值介于 key[i] key[i+1] 之间
5. 所有的叶子节点都在同一层

二、B-树插入分析

为了简单起见,假设 M = 3. 三叉树,每个节点中存储两个数据,两个数据可以将区间分割成三个部分,因此节点 应该有三个孩子 ,为了后续实现简单期间,节点的结构如下:
注意:孩子永远比数据多一个。
插入过程当中,有可能需要分裂,分裂的前提是:
假设,当前是要组成一个M路查找树,关键字数必须<=M-1(这里关键字数>M-1就要进行节点拆分) 规则是:把中间的元素,提取出来,放到父亲节点上,左边的单独构成一个节点,右边的单独构成一个节点。

1.用序列{53, 139, 75, 49, 145, 36, 101}构建B树的过程如下

2.插入过程总结

1. 如果树为空,直接插入新节点中,该节点为树的根节点
2. 树非空,找待插入元素在树中的插入位置(注意:找到的插入节点位置一定在叶子节点中)
3. 检测是否找到插入位置(假设树中的key唯一,即该元素已经存在时则不插入)
4. 按照插入排序的思想将该元素插入到找到的节点中
5. 检测该节点是否满足B-树的性质:即该节点中的元素个数是否等于M,如果小于则满足
6. 如果插入后节点不满足B树的性质,需要对该节点进行分裂:
(1申请新节点
(2找到该节点的中间位置
(3将该节点中间位置右侧的元素以及其孩子搬移到新节点中
(4将中间位置元素以及新节点往该节点的双亲节点中插入,即继续4
7. 如果向上已经分裂到根节点的位置,插入结束

三、B树插入实现

public class MyBTree {
    public static final int M=3;//三叉树
    static class BTreeNode {
        public int[] keys;//关键字
        public BTreeNode[] subs;//孩子节点
        public BTreeNode parent;//父节点
        public int UsedSize;//存储的关键字数量
        public BTreeNode() {
            //这里多给一个空间是为了分裂实现更容易
            keys=new int[M];
            subs=new BTreeNode[M+1];
        }
    }
    public BTreeNode root;

    /**
     * 插入一个元素
     * @param val
     */
    public boolean insert(int val) {
        //B树为空的时候
        if(root==null) {
            root=new BTreeNode();
            root.keys[0]=val;
            root.UsedSize=1;
            return true;
        }
        //当B树不为空的时候
        Pair<BTreeNode,Integer> pair=Find(val);
        if(pair.getVal()!=-1) {
            return false;
        }
        BTreeNode parent=pair.getKey();
        int index=parent.UsedSize-1;
        for(;index>=0;index--) {
            if(parent.keys[index]>=val) {
                parent.keys[index+1]=parent.keys[index];
            }else {
                break;
            }
        }
        parent.keys[index+1]=val;
        parent.UsedSize++;
        if(parent.UsedSize>=M) {
            split(parent);
            return true;
        }else {
            return true;
        }
    }

    /**
     * 分裂节点
     * @param cur
     */
    private void split(BTreeNode cur) {
        BTreeNode parent=cur.parent;
        BTreeNode newNode=new BTreeNode();
        int mid= cur.UsedSize>>1;
        int i=mid+1;
        int j=0;
        while (i<cur.UsedSize) {
            newNode.keys[j]=cur.keys[i];
            newNode.subs[j]=cur.subs[i];
            if(newNode.subs[j]!=null) {
                newNode.subs[j].parent=newNode;
            }
            i++;
            j++;
        }
        newNode.subs[j]=cur.subs[i];
        if(newNode.subs[j]!=null) {
            newNode.subs[j].parent=newNode;
        }
        newNode.UsedSize=j;
        cur.UsedSize=cur.UsedSize-j-1;
        if(cur==root) {
            root=new BTreeNode();
            root.keys[0]=cur.keys[mid];
            root.subs[0]=cur;
            root.subs[1]=newNode;
            root.UsedSize=1;
            cur.parent=root;
            newNode.parent=root;
            return;
        }
        newNode.parent=parent;

        int endT=parent.UsedSize-1;
        for (;endT>=0;endT--) {
            if(parent.keys[endT]>=cur.keys[mid]) {
                parent.keys[endT+1]=parent.keys[endT];
                parent.subs[endT+2]=parent.subs[endT+1];
            }else {
                break;
            }
        }
        parent.keys[endT+1]=cur.keys[mid];
        //将当前父亲节点的孩子节点更改为newNode
        parent.subs[endT+2]=newNode;
        parent.UsedSize++;
        if(parent.UsedSize>=M) {
            split(parent);
        }
    }

    /**
     * 查找B树中是否存在该元素
     * @param val
     * @return
     */
    private Pair<BTreeNode, Integer> Find(int val) {
        BTreeNode cur=root;
        BTreeNode parent = null;
        while (cur!=null) {
            int i=0;
            while (i<cur.UsedSize) {
                if(cur.keys[i]==val) {
                    return new Pair<>(cur,i);
                } else if (cur.keys[i]<val) {
                    i++;
                }else {
                    break;
                }
            }
            parent=cur;
            cur=cur.subs[i];
        }
        return new Pair<>(parent,-1);
    }

    /**
     * 验证B树,如果输出的是一个有序的结果则证明是B树
     * @param root
     */
    private void inorder(BTreeNode root){
        if(root == null)
            return;
        for(int i = 0; i < root.UsedSize; ++i){
            inorder(root.subs[i]);
            System.out.println(root.keys[i]);
        }
        inorder(root.subs[root.UsedSize]);
    }
}

B树验证

public static void main(String[] args) {
        MyBTree bTree=new MyBTree();
        int[] arrays={75,49,36,53,101,139,145};
        for (int i = 0; i < arrays.length; i++) {
            bTree.insert(arrays[i]);
        }
        bTree.inorder(bTree.root);
    }

输出结果 :

36
49
53
75
101
139
145

四、B+树

1.B+树概念

B+树是B-树的变形,也是一种多路搜索树:
1. 其定义基本与B-树相同,除了:
2. 非叶子节点的子树指针与关键字个数相同
3. 非叶子节点的子树指针p[i],指向关键字值属于【k[i]k[i+1])的子树
4. 为所有叶子节点增加一个链指针
5. 所有关键字都在叶子节点出现
B+树的搜索与B-树基本相同,区别是B+树只有达到叶子节点才能命中(B-树可以在非叶子节点中命中),其性能也等 价与在关键字全集做一次二分查找。

 

2.B+树的特性

1. 所有关键字都出现在叶子节点的链表中(稠密索引),且链表中的节点都是有序的。

2. 不可能在非叶子节点中命中。

3. 非叶子节点相当于是叶子节点的索引(稀疏索引),叶子节点相当于是存储数据的数据层。
4. 更适合文件索引系统

 

 五、B+树应用

1.索引

B+树最常见的应用就是用来做索引。索引通俗的说就是为了方便用户快速找到所寻之物,比如:书籍目录可以让读 者快速找到相关信息,hao123网页导航网站,为了让用户能够快速的找到有价值的分类网站,本质上就是互联网 页面中的索引结构。
MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构,简单来说:索引就是数据结构。 当数据量很大时,为了能够方便管理数据,提高数据查询的效率,一般都会选择将数据保存到数据库,因此数据库 不仅仅是帮助用户管理数据,而且数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引 用数据,这样就可以在这些数据结构上实现高级查找算法,该数据结构就是索引。

 2.Mysql索引

MyISAM引擎是MySQL5.5.8版本之前默认的存储引擎,不支持事物,支持全文检索,使用B+Tree作为索引结构, 叶节点的data域存放的是数据记录的地址,其结构如下:
上图是以以 Col1 为主键, MyISAM 的示意图,可以看出 MyISAM 的索引文件仅仅保存数据记录的地址 MyISAM 中,主索引和辅助索引( Secondary key )在结构上没有任何区别,只是主索引要求 key 是唯一的,而辅助索引的 key 可以重复 。如果想在 Col2 上建立一个辅助索引,则此索引的结构如下图所示:
同样也是一棵 B+Tree data 域保存数据记录的地址。因此, MyISAM 中索引检索的算法为首先按照 B+Tree 搜索算 法搜索索引,如果指定的Key 存在,则取出其 data 域的值,然后以 data 域的值为地址,读取相应数据记录。 MyISAM的索引方式也叫做 非聚集索引“。

3.InnoDB

InnoDB 存储引擎支持事务 ,其设计目标主要面向在线事务处理的应用,从 MySQL 数据库 5.5.8 版本开始, InnoDB 存储引擎是默认的存储引擎 InnoDB 支持 B+ 树索引、全文索引、哈希索引。但 InnoDB 使用 B+Tree 作为索引结构 时,具体实现方式却与MyISAM 截然不同。
第一个区别是 InnoDB 的数据文件本身就是索引文件 MyISAM 索引文件和数据文件是分离的,索引文件仅保存数 据记录的地址 。而 InnoDB 索引,表数据文件本身就是按 B+Tree 组织的一个索引结构,这棵树的叶节点 data 域保 存了完整的数据记录 。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。

上图是 InnoDB 主索引 (同时也是数据文件)的示意图,可以看到 叶节点包含了完整的数据记录,这种索引叫做聚 集索引 。因为 InnoDB 的数据文件本身要按主键聚集,所以 InnoDB 要求表必须有主键 MyISAM 可以没有), 如果 没有显式指定,则 MySQL 系统会自动选择一个可以唯一标识数据记录的列作为主键 如果不存在这种列,则 MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,这个字段长度为 6 个字节,类型为长整形

第二个区别是 InnoDB 的辅助索引 data 域存储相应记录主键的值而不是地址 , 所有辅助索引都引用主键作为data域。
聚集索引这种实现方式使得按主键的搜索十分高效 ,但是 辅助索引搜索需要检索两遍索引:首先检索辅助索引获得 主键,然后用主键到主索引中检索获得记录。

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

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

相关文章

网络安全——防火墙

基本概念 防火墙是一个系统&#xff0c;通过过滤传输数据达到防止未经授权的网络传输侵入私有网络&#xff0c;阻止不必要流量的同时允许必要流量进入。防火墙旨在私有和共有网络间建立一道安全屏障&#xff0c;因为网上总有黑客和恶意攻击入侵私有网络来破坏&#xff0c;防火…

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 多图推理

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 多图推理 flyfish 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_LoRA配置如何写 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_单图推理 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_原模型_单图推理 基于Q…

Ant Design Pro实战--day01

下载nvm https://nvm.uihtm.com/nvm-1.1.12-setup.zip 下载node.js 16.16.0 //非此版本会报错 nvm install 16.16.0 安装Ant Design pro //安装脚手架 npm i ant-design/pro-cli -g //下载项目 pro create myapp //选择版本 simple 安装依赖 npm install 启动umi yarn add u…

一、为什么要学习麒麟?

麒麟认证&#xff1a;开启职业晋升之门 当前&#xff0c;就业难已经成为一个普遍的社会问题。许多大学生毕业后面临着找工作的困境&#xff0c;他们往往发现自己很难找到满意的职位。即使有幸找到了工作&#xff0c;也经常需要应对工作压力大、薪资低等问题。除此之外&#xff…

python如何减小维度

ravel&#xff08;&#xff09;&#xff1a;将多维数组拉平&#xff08;一维&#xff09;。 flatten&#xff08;&#xff09;&#xff1a;将多维数组拉平&#xff0c;并拷贝一份。 squeeze&#xff08;&#xff09;&#xff1a;除去多维数组中&#xff0c;维数为1的维度&…

未来已来:人工智能如何重塑我们的生活与工作

引言 未来的生活和工作场景正从想象走向现实。想象一下&#xff0c;一个清晨&#xff0c;语音助手已经为你安排好一天的任务&#xff0c;自动驾驶汽车准时送你上班&#xff0c;智能冰箱提醒你需要补充的食材。曾经只存在于科幻小说中的场景&#xff0c;如今正在我们的身边实现。…

Adminer源码编译 精简语言中英文和基本使用方法

Adminer是一个小而强悍的基于web的数据库管理工具&#xff0c; 官方默认支持几十种语言&#xff0c;但是对于中国的用户而言只需要有中文和英文就够了&#xff0c;其他语言基本无用。这就需要我们下载Adminer源码自己编译 Adminer.php , 如下图所示 adminer 中英文语言精简版本…

字符编码讲解(C#)

在学习和编码的过程中&#xff0c;极容易遇到如下概念&#xff0c;他们有些是字符编码&#xff0c;有些是涉及的相关概念&#xff0c;接下来我将围绕下面的熟悉又陌生的概念做详细解释&#xff0c;并且梳理其之间的关系 UTF8&#xff0c; Unicode &#xff0c;ASCII&#xff0…

Mac备忘录表格中换行(`Option` + `Return`(回车键))

在Mac的ARM架构设备上&#xff0c;如果你使用的是Apple的原生“备忘录”应用来创建表格&#xff0c;换行操作可以通过以下步骤来实现&#xff1a; 在单元格中换行&#xff1a; 双击你想要编辑的单元格你可以输入文本&#xff0c;按Option&#xff08;⌥&#xff09; Enter来插…

Windows中将springboot项目运行到docker的容器中

0&#xff0c;先打包好项目&#xff0c;再启动docker 1&#xff0c;在Java项目根目录下创建一个名为Dockerfile的文件&#xff08;没有扩展名&#xff09;&#xff0c;并添加以下内容。 # 使用OpenJDK的基础镜像 FROM openjdk:8-jdk-alpine# 设置工作目录 WORKDIR /app# 将项…

HBU深度学习实验14.5-循环神经网络(1.5)

梯度爆炸实验 造成简单循环网络较难建模长程依赖问题的原因有两个&#xff1a;梯度爆炸和梯度消失。一般来讲&#xff0c;循环网络的梯度爆炸问题比较容易解决&#xff0c;一般通过权重衰减或梯度截断可以较好地来避免&#xff1b;对于梯度消失问题&#xff0c;更加有效的方式…

ZED相机应用

下载SDK wget https://stereolabs.sfo2.cdn.digitaloceanspaces.com/zedsdk/3.6/ZED_SDK_Ubuntu18_cuda11.5_v3.6.5.run 安装 ./ZED_SDK_Ubuntu18_cuda11.5_v3.6.5.run skip_python 测试 cd /usr/local/zed/tools ls ZED_Calibration ZED_Depth_Viewer ZED_Diagnostic ZED_E…

伟测科技再融资11.75亿:增收不增利,毛利率近年来持续下滑

《港湾商业观察》施子夫 王璐 12月9日&#xff0c;上海证券交易所上市审核委员会召开2024年第34次上市审核委员会审议会议&#xff0c;审议上海伟测半导体科技股份有限公司(再融资)&#xff08;以下简称&#xff0c;伟测科技&#xff1b;688372.SH&#xff09;事项。 今年8月…

Java爬虫设计:淘宝商品详情接口数据获取

1. 概述 淘宝商品详情接口&#xff08;如Taobao.item_get&#xff09;允许开发者通过编程方式&#xff0c;以JSON格式实时获取淘宝商品的详细信息&#xff0c;包括商品标题、价格、销量等。本文档将介绍如何设计一个Java爬虫来获取这些数据。 2. 准备工作 在开始之前&#x…

如何绕过IP禁令

网站、游戏和应用程序可以屏蔽特定IP地址&#xff0c;从而阻止使用该IP地址的任何人访问其服务。这称为IP禁令。管理员可以出于多种原因&#xff08;例如发出过多请求或可疑活动&#xff09;屏蔽IP地址。但是&#xff0c;这些禁令会使收集数据或访问在线内容变得更加困难。 一…

AI生成不了复杂前端页面?也许有解决方案了

在2024年&#xff0c;编程成为了人工智能领域最热门的赛道。AI编程技术正以惊人的速度进步&#xff0c;但在生成前端页面方面&#xff0c;AI的能力还是饱受质疑。自从ScriptEcho平台上线以来&#xff0c;我们收到了不少用户的反馈&#xff0c;他们表示&#xff1a;“生成的页面…

支持自定义离线地图地理区域,查询组件及数据源功能增强,DataEase开源BI工具v2.10.3 LTS发布

2024年12月9日&#xff0c;人人可用的开源BI工具DataEase正式发布v2.10.3 LTS版本。 这一版本的功能变动包括&#xff1a;数据源方面&#xff0c;API数据源和Excel数据源支持对字段类型和长度进行设置&#xff1b;图表方面&#xff0c;离线类地图支持自定义地理区域设置&#…

Ubuntu使用telnet连接时出现的错误:没有到主机的路由(能ping通但是还是报错)

Ubuntu使用telnet连接时出现的错误&#xff1a;没有到主机的路由&#xff08;能ping通但是还是报错&#xff09; 文章目录 Ubuntu使用telnet连接时出现的错误&#xff1a;没有到主机的路由&#xff08;能ping通但是还是报错&#xff09;0.环境1.检查是不是能ping通2.防火墙的问…

location重定向和nginx代理

文章目录 1 location重定向1.1 概述1.2 rewrite跳转1.3 用例1.4 实验1.4.1 基于域名的跳转1.4.2 基于ip的跳转1.4.3 基于后缀名的跳转 2 nginx的代理2.1 nginx内置变量2.2 正向代理2.2.1 固定正向代理2.2.2 自动代理 2.3 反向代理2.3.1 负载均衡的算法2.3.2 负载均衡的特点2.3.…

C# 委托详解02(委托和事件单独开一篇)

上一篇仅仅简单通过代码&#xff0c;以及相关运行示例&#xff0c;对委托有了基本概念。这一篇侧重对委托的更加深入的理解。有问题欢迎评论。本人技术不高&#xff0c;也欢迎指正。 希望看完我能够解释清楚以下问题&#xff0c;而大家能够从中找到自己的答案。 什么是委托&a…