【B-树、B+树、B* 树】多叉平衡搜索树,解决“IO次数”与“树高”问题~

news2024/11/18 21:31:00

目录

一、为什么会出现B-树?

面试题:

二、什么是B-树?

2.1、B+,B-树,B*树 导航

三、B-树的模拟实现

3.1、插入结点分析

3.1.1、根节点的分裂

3.1.2、继续插入数据,分裂子节点

3.2.3、再次插入数据,导致根节点继续向上分裂

3.2、性能分析

3.3、模拟实现B-树

四、总结

4.1、特性

小结


一、为什么会出现B-树?

        我们平时所知道的二叉搜索树,红黑树,AVL树,在访问一个大文件,或者是数据量比较大的时候,可能无法一次性把数据加载到内存中,而是只在树的结点中保留了指向该数据在磁盘中的位置,也就是说,真实的数据还在磁盘;我们也知道,在内存中访问数据是很快的,而在磁盘中访问数据相对就要慢很多了;

因此存在以下缺陷:

1. 树的高度比较高,查找的最坏情况是树的高度;

2. 数据量很大时,书中结点无法一次性加载到内存中,需要多次IO;(IO速度是很慢的);

解决方案:

1. 提高IO速度;

2. 降低树的高度——多叉平衡树;

面试题:

数据存储到hashmap和存储到文件中,有什么区别?

1. 存储到文件中读取速度慢;

2. hashmap相当于在内存中存储数据,一旦断电数据就丢失了;


二、什么是B-树?

注意:B-树 不是“B减树”,这个"-"只是一个分隔符,没有B-树这种数据结构;

2.1、B+,B-树,B*树 导航

什么是B树之前写过一篇文章,如下:(什么是B-树,B+树,以及特性)

http://t.csdn.cn/bnlJw

        简而言之,B树就是一颗N叉搜索树,每个结点(结点由关键字和孩子节点地址组成)中最多有N - 1,至少有N/2 - 1个关键字,这些关键字按照升序排序划分成了N个区间,孩子结点就在这些区间里,并且所有叶子结点都在同一层。

结点结构如下图:


三、B-树的模拟实现

3.1、插入结点分析

模拟实现B-树,我们通常会给每个节点多给一个空间,如下:

         为什么要多给一个空间呢?假设不多给一个空间,也就是N = 4,是一个四叉树,但是一个节点最多三个关键字,那么当你要放入第四个元素的时候,进行分裂的时候就不好分裂了,因为你要分情况,看把谁提走。如下图:(以下只是一个简略图,实际上右边树的分叉点不一定在最右边)

3.1.1、根节点的分裂

理解了上面的内容后,首先我们来看一下根节点该如何分裂~

例如一个三叉树,那么刚刚说到,需要多给一个结点一个空间,也就是一个结点有三个关键字,当这个三个关键字都被填满了,就需要分裂,如何分裂呢?步骤是以下三点:

1. 找到该结点的中间位置 m;

2. m 的右边数据,存储到一个新的结点中;

3. m 这个数据,存储到一个新的结点,变成了新的根节点,对于根节点而言,原来的一个结点,被分裂成了最终的三个结点;

如下图:

3.1.2、继续插入数据,分裂子节点

这里分裂的逻辑和分裂根节点相似,具体细节会在以下图中中讲到:

可以观察到:

B树的分裂是横向的,横向增长,也就意味着树的高度没有增加。

哪什么时候会增加树的高度呢?

只有分裂根节点的时候,树的高度才会增加。

3.2.3、再次插入数据,导致根节点继续向上分裂

        假设插入数据189,那么就会导致子节点分裂,子节点分裂又会向根节点提供一个数据,接着根节点就满了,就开始了根节点向上分裂,如下图:

3.2、性能分析

        实际的B树不会这么平凡的分裂,一般是1024叉树,那么想象一下,当M = 1024是,插入数据时,这个树的高度会如何变化?

第一层:1023个关键字;

第二层:1024个子结点 * 1023个关键字,大约是100W的级别;

第三层:1024 * 1024 * 1023,大约是10亿的级别;

