数据结构之B树与B+树

计算机的发展速度很快,CPU、内存、显卡等已不再是计算机性能的瓶颈,SSD硬盘的出现也使得硬盘读写速度有了质的飞跃,但和内存相比依然有极大的差距,这就意味着我们在内存环境下设计的算法,在涉及到硬盘读写时效率会极大地降低。比如红黑树、AVL树等,因为其每个结点只能存储一个数据,且每个结点最多有两个子结点,这意味着当数据很多时,树的高度会非常大,也就意味着要频繁地进行IO操作。即使是普通的树,每个结点可以有多个孩子,那它要么度非常大,要么高度特别大,也可能两者都特别大,也无法摆脱频繁IO操作带来的性能瓶颈。今天要研究的B树和B+树就是这种频繁IO操作场景的解决办法。

B树

定义

我们首先要知道多路查找树的概念,它的定义如下:

多路查找树(multi-way search tree),其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。

和普通的树相比,多路查找树一个节点不再是只能存储一个元素,这打破了我们对树的理解,但是正是这个特性,使得它能够出色地解决IO问题。我们要研究的B树就是一棵多路查找树,它的定义如下:

B树是一种平衡的多路查找树,结点最大的孩子数目称为B树的阶(Order)。

一个m阶的B树具有如下属性:

  • 如果根结点不是叶结点,则其至少有两棵子树。
  • 每一个非根的分支结点都有k-1个元素和k个孩子,其中[m/2]≤ k ≤ m。每一个叶子结点 n 都有k-1个元素,其中[m/2]≤ k ≤ m。
  • 所有叶子结点都位于同一层次。
  • 所有分支结点包含下列信息数据( n, A0, K1, A1, K2, A2, …, Kn, An ),其中: Ki( i=1, 2, …, n ) 为关键字,且Ki<Ki+1( i=1, 2, …, n-1 ); Ai ( i=0, 2, …, n) 为指向子树根结点的指针,且指针Ai-1所指子树中所有结点的关键字均小于Ki( i=1, 2, …, n ) ,An 所指子树中所有结点的关键字均大于Kn,n ( [m/2]-1 ≤ n ≤ m-1 ) 为关键字的个数(或 n+1 为子树的个数)。

这段定义一定让人感到费解吧,那我们就从B树的一个特例:2-3树作为切入点,来看看一个B树是如何构建和操作的。

2-3树是这样的一棵多路查找树:其中的每一个结点都具有两个孩子(称为2结点)或三个孩子(称为3结点)。

它拥有如下属性:

  • 一个2结点包含一个元素和两个孩子(或没有孩子),和二叉排序树一致,左子树包含的元素小于该元素,右子树包含的元素大于该元素。但是这个2结点要么有两个孩子,要么没有孩子,不能只有一个孩子。
  • 一个3结点包含两个元素和三个孩子(或没有孩子),左子树、较小元素、中间子树、较大元素和右子树也按照从小到大排序。一个3结点要么有三个孩子,要么没有孩子。
  • 2-3树的所有叶子结点都在同一层次上。

按照这个描述,一棵正确的2-3树大概长这个样子:

2-3树

插入

下面我们通过构造一棵2-3树来演示它的增删过程,假定初始数据为:{1, 7, 4, 9, 15, 13, 6, 5, 8, 10, 3, 12, 14, 2, 11}。现在树为空,要把1插入进去只需要构建一个2结点即可,如下所示:

插入1

接下来插入元素7,只要把当前结点升级为3结点即可,如下所示:

插入7

接下来插入4,可以发现根结点已经是3结点了,因为必须是平衡的,所以只能把根结点拆开,变为3个2结点,如下所示:

插入4

插入9时,因为9比4大,所以插入到右侧,而7所在结点可以升级为3结点,所以插入结果如下所示:

插入9

接下来要插入15,因为9所在结点已经是3结点,但是它的父结点4是2结点,所以可以把4所在结点升级,因为3结点必须有三个孩子,所以7和9所在结点需要拆分,如下所示:

插入15

接下来插入13和6时,对应节点都可以升级,所以插入结果如下:

插入13和6

接下来插入元素5时,发现6所在结点已经是3结点,而父结点,也就是根结点也是3结点了,这时只能再次拆分。首先,5、6、7中间的数是6,我们把它提出来,它应该位于4和9中间,如下所示:

插入5步骤1

因为3结点只能有两个元素,所以根结点也必须拆分,结果如下:

插入5步骤2

可以发现,根结点拆分后使得树的高度增加了。接下来插入8,10,3也是重复步骤,结果如下:

插入

至此,再插入元素12、14、2时也变得十分简单了,结果如下:

插入

最后插入11,可以发现它在10和12之间,而父结点也是3结点,所以10和12要拆分,9和13也要拆分,11应该和6一起升级为3结点,结果如下:

插入11

删除

现在,我们已经建立了一棵2-3树,我们按照插入顺序,再演示删除的过程。首先删除元素1,因为1是2结点,删除后会影响平衡,但是我们发现它的父结点是一个3结点,所以可以把父结点拆开,2和3合并成一个3结点,结果如下:

