从B 树、B+ 树、B* 树谈到R 树

from: http://blog.youkuaiyun.com/v_JULY_v/archive/2011/06/07/6530142.aspx

B 树、 B+ 树、B* 树谈到 R

作者: July 、weedge、 Frankie 。编程艺术室出品。

说明:本文从 B 树开始谈起,然后论述 B+树、B* 树,最后谈到 R 树。其中 B 树、 B+ 树及B*树部分由weedge完成, R 树部分由 Frabkie 完成,全文最终由 July 统稿完成。

出处:http://blog.youkuaiyun.com/v_JULY_v

第一节、 B 树、 B+ 树、B*树

1. 前言:

动态查找树主要有:二叉查找树( Binary Search Tree ),平衡二叉查找树( Balanced Binary Search Tree ),红黑树 (Red-Black Tree ) B-tree/B+ -tree/ B* -tree (B~Tree) 。前三者是典型的二叉查找树结构,其查找的时间复杂度 O (log2N ) 与树的深度相关,那么降低树的深度自然会提高查找效率。

但是咱们有面对这样一个实际问题:就是大规模数据存储中,实现索引查询这样一个实际背景下,树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),这样导致二叉查找树结构由于树的深度过大而造成磁盘 I/O 读写过于频繁,进而导致查询效率低下(为什么会出现这种情况,待会在外部存储器 - 磁盘中有所解释),那么如何减少树的深度(当然是不能减少查询的数据量),一个基本的想法就是:采用多叉树结构(由于树节点元素数量是有限的,自然该节点的子树数量也就是有限的)。

这样我们就提出了一个新的查找树结构——多路查找树。根据平衡二叉树的启发,自然就想到平衡多路查找树结构,也就是这篇文章所要阐述的第一个主题 B~tree(B 树结构 )。