第四层:1024 * 1024 * 1024 * 1023,大约是万亿级别;

......

多么恐怖的数量,指数爆炸!!!

        那么它的时间复杂度在logM N ~ logM/2 N 之间,也就是说M越大,效率越高,但是M也不是越大越好,因为 会有空间的浪费,有因为结点满了要拷走一半,浪费一个结点一半的空间;

        并且一旦找到结点,可以利用二分查找可以快速定位到该元素,大大减少了读取磁盘的次数!!!

3.3、模拟实现B-树

具体的思路如上,详细代码和注释如下:

public class MyBtree {

    static class BTRNode {
        public int[] keys;//关键字
        public BTRNode[] subs;//孩子
        public BTRNode parent;//当前孩子节点的父亲结点
        public int usedSize;//当前结点关键字的数量

        public BTRNode() {
            //这里多给一个空间,是为了更好的分裂
            this.keys =  new int[M];
            this.subs = new BTRNode[M + 1];
        }
    }

    public static final int M = 3;
    public BTRNode root;//当前B树的根节点

    /**
     * 插入元素
     * @param key
     */
    public boolean insert(int key) {
        if(root == null) {
            root = new BTRNode();
            root.keys[0] = key;
            root.usedSize++;
            return true;
        }
        //2. 当树不为空,就看Key是否存在B树中
        Pair<BTRNode, Integer> pair = find(key);
        //根据判断这里的val值是否为-1, 确定是否存在key
        if(pair.getVal() != -1) {//结点存在
            return false;
        }
        //不存在key,就要进行插入(插入排序)
        BTRNode parent = pair.getKey();
        int index = parent.usedSize - 1;
        for(; index >= 0; index--) {
            if(parent.keys[index] >= key) {
                parent.keys[index + 1] = parent.keys[index];
            } else {
                break;
            }
        }
        parent.keys[index + 1] = key;
        parent.usedSize++;
        //为什么不处理孩子?
        //因为每次插入都是在叶子结点,所以叶子结点都是null
        if(parent.usedSize < M) {
            //没有满
            return true;
        } else {
            //满了,需要分裂
            split(parent);
            return true;
        }
    }