删除1

现在,要删除7,因为7是叶节点也是3结点,直接删除就可以,结果如下:

删除7

删除结点4,因为它的左孩子是3结点,只要把它拆开就可以了,结果如下:

删除4

删除9时比较复杂,因为它的左右孩子都是2结点,首先把它的两个孩子合并为3结点并代替它,结果如下:

删除9步骤1

此时树是不平衡的,此时发现左侧3和6可以合并为3结点,结果如下:

删除9步骤2

接下来删除15,直接删除即可,结果如下:

删除15

删除13也比较复杂,首先需要把它的两个孩子合并,然后以11为根结点,做类似右旋的操作,具体做法是6的右孩子成为11的左孩子,然后6成为11的父结点,这和AVL树等的右旋操作是一致的,结果如下:

删除13

接下来要删除的元素6是根结点,做法是先找到它的前驱(第一个比它小的元素)5代替它,此时2、3结点需要合并,合并后左右子树不再平衡,所以还需要5和11合并,结果如下:

删除6

其余的删除操作其实和前面的都类似,这里不再演示了,感兴趣的可以自己试一试,很快就可以发现规律。

总结

这里介绍的2-3树是B树的一个特例,B树就是把2-3树的阶扩展到了m,它的每个结点特性和2-3树一致,除叶结点外每个结点的指针域和数据域都必须填充。那么B树是如何解决IO访问问题的呢?假设我们有一棵阶为1001的B树,也就是每个结点可以存储1000个数据和1001个指针,那么在高度为2的层上,可以存储的数据是1001X1000个,而它的指针数量为1001X1001个,这些指针可以指向的数据为1001X1001X1000个,大概有10亿条数据。这意味着,只要我们把根结点保存在内存中,访问这10亿条数据最多需要两次IO操作,这是其他结构无法比拟的。

那么B树的问题在哪里呢?在实际使用时,通常阶数是和磁盘页面大小匹配的,也就是每次都会读取一页的数据,因为磁盘在页面内连续读取速度非常快,但在页间就相对慢些。这是它的优点,也恰恰是它的问题所在。假设每个结点都在不同的页面,我们要对它进行中序遍历,其经过大概如下:

遍历

其中序遍历为页面2->页面1->页面3->页面1->页面4。可以发现位于页面1的结点会被多次访问,且位于该结点的元素也会被多次遍历,这样一来效率会变得很低,所以B树对遍历是不友好的。接下来介绍的B+树就是对此问题的优化。

B+

遍历的需求主要来源于“扫库”,比如网站大量充斥着各种列表,如果使用B树遍历,效率实在太低了。B+树在B树的基础上做了改进,在B+树中,出现在分支结点中的元素会被当作它们在该分支结点位置的中序后继者(叶子结点)中再次列出,且每一个叶子结点都会保存一个指向后一叶子结点的指针。如下就是一棵B+树:

B+树示例

为了简化,叶子结点的左右两侧指针域省略。它的特点就是任何非叶子结点都会在叶结点上再次出现一次,并且所有叶子结点从左到右链接了起来。总体来说,它也具备B树的特性,只是在两个方面有所区别。第一就是查找元素时,即使在非叶子结点找到了目标值,它也只是用来索引的,还需要继续找到它在叶子结点的位置。第二就是如果要遍历,只需要遍历一次叶子结点即可。B+树的结构也十分适合范围查找,只需要找到范围的最小值所在位置,然后沿链表遍历即可。

B树与B+树对比

B树与B+树都是对磁盘友好的数据结构,能大幅降低磁盘访问次数。B树的优点在于数据存储在每个结点中,可以更快访问到,而不必须走到叶子结点,B树更多的用在文件系统中。B+树的每个非叶子结点都只充当索引,所以查询必须到叶子结点结束,但它十分适合“扫库”和区间查找,而且因为大多结点只用于索引,所以并不会存储真正的数据,在内存上会更紧凑,相同的内存就可以存放更多的索引数据了。比如字典的拼音和汉字是分离的,只需要几十页就能得到完整的拼音表,但是如果拼音和汉字掺杂在一起,要得到完整的索引(拼音)表就需要整个字典。B+树的这些特性使得它更适合用来做数据库的索引。


我是飞机酱,如果您喜欢我的文章,可以关注我~

编程之路,道阻且长。唯,路漫漫其修远兮,吾将上下而求索。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,221评论 0 25
  • 原文链接 B树 1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(...
    非典型程序员阅读 1,159评论 0 3
  • B树 1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(Balan...
    铁甲依然在_978f阅读 1,447评论 0 4
  • 一. 算法之变,结构为宗 计算机在很多情况下被应用于检索数据,比如航空和铁路运输业的航班信息和列车时刻表的查询,都...
    Leesper阅读 6,905评论 13 42
  • 好久不见 你别来无恙 回忆的枝叶里 你花雨飘香 好久不见 你别来无恙 推开久违的门窗 你走入我的殿堂 好久不见 你...
    张怀智阅读 224评论 0 1