B-tree(B-tree树即B树 这棵神奇的树是在 Rudolf Bayer ,Edward M. McCreight (1970) 写的一篇论文《 Organization and Maintenance of Large Ordered Indices 》中首次提出的( wikipedia 中: http://en.wikipedia.org/wiki/B-tree ,阐述了 B-tree 名字来源以及相关的开源地址)。

在开始介绍 B~tree 之前,先了解下相关的硬件知识,才能很好的了解为什么需要 B~tree 这种外存数据结构。

2. 外存储器 磁盘

计算机存储设备一般分为两种 : 内存储器 (main memory) 和外存储器 (external memory) 内存存取速度快,但容量小,价格昂贵,而且不能长期保存数据 ( 在不通电情况下数据会消失 )

外存储器—磁盘是一种直接存取的存储设备 (DASD) 。它是以存取时间变化不大为特征的。可以直接存取任何字符组,且容量大、速度较其它外存设备更快。

2.1 磁盘的构造

磁盘时一个扁平的圆盘 ( 与电唱机的唱片类似 ) 。盘面上有许多称为磁道的圆圈,数据就记录在这些磁道上。磁盘可以是单片的,也可以是由若干盘片组成的盘组,每一盘片上有两个面。如下图11.3中所示的 6 片盘组为例,除去最顶端和最底端的外侧面不存储数据之外,一共有 10 个面可以用来保存信息。

当磁盘驱动器执行读 / 写功能时。盘片装在一个主轴上,并绕主轴高速旋转,当磁道在读 / 写头 ( 又叫磁头 ) 下通过时,就可以进行数据的读 / 写了。

一般磁盘分为固定头盘 ( 磁头固定 ) 和活动头盘。固定头盘的每一个磁道上都有独立的磁头,它是固定不动的,专门负责这一磁道上数据的读 / 写。

活动头盘 ( 如上图 ) 的磁头是可移动的。每一个盘面上只有一个磁头 ( 磁头是双向的,因此正反盘面都能读写 ) 。它可以从该面的一个磁道移动到另一个磁道。所有磁头都装在同一个动臂上,因此不同盘面上的所有磁头都是同时移动的 ( 行动整齐划一 ) 。当盘片绕主轴旋转的时候,磁头与旋转的盘片形成一个圆柱体。各个盘面上半径相同的磁道组成了一个圆柱面,我们称为柱面 。因此,柱面的个数也就是盘面上的磁道数。

2.2 磁盘的读 / 写原理和效率

磁盘上数据必须用一个三维地址唯一标示:柱面号、盘面号、块号 ( 磁道上的盘块 )

/ 写磁盘上某一指定数据需要下面 3 个步骤:

(1) 首先移动臂根据柱面号使磁头移动到所需要的柱面上,这一过程被称为定位或查找

(2) 如上图11.3中所示的 6 盘组示意图中,所有磁头都定位到了 10 个盘面的 10 条磁道上 ( 磁头都是双向的 ) 。这时根据盘面号来确定指定盘面上的磁道。

(3) 盘面确定以后,盘片开始旋转,将指定块号的磁道段移动至磁头下。

经过上面三个步骤,指定数据的存储位置就被找到。这时就可以开始读 / 写操作了。

访问某一具体信息,由 3 部分时间组成:

查找时间 (seek time) Ts: 完成上述步骤 (1) 所需要的时间。这部分时间代价最高,最大可达到 0.1s 左右。

等待时间 (latency time) Tl: 完成上述步骤 (3) 所需要的时间。由于盘片绕主轴旋转速度很快,一般为 7200/( 电脑硬盘的性能指标之一 , 家用的普通硬盘的转速一般有 5400rpm( 笔记本 )7200rpm 几种 ) 。 因此一般旋转一圈大约 0.0083s

传输时间 (transmission time) Tt: 数据通过系统总线传送到内存的时间,一般传输一个字节 (byte) 大概 0.02us=2*10^(-8)s

磁盘读取数据是以盘块 (block) 为基本单位的。 位于同一盘块中的所有数据都能被一次性全部读取出来。而磁盘 IO 代价主要花费在查找时间 Ts 上。因此我们应该尽量将相关信息存放在同一盘块,同一磁道中。或者至少放在同一柱面或相邻柱面上,以求在读 / 写信息时尽量减少磁头来回移动的次数,避免过多的查找时间 Ts

所以,在大规模数据存储方面,大量数据存储在外存磁盘中,而在外存磁盘中读取 / 写入块 (block) 中某数据时,首先需要定位到磁盘中的某块,如何有效地查找磁盘中的数据,需要一种合理高效的外存数据结构,就是下面所要重点阐述的 B-tree 结构,以及相关的变种结构: B+ -tree 结构和 B* -tree 结构。

3. B- 树

具体讲解之前,有一点,再次强调下:B-树,即为B树。因为B树的原英文名称为B-tree,而国内很多人喜欢把B-tree译作B-树,其实,这是个非常不好的直译,很容易让人产生误解。如人们可能会以为B-树是一种树,而B树又是一种一种树。而事实上是,B-tree就是指的B树 。特此说明。

我 们知道,B 树是为了磁盘或其它存储设备而设计的一种多叉(下面你会看到,相对于二叉,B树每个内结点有多个分支,即多叉)平衡查找树。与本blog之前介绍的红黑树 很相似,但在降低磁盘I/0操作方面要更好一些。许多数据库系统都一般使用B树或者B树的各种变形结构,如下文即将要介绍的B+树,B*树来存储信息。

B 树与红黑树最大的不同在于,B树的结点可以有许多子女,从几个到几千个。那为什么又说B树与红黑树很相似呢?因为与红黑树一样,一棵含n个结点的B树的高 度也为O(lgn),但可能比一棵红黑树的高度小许多,应为它的分支因子比较大。所以,B树可以在O(logn)时间内,实现各种如插入 (insert),删除(delete)等动态集合操作。

如 下图所示,即是一棵B树,一棵关键字为英语中辅音字母的B树,现在要从树种查找字母R(包含n[x]个关键字的内结点x,x有n[x]+1]个子女(也就 是说,一个内结点x若含有n[x]个关键字,那么x将含有n[x]+1个子女)。所有的叶结点都处于相同的深度,带阴影的结点为查找字母R时要检查的结 点):

相信,从上图你能轻易的看到,一个内结点x若含有n[x]个关键字,那么x将含有n[x]+1个子女。如含有2个关键字D H 的内结点有3个子女,而含有3个关键字Q T X 有4个子女。

B 树又叫平衡多路查找树。一棵 m 阶的 B (m 叉树 ) 的特性如下:

  1. 树中每个结点最多含有m个孩子(m>=2);
  2. 除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子(其中ceil(x)是一个取上限的函数 );
  3. 若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);
  4. 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,实际上这些结点不存在,指向这些结点的指针都为null);
  5. 每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
    a) Ki (i=1...n)为关键字,且关键字按顺序升序排序K(i-1)< Ki。
    b) Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。
    c) 关键字的个数n必须满足: [ceil(m / 2)-1]<= n <= m-1。

