赞
踩
动态查找树主要有:二叉查找树(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树的各种操作能使B树保持较低的高度,从而达到有效避免磁盘过于频繁的查找存取操作,从而有效提高查找效率)。
在开始介绍B~tree之前,先了解下相关的硬件知识,才能很好的了解为什么需要B-tree这种外存数据结构。
当磁盘驱动器执行读/写功能时。盘片装在一个主轴上,并绕主轴高速旋转,当磁道在读/写头(又叫磁头) 下通过时,就可以进行数据的读 / 写了。
当盘片绕主轴旋转的时候,磁头与旋转的盘片形成一个圆柱体。各个盘面上半径相同的磁道组成了一个圆柱面,我们称为柱面 。因此,柱面的个数也就是盘面上的磁道数。
磁盘上数据必须用一个三维地址唯一标示:柱面号、盘面号、块号(磁道上的盘块)。
经过上面三个步骤,指定数据的存储位置就被找到。这时就可以开始读/写操作了。
每次磁盘的读写,IO代价主要花费在查找时间Ts上,所以,在大规模数据存储方面,大量数据存储在外存磁盘中,而在外存磁盘中读取/写入块(block)中某数据时,首先需要定位到磁盘中的某块,如何有效地查找磁盘中的数据,需要一种合理高效的外存数据结构,就是下面所要重点阐述的B-tree结构,以及相关的变种结构:B+-tree结构和B*-tree结构。
具体讲解之前,有一点,再次强调下:B-树,即为B树。因为B树的原英文名称为B-tree,而国内很多人喜欢把B-tree译作B-树,其实,这是个非常不好的直译,很容易让人产生误解。如人们可能会以为B-树是一种树,而B树又是一种一种树。而事实上是,B-tree就是指的B树。特此说明。
B树是为了磁盘或其它存储设备而设计的一种多叉平衡查找树,与红黑树很相似。但是也存在一些不同。B树与红黑树最大的不同在于,B树的结点可以有多个子女,从几个到上千个。那为什么又说B树与红黑树很相似呢?因为与红黑树一样,一棵含n个结点的B树的高度也为O(lgn),但可能比一棵红黑树的高度小许多,应为它的分支因子比较大。所以,B树可以在O(logn)时间内,实现各种如插入(insert),删除(delete)等动态集合操作。
B树中每一个结点能包含的关键字数有一个上界和下界。这个下界可以用一个称作B树的最小度数(算法导论中文版上译作度数,最小度数即内节点中节点最小孩子数目)t(t>=2)表示。
对于辅存做IO读的次数取决于B树的高度。而B树的高度由什么决定的呢?
若B树某一非叶子节点包含N个关键字,则此非叶子节点含有N+1个孩子结点,而所有的叶子结点都在第I层,我们可以得出:
树为空时
插入20时,对已有键值10和20进行比较,按照从左到右从小到大的顺序插入。
当20插入根节点以后,节点size等于M,此时需要对节点进行分裂。若不分裂,则该节点孩子为四个,违反了性质2。
这里节点结构定义时多给了一格,以便插入键值时键值数组不会越界。
分裂过程如下图:
分裂时,创建两个新节点,一个作为根节点用以存放节点中间键值为20的节点,一个用来存放中间键值右边的所有键值,其次,更新孩子双亲指向关系。
树不空
依次插入40和50,自上而下查找插入位置,根据大小排列,插入30所在节点,50插入后需要再次分裂节点,此时,因该节点非根节点,则,分裂时,将中间键值之后的键值移入新节点中,中间键值存入双亲节点中,在此例中,其双亲为根节点,往双亲插入中间键值时,按照从左向右,从小到大的顺序,即键值的插入顺序。
再次插入80,70。图示如下:
分裂图示如下:
上图中,70插入后,该节点需要分裂,分裂完毕之后,70存入根节点,此时根节点也需要分裂,以满足B树性质。需要注意的是,分裂过程中,各个节点的孩子双亲指向需要及时更改,否则出错,具体细节见代码实现。
首先,查找B树中需要删除的元素,如果该元素在B树中存在,则将该元素在其结点中进行删除,如果删除该元素后,首先判断该元素是否有左右孩子结点,如果有,则上移孩子结点中的某相近元素到父结点中,然后是移动之后的情况;如果没有,直接删除后,然后是移动之后的情况
删除元素,移动相应元素之后的处理过程
下面看图解:一棵5阶B树(树中每个结点最多有4个元素,最少有2个元素)
B树初始状态
依次删除H,T,R,E
删除H结点,首先查找H结点,H在一个叶子结点中,且该叶子结点元素数目大于3大于最小的元素数目2,则操作很简单,只需要移动K至原来H的位置,移动L至原来K的位置(也就是结点中删除元素后面的元素依次向前移动)
删除元素T
删除元素T,元素T没有在叶子结点中,而是在中间结点中找到,发现他的继承者W,将W上移到T的位置,然后将原包含W的孩子结点中的W进行删除,这里恰好删除W后,该孩子结点中元素个数大于2,无需进行合并操作
删除元素R
删除元素R,R在叶子结点中,但是该结点中元素数目为2,删除导致只有1个元素,已经小于最小元素数目ceil(5/2)-1=2,根据移动后的方案我们知道:如果其某个相邻兄弟结点中比较丰满,则可以向父结点借一个元素,然后将最丰满的相邻兄弟结点中上移最后或最前一个元素到父结点中,在这个例子中,右相邻兄弟结点比较丰满,所以先向父结点借一个元素W下移到叶子结点中,替代原来S的位置,同时S前移;然后X在相邻右兄弟结点中上移到父结点中,最后在相邻右兄弟结点中删除X,后面元素前移
删除元素E
删除元素E,删除后会导致很多问题,因为E所在的结点数目刚好达标,刚好满足最小元素个数2,而相邻的兄弟结点也是同样的情况,删除一个元素都不能满足条件,所以需要该结点与某相邻兄弟结点进行合并操作;首先,移动父结点的元素(该元素在两个需要合并的结点元素之间)下移到其子结点中,然后将这两个结点合并称一个结点,所以在该实例中,首先将父结点中的元素D下移到已经删除E而只有F的结点中,然后含有D、F的结点和含有A、C的结点进行合并成为一个结点
但是,这样并没有结束,因为父结点只包含一个元素G,不满足5阶B树每个结点至少有2个关键字的特性。如果这个问题结点的相邻兄弟结点比较丰满,则可以向父结点借一个元素。假设这时右兄弟结点(含有Q、X)有两个以上的元素,可以把M下移到元素少的结点,将Q上移到M的位置,这时,Q的左子树变成含有G、M的树。但是这个实例中,相邻结点都正好脱贫,所以,只能与兄弟结点合并成一个结点,而根节点中唯一的元素M下移到子结点,这样,树的高度减少一层。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。