数据结构与算法-15_ B 树

文章目录

      • 1.概述
      • 2.实现
        • 定义节点
        • 多路查找
        • 插入 key 和 child
        • 定义树
        • 插入
        • 分裂
        • 删除
        • 代码

1.概述

  • B树(B-Tree)结构是一种高效存储和查询数据的方法。
  • B树主要思想是将每个节点扩展成多个子节点,以减少查找所需的次数。
  • B树结构非常适合应用于磁盘等大型存储器的高效操作,被广泛应用于关系数据库和文件系统中。

特性

一棵 B-树具有以下性质

特性1:每个节点 x 具有

  • 属性 n,表示节点 x 中 key 的个数
  • 属性 leaf,表示节点是否是叶子节点
  • 节点 key 可以有多个,以升序存储

特性2:每个非叶子节点中的孩子数是 n + 1、叶子节点没有孩子

特性3:最小度数t(节点的孩子数称为度)和节点中键数量的关系如下:

最小度数t键数量范围
21 ~ 3
32 ~ 5
43 ~ 7
n(n-1) ~ (2n-1)

其中,当节点中键数量达到其最大值时,即 3、5、7 … 2n-1,需要分裂

特性4:叶子节点的深度都相同

问:

B-树为什么有最小度数的限制?

答:

B树中有最小度数的限制是为了保证B树的平衡特性。

在B树中,每个节点都可以有多个子节点,这使得B树可以存储大量的键值,但也带来了一些问题。如果节点的子节点数量太少,那么就可能导致B树的高度过高,从而降低了B树的效率。此外,如果节点的子节点数量太多,那么就可能导致节点的搜索、插入和删除操作变得复杂和低效。

最小度数的限制通过限制节点的子节点数量,来平衡这些问题。在B树中,每个节点的子节点数量都必须在一定的范围内,即t到2t之间(其中t为最小度数)

B-树与 2-3 树、2-3-4 树的关系

  1. 2-3树是最小度数为2的B树,其中每个节点可以包含2个或3个子节点。
  2. 2-3-4树是最小度数为2的B树的一种特殊情况,其中每个节点可以包含2个、3个或4个子节点。
  3. B树是一种更加一般化的平衡树,可以适应不同的应用场景,其节点可以包含任意数量的键值,节点的度数取决于最小度数t的设定。

2.实现

定义节点
static class Node {boolean leaf = true;int keyNumber;int t;int[] keys;Node[] children;    public Node(int t) {this.t = t;this.keys = new int[2 * t - 1];this.children = new Node[2 * t];}@Overridepublic String toString() {return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));}
}
  • leaf 表示是否为叶子节点
  • keyNumber 为 keys 中有效 key 数目
  • t 为最小度数,它决定了节点中key 的最小、最大数目,分别是 t-1 和 2t-1
  • keys 存储此节点的 key(keys 应当改为 entries 以便同时保存 key 和 value)
  • children 存储此节点的 child
  • toString 调试和测试
多路查找

节点类添加 get 方法

Node get(int key) {int i = 0;while (i < keyNumber && keys[i] < key) {i++;}if (i < keyNumber && keys[i] == key) {return this;}if (leaf) {return null;}return children[i].get(key);
}
插入 key 和 child

节点类添加 insertKey 和 insertChild 方法

void insertKey(int key, int index) {System.arraycopy(keys, index, keys, index + 1, keyNumber - index);keys[index] = key;keyNumber++;
}void insertChild(Node child, int index) {System.arraycopy(children, index, children, index + 1, keyNumber - index);children[index] = child;
}

作用是向 keys 数组或 children 数组指定 index 处插入新数据,注意

  • 由于使用了静态数组,并且不会在新增或删除时改变它的大小,因此需要额外的 keyNumber 来指定数组内有效 key 的数目
    • 插入时 keyNumber++
    • 删除时减少 keyNumber 的值即可
  • children 不会单独维护数目,它比 keys 多一个
  • 如果这两个方法同时调用,注意它们的先后顺序,insertChild 后调用,因为它计算复制元素个数时用到了 keyNumber
