B树最大高度推导

article/2025/9/17 11:19:44

文章目录

  • B树最大高度推导
        • 推导B树的最小高度
        • 推导最大高度
  • B+树:MySQL数据库索引是如何实现的?
          • 1. 遇到问题
          • 2. 尝试用学过的数据结构解决这个问题
          • 3. 改造二叉查找树
          • 4. 索引的弊端

B树最大高度推导

【声明几个重要概念】

B树的关键字就是要查找的东西

B树中的 :可理解为分支数. 三阶树也可理解三叉树

按照定义:

m阶B tree,每个节点最多有 m 个subnode,除根节点和叶子节点外,每个节点至少有ceil(m / 2)个子节点

根节点:

  • 儿子节点个数**[2, m]**
  • 关键字个数**[1, m-1]**

非根节点 :

  • 儿子节点个数**[ceil(m/2), m]**
  • 关键字个数**[ceil(m/2)-1, m-1]**

【注意】:儿子节点就是树本身的节点,关键字是要查询的节点

前提条件:n>=1,则对于任意一棵包含n个关键字、高度为h、阶数为m的B树

其中 : ceil()函数 代表向上取整):

推导B树的最小高度

让树变的很矮,就需要让每一层节点最多,让他充实起来

节点个数层数
10
m1
m 2 m^2 m22
m 3 m^3 m33
m h − 1 m^{h-1} mh1h-1 (高度为h)

对于高度为h,阶数为m的B树的总的节点个数就是每一层相加之和:

1 + m + m 2 + m 3 . . . . + m h − 1 = ( m h − 1 ) / ( m − 1 ) 1+ m +m^2+m^3 ....+m^{h-1} = (m^{h-1})/(m-1) 1+m+m2+m3....+mh1=(mh1)/(m1)
$$
最大总关键字数 = 最大总节点数 \times 每个节点最大关键字数\

$$

s u m = ( m h − 1 ) / ( m − 1 ) ⋅ ( m − 1 ) = ( m h − 1 ) sum = (m^{h-1})/(m-1) \cdot (m-1) = (m^{h-1}) sum=(mh1)/(m1)(m1)=(mh1)

n < = s u m n < = ( m h − 1 ) 即 : l o g m n + 1 < = h n<= sum\\ n<= (m^{h-1})\\ 即: log_m{n+1} < = h n<=sumn<=(mh1)logmn+1<=h

所以对于关键字为n,高度为h,阶数为m的B数,最小高度为 l o g m n + 1 log_m{n+1} logmn+1

推导最大高度

最小高度易于理解,就是把每一层填满即可,那么反之,最大高度就是每个节点里面的关键字最少,让关键字分布的足够宽广,这样在容纳相同个数的关键字的B树中,其高度可以达到最大。

虽然这样类比,但最大高度我还是想了很久

最后一个很关键的定义点醒了我:

m阶B tree,每个节点最多有 m 个subnode,除根节点和叶子节点外,每个节点至少有ceil(m / 2)个子节点

我们量化定义

最小关键字:

  • 根节点 : 1
  • 非根节点:ceil(m/2)-1

最小关键字对应的最小子结点个数

  • 根节点 : 1 +1 =2 (很关键)
  • 非根节点:ceil(m/2)-1 +1 = ceil(m/2)(也就是定义强行背诵的)

我原来的困惑是,B树在求最大高度时,难道不能变成二叉树吗,这样就是最高了,而且好算,我没理清,m阶B树每一层最少也得ceil(m/2)个节点

以五阶B树为例画图:

根节点为2,每一层最多ceil(5/2) = 3
在这里插入图片描述

通过上图就很清晰的理解了下面的推导

节点个数层数
10
21
2 * ceil(m/2)2
2 * ceil(m/2)3
2 ∗ c e i l ( m / 2 ) h − 2 2 * ceil(m/2)^ {h-2} 2ceil(m/2)h2h-1 (高度为h)
2 ∗ c e i l ( m / 2 ) h − 1 2 * ceil(m/2)^ {h-1} 2ceil(m/2)h1叶子节点

在B树中查找失败的节点个数 为n+1,而查找失败的节点就是叶子节点,叶子节点为n+1