    /**
     * 分裂逻辑
     * @param cur 代表当前需要分裂的结点
     */
    private void split(BTRNode cur) {
        BTRNode newNode = new BTRNode();
        //1.先存储当前需要分裂结点的父节点
        BTRNode parent = cur.parent;
        //2.开始挪动数据
        int mid = cur.usedSize >> 1;
        int i = mid + 1;
        int j = 0;
        for(; i < cur.usedSize; i++) {
            //这里既要拷贝数值也需要拷贝孩子的地址
            newNode.keys[j] = cur.keys[i];
            newNode.subs[j] = cur.subs[i];
            //处理刚刚拷贝过来的孩子节点的父亲结点,为新分裂的结点
            if(newNode.subs[j] != null){
                newNode.subs[j].parent = newNode;
            }
            j++;
        }
        //多拷贝一次孩子
        newNode.subs[j] = cur.subs[i];
        if(newNode.subs[j] != null){
            newNode.subs[j].parent = newNode;
        }
        //更新当前新节点的有效数据
        newNode.usedSize = j;
        //这里-1是因为将来要提到父亲结点的key
        cur.usedSize = cur.usedSize - j - 1;

        //处理根节点的情况
        if(cur == root) {
            root = new BTRNode();
            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;
        int midVal = cur.keys[mid];
        for(; endT >= 0; endT--) {
            if(parent.keys[endT] >= midVal) {
                parent.keys[endT + 1] = parent.keys[endT];
                parent.subs[endT + 2] = parent.subs[endT + 1];
            } else {
                break;
            }
        }
        parent.keys[endT + 1] = midVal;
        //将当前父亲结点的孩子结点新增为newNode;
        parent.subs[endT + 2] = newNode;
        parent.usedSize++;
        if(parent.usedSize >= M) {
            split(parent);
        }
    }

    /**
     * 返回一个值是否可行?
     * 地址-》无法记录你存储在哪
     * 所以需要返回一对
     * @param key
     * @return
     */
    private Pair<BTRNode, Integer> find(int key) {
        BTRNode cur = root;
        BTRNode parent = null;
        while(cur != null) {
            int i = 0;
            while(i < cur.usedSize) {
                if(cur.keys[i] == key) {
                    //返回一个当前找到的结点 和 当前结点的下标
                    return new Pair<>(cur, i);
                } else if(cur.keys[i] < key) {
                    i++;
                } else {
                    break;
                }
            }
            parent = cur;
            cur = cur.subs[i];
        }
        return new Pair<>(parent, -1);
    }

    //测试
    public static void main(String[] args) {
        MyBtree myBtree = new MyBtree();
        int[] arr = {53, 139, 75, 49, 145, 36, 101};
        for(int i = 0; i < arr.length; i++) {
            myBtree.insert(arr[i]);
        }
        System.out.println("123121");
    }
}

四、总结

4.1、特性

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

2. 每个非根节点至少有M/2-1(上取整)个关键字,至多有M-1个关键字,并且以升序排列。

3. 每个非根节点至少有M/2(上取整)个孩子,至多有M个孩子。

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

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


小结

面试不会考这样的,但是建议多敲~

思想要懂!


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

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

相关文章

tomcat和apache有什么区别?如何将内网发布到互联网访问?

tomcat、 apache是比较常用的搭建服务器的中间件&#xff0c;它们之间还是有一些区别差异的&#xff0c;我们通常会根据本地应用场景来选择合适的中间件来搭建服务器。在内网本地部署搭建服务器后&#xff0c;还可以通过快解析端口映射方法&#xff0c;将内网应用地址发布到互联…

Android原生检测Selinux的三种方法

本文介绍 3 种检测 Android 设备 SELinux 状态的方法, Java 层检测Selinux已经没有太多意义,因为不是很靠谱,随便一个hook代码就能绕过,所以我要告诉你如何在 C 层完成检测。这几种方法在效率和抵抗mock SELinux State 的技术方面都不相同,因此在使用之前你需要知道每种方…

Windows server——部署DNS服务

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 本章重点 一.DNS概述 1.DNS的诞生 二.DNS的功能 使用域名访问具有以下优点…

【大厂高频真题100题】《二叉树的序列化与反序列化》 真题练习第23题 持续更新~

二叉树的序列化与反序列化 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。 请设计一个算法来实现二叉树的序列化与反序列化。这里不限…

c语言 图形化贪吃蛇 多种功能 无需安装第三方库 课设 (附代码)

前言 类贪吃蛇是利用c语言模仿并实现经典游戏贪吃蛇&#xff0c;使其在窗口有贪吃蛇活动的规定范围&#xff0c;并完成一系列包括但不限于模仿蛇的移动&#xff0c;方向控制&#xff0c;吃到食物加分&#xff0c;撞上墙壁及蛇头碰到蛇身死亡等游戏功能。 附加功能&#xff1a…

软件测试复习03:动态测试——白盒测试

作者&#xff1a;非妃是公主 专栏&#xff1a;《软件测试》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录逻辑覆盖法&#xff1a;最常用程序插桩技术基本路径法点覆盖边覆盖边对覆盖主路径覆盖符号测试错误…

前端leaflet框选下载bing遥感图

微软必应bing遥感图。bing地图比百度遥感图清晰很多&#xff0c;19级&#xff0c;百度是18级&#xff0c;同样的18级&#xff0c;bing地图比百度也清晰很多。所以没有必要用百度地图了。不过bing地图仅用于学习&#xff0c;商用要付费。参考了https://xiaozhuanlan.com/topic/6…

CV+Deep Learning——网络架构Pytorch复现系列——Detection(二:RtinaNet)更换backbones

上一话 CVDeep Learning——网络架构Pytorch复现系列——Detection(一&#xff1a;SSD:Single Shot MultiBox Detector 4.推理Detect)https://blog.csdn.net/XiaoyYidiaodiao/article/details/128683973?spm1001.2014.3001.5501 复现Object Detection&#xff0c;会复现的网络…

设计模式-门面模式

医院的例子 现代的软件系统都是比较复杂的&#xff0c;设计师处理复杂系统的一个常见方法便是将其"分而治之"&#xff0c;把一个系统划分为几个较小的子系统。如果把医院作为一个子系统&#xff0c;按照部门职能&#xff0c;这个系统可以划分为挂号、门诊、划价、化…

Web--Jedis

# Redis 1. 概念&#xff1a; redis是一款高性能的NOSQL系列的非关系型数据库 1.1.什么是NOSQL NoSQL(NoSQL Not Only SQL)&#xff0c;意即“不仅仅是SQL”&#xff0c;是一项全新的数据库理念&#xff0c;泛指非关系型的数据库。 随着互联网web2.0网…

Arch Linux 来报道!!!

导读Ubuntu 的制造商 Canonical 早已和微软进行合作&#xff0c;让我们体验了极具争议的 Bash on Windows。外界对此也是褒贬不一&#xff0c;许多 Linux 重度用户则是质疑其是否有用&#xff0c;以及更进一步认为 Bash on Windows 是一个安全隐患。 Unix 的 Bash 是通过 WSL (…

LabVIEW使用VI脚本创建和打开VI

LabVIEW使用VI脚本创建和打开VI按照下列步骤&#xff0c;可以创建一个VI&#xff0c;该VI使用VI脚本创建和打开VI。创建VI前&#xff0c;需先了解VI脚本的基本内容。必须启用VI脚本&#xff0c;才能显示VI脚本选板&#xff0c;使用相关属性和方法。1. 选择文件新建VI&#xff0…

第1章 ESP32-VSCODE环境搭建

ESP32-VSCODE环境搭建 环境安装 在Windows中安装ESP-IDF在vscode中安装Espressif IDF插件开始配置Espressif IDF插件 在vscode最上方点击&#xff1a;查看->命令面板&#xff0c;输入esp-idf:config&#xff0c;选择ESP-IDF:Configure ESP-IDF extension 选择EXPRESS Sele…

java07-面向对象2

一&#xff1a;面向对象的第二个特征&#xff1a;继承&#xff0c;关键字extends 1.继承的好处&#xff1a; 1&#xff09;减少代码的冗余&#xff0c;提高代码的复用​​​​​性。 2&#xff09;便于功能的扩展 3&#xff09;为之后多态性的使用&#xff0c;提供了前提 …

【阶段三】Python机器学习27篇:机器学习项目实战:数据降维:主成分分析PCA、基本原理与PCA模型:人脸识别

本篇的思维导图: 数据降维:主成分分析PCA 建立模型分析特征数据时,很可能会面临特征数据维度过大的问题。例如,根据已有的信用卡持有人信息及其违约数据来建立信用卡违约预测模型时,数据可能包含申请人的收入、年龄、性别、婚姻状况、工作单位等数百个维度的特征。…

【图像分类】基于yolov5的钢板表面缺陷分类(附代码和数据集)

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 Hello&#xff0c;大家好&#xf…

ArcGIS基础实验操作100例--实验100三维可视性分析

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 空间分析篇--实验100 三维可视性分析 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&a…

JavaScript---DOM---高级事件---1.8

注册事件&#xff08;绑定事件&#xff09; 给元素添加事件称为注册事件或绑定事件。注册事件有两种方式&#xff1a;传统方式、方法监听注册方式。 传统注册方式&#xff1a; 利用on开头的事件onclick&#xff1a; <button onclick"alert(hi~)"></butt…

测试用例具体的设计方法

等价类法由于输入的集合是无穷的&#xff0c;不能全部覆盖到&#xff0c;所以通过划分若干个等价类&#xff0c;选出有代表性的达到尽量多的功能覆盖有效等价类&#xff1a;根据规格说明书是合理的、有意义的输入数据构成的集合无效等价类&#xff1a;根据需求说明书是不合理&a…

246页10万字省级政务专用云项目技术方案

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 目录 对本项目的技术服务类总体…