定义树
public class BTree {final int t;final int MIN_KEY_NUMBER;final int MAX_KEY_NUMBER;Node root;public BTree() {this(2);}public BTree(int t) {this.t = t;MIN_KEY_NUMBER = t - 1;MAX_KEY_NUMBER = 2 * t - 1;root = new Node(t);}
}
插入
public void put(int key) {doPut(null, 0, root, key);
}private void doPut(Node parent, int index, Node node, int key) {int i = 0;while (i < node.keyNumber && node.keys[i] < key) {i++;}if (i < node.keyNumber && node.keys[i] == key) {return;}if (node.leaf) {node.insertKey(key, i);} else {doPut(node, i, node.children[i], key);}if (isFull(node)) {split(parent, index, node);}
}
  • 先查找本节点中的插入位置 i,如果没有空位(key 被找到),走更新的逻辑
  • 接下来分两种情况
    • 如果节点是叶子节点,可以直接插入了
    • 如果节点是非叶子节点,需要继续在 children[i] 处继续递归插入
  • 无论哪种情况,插入完成后都可能超过节点 keys 数目限制,此时应当执行节点分裂
    • 参数中的 parent 和 index 都是给分裂方法用的,代表当前节点父节点,和分裂节点是第几个孩子

判断依据:

boolean isFull(Node node) {return node.keyNumber == MAX_KEY_NUMBER;
}
分裂
void split(Node parent, int index , Node left) {if (parent == null) {Node newRoot = new Node(this.t);newRoot.leaf = false;newRoot.insertChild(root, 0);root = newRoot;parent = newRoot;}Node right = new Node(this.t);right.leaf = left.leaf;right.keyNumber = t - 1;System.arraycopy(left.keys, t, right.keys, 0, t - 1);if (!left.leaf) {System.arraycopy(left.children, t, right.children, 0, t);}left.keyNumber = t - 1;int mid = left.keys[t - 1];parent.insertKey(mid, index);parent.insertChild(right, index + 1);}

两种情况:

  • 如果 parent == null 表示要分裂的是根节点,此时需要创建新根,原来的根节点作为新根的 0 孩子
  • 否则
    • 创建 right 节点(分裂后大于当前 left 节点的),把 t 以后的 key 和 child 都拷贝过去
    • t-1 处的 key 插入到 parent 的 index 处,index 指 left 作为孩子时的索引
    • right 节点作为 parent 的孩子插入到 index + 1 处
删除

case 1:当前节点是叶子节点,没找到

case 2:当前节点是叶子节点,找到了

case 3:当前节点是非叶子节点,没找到

case 4:当前节点是非叶子节点,找到了

case 5:删除后 key 数目 < 下限(不平衡)

case 6:根节点