h层对应的节点个数就是叶子节点的个数
2 ∗ c e i l ( m / 2 ) h − 1 < = n + 1 得 : h < = l o g ( c e i l ( m / 2 ) ) ( n + 1 ) / 2 + 1 2 * ceil(m/2)^ {h-1} <= n+1 \\ 得:\\ h<= log_{(ceil(m/2))}{(n+1)/2+1} 2ceil(m/2)h1<=n+1h<=log(ceil(m/2))(n+1)/2+1

B+树:MySQL数据库索引是如何实现的?

1. 遇到问题

我们假设一个要解决的问题,其中包含这样两个常用的需求:

  • 根据某个值查找数据,比如 select * from user where id=1234;
  • 根据区间值来查找某些数据,比如 select * from user where id > 1234 and id < 2345。

对于非功能性需求,我们着重考虑性能方面的需求。性能方面的需求,我们主要考察时间和空间两方面,也就是执行效率和存储空间

在执行效率方面,我们希望通过索引,查询数据的效率尽可能的高;在存储空间方面,我们希望索引不要消耗太多的内存空间。

2. 尝试用学过的数据结构解决这个问题

有哪些算法可以帮助我们解决呢?

  • 散列表:时间复杂度为O(1),但是不能支持按照区间快速查询数据
  • 平衡二叉查找树:时间复杂度O(logn)。而且,对树进行中序遍历,我们还可以得到一个从小到大有序的数据序列,但这仍然不足以支持按照区间快速查找数据。
  • 跳表。跳表是在链表之上加上多层索引构成的,对应的时间复杂度是 O(logn)。并且,跳表也支持按照区间快速地查找数据
  • 跳表是可以解决这个问题。实际上,数据库索引所用到的数据结构跟跳表非常相似,叫作 B+ 树。不过,它是通过二叉查找树演化过来的,而非跳表
3. 改造二叉查找树

为了让二叉查找树支持按照区间来查找数据,我们可以对它进行这样的改造:树中的节点并不存储数据本身,而是只是作为索引。除此之外,我们把每个叶子节点串在一条链表上,链表中的数据是从小到大有序的。经过改造之后的二叉树,就像图中这样,看起来是不是很像跳表呢?

B+树的叶子节点上存放数据,其余节点不存放数据只作为索引

在这里插入图片描述

改造之后,如果我们要求某个区间的数据。我们只需要拿区间的起始值,在树中进行查找,当查找到某个叶子节点之后,我们再顺着链表往后遍历,直到链表中的结点数据值大于区间的终止值为止。所有遍历到的数据,就是符合区间值的所有数据。

在这里插入图片描述

1字节(Byte)= 8位(bit)

但是如果,我们给一亿个数据构建二叉查找树索引,那索引中会包含大约 1 亿个节点,每个节点假设占用 16 个字节,16亿字节

而1G = 2 30 2^{30} 230 = 10亿

大约是1.5G的内存空间,如果我们要给 10 张表建立索引,那对内存的需求是无法满足的。如何解决这个索引占用太多内存的问题呢?

经典思路:拿时间换空间

把索引存储在硬盘中,而非内存中。

这就带来了另一个问题:磁盘读取数据很慢,花费的时间是内存读取的上万倍,甚至几十万倍,查询效率太低了

但我们之前学习树的时候了解到树的高度就等于每次查询数据时磁盘 IO 操作的次数。

所有只要我们想办法降低树的高度就可以减少IO操作,进而提高查询速度

问题变为了:怎么降低树的高度

现在是二叉树,但如果这个叉多一点是不是就矮下来了,相同的数据,对比下面两棵树

在这里插入图片描述

如果我们将 m 叉树实现 B+ 树索引,用代码实现出来,就是下面这个样子(假设我们给 int 类型的数据库字段添加索引,所以代码中的 keywords 是 int 类型的):