针对上面第5点,再阐述下:B树中每一个结点能包含的关键字(如之前上面的D HQ T X )数有一个上界和下界。这两个界可以用一个称作B树的最小度数t(t>=2)表示。

  • 每个非根的结点必须至少含有m-1个关键字。每个非根的内结点至少有m个子女。如果树是非空的,则根结点至少包含一个关键字;
  • 每个结点可包含之多2m-1个关键字。所以一个内结点之多可有2m个子女。如果一个结点恰好有2t-1个关键字,我们就说这个结点是满的(而稍后介绍的B*树作为B树的一种常用变形,B*树中要求每个内结点至少为2/3满,而不是像这里的B树所要求的至少半满 );
  • 当关键字数m=2(m=2的意思是,m min =2,m可以>=2)时的B树是最简单的 有很多人会因此误认为B树就是二叉查找树,但二叉查找树就是二叉查找树,B树就是B树,B树的真正最准确的定义为:一棵含有m(m>=2)个关键字的平衡多路查找树 。每个内结点可能因此而含有2个、3个或4个子女,亦即一棵2-3-4树,然而在实际中,通常采用大得多的t值。

B 树中的每个结点根据实际情况可以包含大量的关键字信息和分支(当然是不能超过磁盘块的大小,根据磁盘驱动(disk drives)的不同,一般块的大小在1k~4k左右);这样树的深度降低了,这就意味着查找一个元素只要很少结点从外存磁盘中读入内存,很快访问到要查 找的数据。

为了简单,这里用少量数据构造一棵 3 叉树的形式,实际应用中的 B 树结点中关键字很多的。上面的图中比如根结点,其中 17 表示一个磁盘文件的文件名;小红方块表示这个 17 文件的内容在硬盘中的存储位置; p1 表示指向 17 左子树的指针。

其结构可以简单定义为:

typedef struct {

/* 文件数*/

int file_num;

/* 文件名(key)*/

char * file_name[max_file_num];

/* 指向子节点的指针*/

BTNode * BTptr[max_file_num+1];

/* 文件在硬盘中的存储位置*/

FILE_HARD_ADDR offset[max_file_num];

}BTNode;

假如每个盘块可以正好存放一个 B 树的结点(正好存放 2 个文件名)。那么一个 BTNode 结点就代表一个盘块,而子树指针就是存放另外一个盘块 的地址。

下面,咱们来模拟下查找文件 29 的过程:

(1) 根据根结点指针找到文件目录的根磁盘块 1 ,将其中的信息导入内存。【磁盘 IO 操作 1 次】

(2) 此时内存中有两个文件名 1735 和三个存储其他磁盘页面地址的数据。根据算法我们发现 17<29<35 ,因此我们找到指针 p2

(3) 根据 p2 指针,我们定位到磁盘块 3 ,并将其中的信息导入内存。【磁盘 IO 操作 2 次】

(4) 此时内存中有两个文件名 2630 和三个存储其他磁盘页面地址的数据。根据算法我们发现 26<29<30 ,因此我们找到指针 p2

(5) 根据 p2 指针,我们定位到磁盘块 8 ,并将其中的信息导入内存。【磁盘 IO 操作 3 次】

(6) 此时内存中有两个文件名 2829 。根据算法我们查找到文件 29 ,并定位了该文件内存的磁盘地址。

分析上面的过程,发现需要 3 次磁盘 IO 操作和 3 次内存查找操作。关于内存中的文件名查找,由于是一个有序表结构,可以利用折半查找提高效率。至于 3 次磁盘 IO 操作时影响整个 B 树查找效率的决定因素。

当然,如果我们使用平衡二叉树的磁盘存储结构来进行查找,磁盘 IO 操作最少 4 次,最多 5 次。而且文件越多, B 树比平衡二叉树所用的磁盘 IO 操作次数将越少,效率也越高

4. B+ -tree

B+ -tree :是应文件系统所需而产生的一种 B-tree 的变形树。

一棵 m 阶的 B+ 树和 m 阶的 B 树的差异在于:

1.n 棵子树的结点中含有 n 个关键字; (而B 树n 棵子树有 n+1 个关键字 )

2. 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树 的叶子节点并没有包括全部需要查找的信息 )

3. 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树 的非终节点也包含需要查找的有效信息 )

a) 为什么说 B+ -tree B 树 更适合实际应用中操作系统的文件索引和数据库索引?

1) B+ -tree 的磁盘读写代价更低

B+ -tree 的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树 更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说 IO 读写次数也就降低了。