代码
package com.itheima.algorithm.btree;import java.util.Arrays;/*** <h3>B-树</h3>*/
@SuppressWarnings("all")
public class BTree {static class Node {int[] keys; // 关键字Node[] children; // 孩子int keyNumber; // 有效关键字数目boolean leaf = true; // 是否是叶子节点int t; // 最小度数 (最小孩子数)public Node(int t) { // t>=2this.t = t;this.children = new Node[2 * t];this.keys = new int[2 * t - 1];}public Node(int[] keys) {this.keys = keys;}@Overridepublic String toString() {return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));}// 多路查找Node get(int key) {int i = 0;while (i < keyNumber) {if (keys[i] == key) {return this;}if (keys[i] > key) {break;}i++;}// 执行到此时 keys[i]>key 或 i==keyNumberif (leaf) {return null;}// 非叶子情况return children[i].get(key);}// 向 keys 指定索引处插入 keyvoid insertKey(int key, int index) {System.arraycopy(keys, index, keys, index + 1, keyNumber - index);keys[index] = key;keyNumber++;}// 向 children 指定索引处插入 childvoid insertChild(Node child, int index) {System.arraycopy(children, index, children, index + 1, keyNumber - index);children[index] = child;}int removeKey(int index) {int t = keys[index];System.arraycopy(keys, index + 1, keys, index, --keyNumber - index);return t;}int removeLeftmostKey() {return removeKey(0);}int removeRightmostKey() {return removeKey(keyNumber - 1);}Node removeChild(int index) {Node t = children[index];System.arraycopy(children, index + 1, children, index, keyNumber - index);children[keyNumber] = null;return t;}Node removeLeftmostChild() {return removeChild(0);}Node removeRightmostChild() {return removeChild(keyNumber);}void moveToLeft(Node left) {int start = left.keyNumber;if (!leaf) {for (int i = 0; i <= keyNumber; i++) {left.children[start + i] = children[i];}}for (int i = 0; i < keyNumber; i++) {left.keys[left.keyNumber++] = keys[i];}}Node leftSibling(int index) {return index > 0 ? children[index - 1] : null;}Node rightSibling(int index) {return index == keyNumber ? null : children[index + 1];}}Node root;int t; // 树中节点最小度数final int MIN_KEY_NUMBER; // 最小key数目final int MAX_KEY_NUMBER; // 最大key数目public BTree() {this(2);}public BTree(int t) {this.t = t;root = new Node(t);MAX_KEY_NUMBER = 2 * t - 1;MIN_KEY_NUMBER = t - 1;}// 1. 是否存在public boolean contains(int key) {return root.get(key) != null;}// 2. 新增public void put(int key) {doPut(root, key, null, 0);}private void doPut(Node node, int key, Node parent, int index) {int i = 0;while (i < node.keyNumber) {if (node.keys[i] == key) {return; // 更新}if (node.keys[i] > key) {break; // 找到了插入位置,即为此时的 i}i++;}if (node.leaf) {node.insertKey(key, i);} else {doPut(node.children[i], key, node, i);}if (node.keyNumber == MAX_KEY_NUMBER) {split(node, parent, index);}}/*** <h3>分裂方法</h3>** @param left   要分裂的节点* @param parent 分裂节点的父节点* @param index  分裂节点是第几个孩子*/void split(Node left, Node parent, int index) {// 分裂的是根节点if (parent == null) {Node newRoot = new Node(t);newRoot.leaf = false;newRoot.insertChild(left, 0);this.root = newRoot;parent = newRoot;}// 1. 创建 right 节点,把 left 中 t 之后的 key 和 child 移动过去Node right = new Node(t);right.leaf = left.leaf;System.arraycopy(left.keys, t, right.keys, 0, t - 1);// 分裂节点是非叶子的情况if (!left.leaf) {System.arraycopy(left.children, t, right.children, 0, t);for (int i = t; i <= left.keyNumber; i++) {left.children[i] = null;}}right.keyNumber = t - 1;left.keyNumber = t - 1;// 2. 中间的 key (t-1 处)插入到父节点int mid = left.keys[t - 1];parent.insertKey(mid, index);// 3. right 节点作为父节点的孩子parent.insertChild(right, index + 1);}// 3. 删除public void remove(int key) {doRemove(root, key, null, 0);}private void doRemove(Node node, int key, Node parent, int index) {int i = 0;while (i < node.keyNumber) {if (node.keys[i] >= key) {break;}i++;}if (node.leaf) {if (notFound(node, key, i)) { // case 1return;}node.removeKey(i);  // case 2} else {if (notFound(node, key, i)) { // case 3doRemove(node.children[i], key, node, i);} else { // case 4Node s = node.children[i + 1];while (!s.leaf) {s = s.children[0];}int k = s.keys[0];node.keys[i] = k;doRemove(node.children[i + 1], k, node, i + 1);}}if (node.keyNumber < MIN_KEY_NUMBER) { // case 5balance(node, parent, index);}}private boolean notFound(Node node, int key, int i) {return i >= node.keyNumber || (i < node.keyNumber && node.keys[i] != key);}private void balance(Node node, Node parent, int i) {if (node == root) {if (root.keyNumber == 0 && root.children[0] != null) {root = root.children[0];}return;}Node leftSibling = parent.leftSibling(i);Node rightSibling = parent.rightSibling(i);if (leftSibling != null && leftSibling.keyNumber > MIN_KEY_NUMBER) {rightRotate(node, leftSibling, parent, i);return;}if (rightSibling != null && rightSibling.keyNumber > MIN_KEY_NUMBER) {leftRotate(node, rightSibling, parent, i);return;}if (leftSibling != null) {mergeToLeft(leftSibling, parent, i - 1);} else {mergeToLeft(node, parent, i);}}private void mergeToLeft(Node left, Node parent, int i) {Node right = parent.removeChild(i + 1);left.insertKey(parent.removeKey(i), left.keyNumber);right.moveToLeft(left);}private void rightRotate(Node node, Node leftSibling, Node parent, int i) {node.insertKey(parent.keys[i - 1], 0);if (!leftSibling.leaf) {node.insertChild(leftSibling.removeRightmostChild(), 0);}parent.keys[i - 1] = leftSibling.removeRightmostKey();}private void leftRotate(Node node, Node rightSibling, Node parent, int i) {node.insertKey(parent.keys[i], node.keyNumber);if (!rightSibling.leaf) {node.insertChild(rightSibling.removeLeftmostChild(), node.keyNumber + 1);}parent.keys[i] = rightSibling.removeLeftmostKey();}
}

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

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

相关文章

golang结构与接口方法实现与交互使用示例

1.定义结构 // 结构定义 type VideoFrame struct {id inthead []bytelen int64data []byte } 2.实现结构方法 // 生成结构字段的get与set方法 // func (v *VideoFrame) Id() int {return v.id }func (v *VideoFrame) SetId(id int) {v.id id }func (v *VideoFrame) He…

React中使用 ts 后,craco库配置别名时需要注意什么?

文章目录 前言编译报错如下解决方式总结 前言 我们都知道craco库可以用来覆盖react配置&#xff0c;如设置别名等。但是在项目使用 Typescript 后&#xff0c;我们需要额外配置&#xff0c;否则会造成编译报错。 详细craco配置可以查看之前文章&#xff1a; 项目初始化与配置…

JDBC是什么?它如何工作?

一、JDBC概述 JDBC&#xff08;Java Database Connectivity&#xff09;是Java语言与数据库之间进行交互的API。它允许Java程序通过SQL&#xff08;结构化查询语言&#xff09;来执行各种数据库操作&#xff0c;如查询、更新、删除等。JDBC是Java应用程序访问数据库的标准方式…

探索 LLM 预训练的挑战,GPU 集群架构实战

万卡 GPU 集群实战&#xff1a;探索 LLM 预训练的挑战 一、背景 在过往的文章中&#xff0c;我们详细阐述了LLM预训练的数据集、清洗流程、索引格式&#xff0c;以及微调、推理和RAG技术&#xff0c;并介绍了GPU及万卡集群的构建。然而&#xff0c;LLM预训练的具体细节尚待进一…

Vue06-el与data的两种写法

一、el属性 用来指示vue编译器从什么地方开始解析 vue的语法&#xff0c;可以说是一个占位符。 1-1、写法一 1-2、写法二 当不使用el属性的时候&#xff1a; 两种写法都可以。 v.$mount(#root);写法的好处&#xff1a;比较灵活&#xff1a; 二、data的两种写法 2-1、对象式…

【java深拷贝和浅拷贝区别是什么?】

文章目录 Java深拷贝和浅拷贝的区别&#xff08;1&#xff09;浅拷贝&#xff08;Shallow Copy&#xff09;&#xff08;2&#xff09;深拷贝&#xff08;Deep Copy&#xff09; 总结 Java深拷贝和浅拷贝的区别 在Java中&#xff0c;深拷贝&#xff08;Deep Copy&#xff09;和…

【面试干货】SQL中count(*)、count(1)和count(column)的区别与用法

【面试干货】SQL中count&#xff08;*&#xff09;、count&#xff08;1&#xff09;和count&#xff08;column&#xff09;的区别与用法 1、count(*)2、count(1)3、count(column) &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在SQL中&a…

Oracle作业调度器Job Scheduler

Oracle数据库调度器 (Oracle Database Scheduler) 在数据库管理系统中&#xff0c;数据库调度器负责调度和执行数据库中的存储过程、触发器、事件等。它可以确保这些操作在正确的时间和条件下得到执行&#xff0c;以满足业务需求。 1、授权用户权限 -- 创建目录对象 tmp_dir…

5.4.18 加载某三方模块使用内核 panic 问题分析

环境信息 内核版本&#xff1a;5.4.18 cpu 架构&#xff1a;arm64 问题描述 加载了产品的某三方 ko 文件使用过程中&#xff0c;会触发如下 panic 信息&#xff1a; [ 218.133479][ 0] Unable to handle kernel NULL pointer dereference at virtual address 0000000000…

CSS函数:fit-content与matrix的使用

网格函数 fit-content()属于网格函数&#xff0c;除此之外的网格函数还有&#xff1a;CSS函数&#xff1a; 实现数据限阈的数字函数。顾名思义&#xff0c;这三个函数只能在网格布局中使用。fit-content()函数主要是用于给定布局可用大小&#xff0c;适应内容&#xff0c;其功…

MySQL事务与MVCC

文章目录 事务和事务的隔离级别1.为什么需要事务2.事务特性1_原子性&#xff08;atomicity&#xff09;2_一致性&#xff08;consistency&#xff09;3_持久性&#xff08;durability&#xff09;4_隔离性&#xff08;isolation&#xff09; 3.事务并发引发的问题1_脏读2_不可重…

基于小波域优化Savitzky–Golay滤波器的脑电图信号的运动伪影去除方法(MATLAB R2018A)

在获取或采集数据的过程中&#xff0c;不可避免地将噪声引入到数据中&#xff0c;噪声的存在使得原始数据发生变异&#xff0c;对数据的处理及分析产生严重地影响。常用的去噪模型有平滑去噪、均值去噪。其中&#xff0c;平滑去噪又包括移动平均平滑法和Savitzky-Golay卷积平滑…

一周学会Django5 Python Web开发 - Django5内置Auth认证系统-用户注册实现

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计57条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

keil下载及安装(社区版本)

知不足而奋进 望远山而前行 目录 文章目录 前言 Keil有官方版本和社区版本&#xff0c;此文章为社区版本安装&#xff0c;仅供参考。 1.keil MDK 2.keil社区版介绍 3.keil下载 (1)打开进入登录界面 (2)点击下载,跳转到信息页面 (3)填写个人信息,点击提交 (4)点击下载…

韩国Neowine推出第三代强加密芯片ALPU-CV

推出第三代加密芯片&#xff1b;是ALPU系列中的高端IC&#xff1b;是一款高性能车规级加密芯片&#xff1b;其加密性更强、低耗电、体积小&#xff1b;使得防复制、防抄袭板子的加密性能大大提升&#xff0c;该芯片通过《AEC-Q100》认证&#xff0c;目前已经在国产前装车辆配件…

VMware Workstation虚拟机安装 CentOS 7.9 后ping ip地址出现错误:Network is unreachable

VMware Workstation虚拟机安装 CentOS 7.9 后ping ip地址出现错误&#xff1a;Network is unreachable 解决步骤&#xff1a; 进入目录 进入/etc/sysconfig/network-scripts/&#xff0c; cd /etc/sysconfig/network-scripts/修改文件 vi ifcfg-ens33变更项 ONBOOTyes保存…

算法学习笔记(7.6)-贪心算法(霍夫曼编码)

目录 1.什么是霍夫曼树 2.霍夫曼树的构造过程 3.霍夫曼编码 3.1具体的作用-频率统计 ##实战题目 1.什么是霍夫曼树 给定N个权值作为N个叶子结点&#xff0c;构造一棵二叉树&#xff0c;若该树的带权路径长度达到最小&#xff0c;称这样的二叉树为最优二叉树&#xff0c;也…

ast.js是什么?

在devtools分析网站时&#xff0c;出现了ast.js的页面。那么&#xff0c;什么是ast.js?它有什么用&#xff1f; 经查询&#xff0c;AST是抽象语法树&#xff08;Abstract Syntax Tree&#xff09;也称为AST语法树&#xff0c;指的是源代码语法所对应的树状结构。也就是说&…

vue3+uniapp

1.页面滚动 2.图片懒加载 3.安全区域 4.返回顶部&#xff0c;刷新页面 5.grid布局 place-self: center; 6.模糊效果 7.缩放 8.微信小程序联系客服 9.拨打电话 10.穿透 11.盒子宽度 12.一般文字以及盒子阴影 13.选中文字 14.顶部安全距离 15.onLoad周期函数在setup语法糖执行后…