/*** 这是 B+ 树非叶子节点的定义。** 假设 keywords=[3, 5, 8, 10]* 4 个键值将数据分为 5 个区间:(-INF,3), [3,5), [5,8), [8,10), [10,INF)* 5 个区间分别对应:children[0]...children[4]** m 值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:* PAGE_SIZE = (m-1)*4[keywordss 大小]+m*8[children 大小]*/
public class BPlusTreeNode {public static int m = 5; // 5 叉树public int[] keywords = new int[m-1]; // 键值,用来划分数据区间public BPlusTreeNode[] children = new BPlusTreeNode[m];// 保存子节点指针
}/*** 这是 B+ 树中叶子节点的定义。** B+ 树中的叶子节点跟内部结点是不一样的,* 叶子节点存储的是值,而非区间。* 这个定义里,每个叶子节点存储 3 个数据行的键值及地址信息。** k 值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:* PAGE_SIZE = k*4[keyw.. 大小]+k*8[dataAd.. 大小]+8[prev 大小]+8[next 大小]*/
public class BPlusTreeLeafNode {public static int k = 3;public int[] keywords = new int[k]; // 数据的键值public long[] dataAddress = new long[k]; // 数据地址public BPlusTreeLeafNode prev; // 这个结点在链表中的前驱结点public BPlusTreeLeafNode next; // 这个结点在链表中的后继结点
}

按照之前分析的思路:

对于一亿 的数据,需要一亿个叶子节点,对二叉树 大概是27层高,需要操作27次

但是如果 m 叉树中的 m 是 100,那对一亿个数据构建索引,树的高度也只是 3,(根节点在内存)最多只要 3 次磁盘 IO 就能获取到数据。

是不是m越大越好呢?

不管是内存中的数据,还是磁盘中的数据,操作系统都是按页(一页大小通常是 4KB,这个值可以通过 getconfig PAGE_SIZE 命令查看)来读取的,一次会读一页的数据。**如果要读取的数据量超过一页的大小,就会触发多次 IO 操作。**所以,我们在选择 m 大小的时候,要尽量让每个节点的大小等于一个页的大小。读取一个节点,只需要一次磁盘 IO 操作

//m 值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:PAGE_SIZE = (m-1)*4[keywordss 大小]+m*8[children 大小]
4. 索引的弊端

索引有利也有弊,它也会让写入数据的效率下降。这是为什么呢?

数据的写入过程,会涉及索引的更新,这是索引导致写入变慢的主要原因。

对于一个 B+ 树来说,m 值是根据页的大小事先计算好的,也就是说,每个节点最多只能有 m 个子节点。在往数据库中写入数据的过程中,这样就有可能使索引中某些节点的子节点个数超过 m,这个节点的大小超过了一个页的大小,读取这样一个节点,就会导致多次磁盘 IO 操作。我们该如何解决这个问题呢?

实际上,处理思路并不复杂。我们只需要将这个节点分裂成两个节点。但是,节点分裂之后,其上层父节点的子节点个数就有可能超过 m 个。不过这也没关系,我们可以用同样的方法,将父节点也分裂成两个节点。这种级联反应会从下往上,一直影响到根节点。这个分裂过程,你可以结合着下面这个图一块看,会更容易理解(图中的 B+ 树是一个三叉树。我们限定叶子节点中,数据的个数超过 2 个就分裂节点;非叶子节点中,子节点的个数超过 3 个就分裂节点)。

增加会导致节点数变多超过承载,需要“分裂”

同理:删除 会导致 节点数变少,远没有到一页的承载数,需要“合并”

正是因为要时刻保证 B+ 树索引是一个 m 叉树,所以,索引的存在会导致数据库写入的速度降低。实际上,不光写入数据会变慢,删除数据也会变慢。这是为什么呢?

我们在删除某个数据的时候,也要对应的更新索引节点。这个处理思路有点类似跳表中删除数据的处理思路。频繁的数据删除,就会导致某些结点中,子节点的个数变得非常少,长此以往,如果每个节点的子节点都比较少,势必会影响索引的效率。

**我们可以设置一个阈值。在 B+ 树中,这个阈值等于 m/2。**如果某个节点的子节点个数小于 m/2,我们就将它跟相邻的兄弟节点合并。不过,合并之后结点的子节点个数有可能会超过 m。针对这种情况,我们可以借助插入数据时候的处理方法,再分裂节点。

在这里插入图片描述

总结一下 B+ 树的特点:

  • 每个节点中子节点的个数不能超过 m,也不能小于 m/2;
  • 根节点的子节点个数可以不超过 m/2,这是一个例外;
  • m 叉树只存储索引,并不真正存储数据,这个有点儿类似跳表;
  • 通过链表将叶子节点串联在一起,这样可以方便按区间查找;
  • 一般情况,根节点会被存储在内存中,其他节点存储在磁盘中。