举个例子,假设磁盘中的一个盘块容纳 16bytes ,而一个关键字 2bytes ,一个关键字具体信息指针 2bytes 。一棵 9 B-tree ( 一个结点最多 8 个关键字 ) 的内部结点需要 2 个盘快。而 B+ 树内部结点只需要 1 个盘快。当需要把内部结点读入内存中的时候, B 树 就比 B+ 树多一次盘块查找时间 ( 在磁盘中就是盘片旋转的时间 )

2) B+ -tree 的查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

b) B+ -tree 的应用: VSAM( 虚拟存储存取法 ) 文件 ( 来源论文 the ubiquitous Btree 作者: D COMER - 1979 )

5. B* -tree

B*-treeB+ -tree 的变体,在 B+ 树非根和非叶子结点再增加指向兄弟的指针; B* 树定义了非叶子结点关键字个数至少为 (2/3)*M ,即块的最低使用率为 2/3 (代替 B+ 树的 1/2 )。给出了一个简单实例,如下图所示:

B+ 树的分裂:当一个结点满时,分配一个新的结点,并将原结点中 1/2 的数据复制到新结点,最后在父结点中增加新结点的指针; B+ 树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。

B* 树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制 1/3 的数据到新结点,最后在父结点增加新结点的指针。

所以, B* 树分配新结点的概率比 B+ 树要低,空间使用率更高;

6. 总结

在大规模数据存储的文件系统中, B~tree 系列数据结构,起着很重要的作用,对于存储不同的数据,节点相关的信息也是有所不同,这里根据自己的理解,画的一个查找以职工号为关键字,职工号为 38 的记录的简单示意图。 ( 这里假设每个物理块容纳 3 个索引,磁盘的 I/O 操作的基本单位是块( block), 磁盘访问很费时,采用 B+ 树有效的减少了访问磁盘的次数。)

对于像 MySQLDB2Oracle 等数据库中的索引结构得有较深入的了解才行,建议去找一些B 树 相关的开源代码研究。

走进搜索引擎的作者梁斌老师 针对B 树、B+ 树给出了他的意见(为了真实性,特引用其原话,未作任何改动): B+ 树还有一个最大的好处,方便扫库,B 树必须用中序遍历的方法按序扫库,而B+ 树直接从叶子结点挨个扫一遍就完了,B+ 树支持range-query 非常方便,而B 树不支持。这是数据库选用B+ 树的最主要原因。

比如要查 5-10 之间的,B+ 树一把到5 这个标记,再一把到10 ,然后串起来就行了,B 树就非常麻烦。B 树的好处,就是成功查询特别有利,因为树的高度总体要比B+ 树矮。不成功的情况下,B 树也比B+ 树稍稍占一点点便宜。

B 树比如你的例子中查,17 的话,一把就得到结果了,
有很多基于频率的搜索是选用B 树,越频繁query 的结点越往根上走,前提是需要对query 做统计,而且要对key 做一些变化。

另外B 树也好B+ 树也好,根或者上面几层因为被反复query ,所以这几块基本都在内存中,不会出现读磁盘IO ,一般已启动的时候,就会主动换入内存。”非常感谢。

第二节、 R 树:处理空间存储问题

相信经过上面第一节的介绍,你已经对 B 树或者 B+ 树有所了解。这种树可以非常好的处理一维空间存储的问题。 B 树是一棵平衡树,它是把一维直线分为若干段线段,当我们查找满足某个要求的点的时候,只要去查找它所属的线段即可。依我看来,这种思想其实就是先找一个大的空间,再逐步缩小所要查找的空间,最终在一个自己设定的最小不可分空间内找出满足要求的解。一个典型的 B 树查找如下:

要查找某一满足条件的点,先去找到满足条件的线段,然后遍历所在线段上的点,即可找到答案。

B 树是一种相对来说比较复杂的数据结构,尤其是在它的删除与插入操作过程中,因为它涉及到了叶子结点的分解与合并。由于本文第一节已经详细介绍了 B 树即 B+ 树,下面直接开始介绍我们的第二个主角: R 树。

简介

1984 年,加州大学伯克利分校的 Guttman 发表了一篇题为 “R-trees: a dynamic index structure for spatial searching” 的论文,向世人介绍了 R 树这种处理高维空间存储问题的数据结构。本文便是基于这篇论文写作完成的,因此如果大家对 R 树非常有兴趣,我想最好还是参考一下原著吧:)。为表示对这位牛人的尊重,给个引用先:

Guttman, A.; “R-trees: a dynamic index structure for spatial searching,” ACM, 1984 , 14

R树 在数据库等领域做出的功绩是非常显著的。它很好的解决了在高维空间搜索等问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值