除了 B+ 树,你可能还听说过 B 树、B- 树,我这里简单提一下。实际上,B- 树就是 B 树,英文翻译都是 B-Tree,这里的“-”并不是相对 B+ 树中的“+”,而只是一个连接符。这个很容易误解,所以我强调下。

而 B 树实际上是低级版的 B+ 树,或者说 B+ 树是 B 树的改进版。B 树跟 B+ 树的不同点主要集中在这几个地方:

  • B+ 树中的节点不存储数据,只是索引,而 B 树中的节点存储数据;
  • B 树中的叶子节点并不需要链表来串联。

也就是说,B 树只是一个每个节点的子节点个数不能小于 m/2 的 m 叉树。

参考:

乐行僧丶: 推导B树的最大高度和最小高度得出B树的高度范围

极客时间: B+树:MySQL数据库索引是如何实现的?


http://chatgpt.dhexx.cn/article/jtYBnIQd.shtml

相关文章

红黑树详解

1&#xff0c;红黑树特点 每一个结点都有颜色&#xff0c;要么红色&#xff0c;要么黑色。根结点必须是黑色的。红色结点的子结点必须是黑色的。任何一个结点&#xff0c;到它所有叶子结点&#xff0c;经过相同个数的黑色结点。&#xff08;红黑树的平衡含义&#xff0c;左右高…

MYSQL的B+Tree索引树高度如何计算

我们使用MySQL数据库的时候&#xff0c;绝大部分的情况下在使用InnoDB存储引擎&#xff0c;偶尔会使用MyISAM存储引擎&#xff0c;至于其他存储引擎&#xff0c;我相信大家都很少接触到&#xff0c;甚至可能都没有听说过。所以本文只讲解InnoDB和MyISAM两个存储引擎的索引&…

红黑树高度上限的证明(通俗易懂)

先把结论放上&#xff0c;设红黑树的高度为h&#xff0c;总结点数为n&#xff0c;那么h与n的关系就是 下面开始证明过程 首先&#xff0c;从任意节点出发&#xff0c;到其子树的叶子节点的路径中黑色节点的数量称为该节点的黑高&#xff0c;即 bh 我们设根节点为T,那么根节点…

数据结构与算法(一): 树的高度和深度的区别

1.高度 对于高度的理解&#xff0c;我们不管他数据结构什么什么知识&#xff0c;就拿楼房来说&#xff0c;假如一个人提问&#xff1a;楼房的高度有好高&#xff1f;我们会下意识的从底层开始往上数&#xff0c;假如楼有6层&#xff0c;则我们会说&#xff0c;这个楼有6层楼那…

经典PID算法

首先感谢黄工&#xff0c;公众号strongerHuang&#xff0c;以下为三篇文章整合而成。 文章链接&#xff1a; https://mp.weixin.qq.com/s/6Ew431m4nXhScpNVp8mGxQ https://mp.weixin.qq.com/s/JYWnu67HEx2uMrntcUhggQ https://mp.weixin.qq.com/s/IrTehHvTofXWP_BapoN1NQ 在工…

PID控制原理

PID控制原理 在模拟控制系统中,控制器最常用的控制规律是PID控制。 PID控制器是一种线性控制器,它根据给定值与实际输出值构成控制偏差。 PID控制规律为 数学表达形式为: 进行拉普拉斯变换,写出传递函数的形式: kp为比例系数,T1为积分时间常数,Td为微分时间常数。…

SMART PLC PID算法基本解析(附公式)

在稳态运行中,控制器调节输出值,使偏差 (e) 为零。偏差是设定值(目标值)与过程变量(实际值、反馈值)之差。输出 = 比例项 + 积分项 + 微分项 离散化的PID公式基本框架几乎一样,不同的厂家描述符号,变量名称定义可能不太一样, 从公式中可以看出,积分项是从第一次采样到…

PID算法-理论分析

连续PID算法 典型PID算法框图 r(t)&#xff1a;设定状态量y(t)&#xff1a;实际状态量e(t)&#xff1a;当前误差u(t)&#xff1a;控制 器输出 P-比例环节 u p ( t ) K p ∗ e ( t ) K p [ r ( t ) − y ( t ) ] u_{p}(t)Kp*e(t)Kp[r(t)-y(t)] up​(t)Kp∗e(t)Kp[r(t)−y(t)…

PID详解

PID在控制领域应该是应用最为广泛的算法了&#xff0c;在工业控制&#xff0c;汽车电子等诸多领域中运用 下面我用一个例子和算法过程来讲解PID的概念 PID&#xff1a; P比例控制&#xff1a;基本作用就是控制对象以线性的方式增加&#xff0c;在一个常量比例下&#xff0c;动态…

模糊PID算法

在讲解模糊PID前&#xff0c;我们先要了解PID控制器的原理&#xff08;本文主要介绍模糊PID的运用,对PID控制器的原理不做详细介绍&#xff09;。PID控制器&#xff08;比例-积分-微分控制器&#xff09;是一个在工业控制应用中常见的反馈回路部件&#xff0c;由比例单元P、积分…

PID控制器整理分享

概述 日常开发中&#xff0c;常常需要对速度、温度等物理量进行稳态控制&#xff0c;而在目前的自动化控制原理中&#xff0c;使用最为广泛的方法就是PID控制算法。本文简要整理分享PID控制器的使用。 正文 PID控制器&#xff0c;即比例-积分-微分控制器。它是一个不依赖系统…

PID算法详解

文章目录 什么是pid比例&#xff08;p&#xff09;控制积分&#xff08;I&#xff09;控制微分&#xff08;D&#xff09;控制PID使用增量式PIDC语言实现pid算法 什么是pid PID算法是一种具有预见性的控制算法&#xff0c;其核心思想是&#xff1a; 1>. PID算法不但考虑控制…

《PID》一篇文章带你搞懂使用PID

节选自本人博客&#xff1a;https://www.blog.zeeland.cn/archives/pid-learning 本文为笔者参考了网上众多大神的解析之后加上自己的理解整合起来的&#xff0c;因此在内容上部分参考了其他作者&#xff0c;目的仅用作参考以便更好地学习&#xff0c;如有侵犯&#xff0c;可联…

PID几种公式总结

模拟式PID 其中&#xff0c;t为采样时间 位置式PID 其中&#xff0c;为采样间隔 增量式PID 增量式PID和位置式PID都是数字式PID&#xff08;模拟式PID的离散化&#xff09;的不同表达形式&#xff0c;因为计算机只能处理离散数据&#xff0c;将连续信号变为离散信号&#xff…

PID控制及公式讲解

1、PID引入 2、PID代码 /*******************************************************************位置式pid********************************************************************/ double PID(double Actual,double SET){ static double E_sum,Error_last; //上一…

一文读懂PID控制算法(抛弃公式,从原理上真正理解PID控制)

一文读懂PID控制算法&#xff08;抛弃公式&#xff0c;从原理上真正理解PID控制&#xff09; PID控制应该算是应用非常广泛的控制算法了。小到控制一个元件的温度&#xff0c;大到控制无人机的飞行姿态和飞行速度等等&#xff0c;都可以使用PID控制。这里我们从原理上来理解PI…

PID公式的推导过程及实现代码

一、PID框图&#xff1a; n0(t)是要稳定的值 n(t)是当前输出值 e(t) n0(t) - n(t) 一、模拟PID控制原理 这个公式网络上很好找&#xff1a; 二、数字PID控制 由于模拟的微积分运算对应计算机来说是不太好写代码的&#xff0c;所以要利用采样将数据离散化 于是公式就可以转换…

经典的pid公式,好脑子不如烂笔头。

这个算法涉及昨天&#xff0c;今天&#xff0c;明天。 思路就是以史为鉴&#xff0c;预测明天&#xff0c;改革当前。

PID公式通俗理解

PID调节是有方法、有规律可循的&#xff0c;不过在此之前先深入理解其公式。 别怕&#xff0c;先看认真看PID本体&#xff1a; 其中&#xff1a; u(t) -------------输出曲线&#xff0c;pid输出值随时间的变化曲线 Kp --------------比例系数 e(t)------------- 偏差曲线&…