《算法导论》-- B树

1 概述

B树是为磁盘或其它直接存取的辅助存储设备而设计的一种平衡搜索树。B树类似于红黑树,但它在降低磁盘I/O操作方面要更好一些。许多数据库系统使用B树或者B树的变种来存储信息(比如MYSQL的B+tree)。

如图,当B树的一个内部结点x包含x.n个关键字,那么结点x就有x.n+1个孩子。结点x中的关键字就是分隔点,它把节点x中所处理的关键字的属性分隔为x.n+1个子域。当一课B树中查找一个关键字时,基于对存储在x中的x.n个关键字的比较,做出一个(x.n+1)路的选择。叶结点的结构和内部结点的结构不同,后面讨论。


image.png

2 辅存上的数据结构

磁盘结构:


image.png

磁盘有两个机械运动的部分:盘片旋转和磁臂移动,这是一次I/O耗费时间的原因所在。磁盘每次存取多个数据项。由于大多数系统中,一个B树算法的运行时间主要由它所执行的DISK-READ和DISK-WRITE操作的次数决定,所以我们希望这些操作能够读或写尽可能的信息。因此,一个B树结点通常和一个完整磁盘叶一样大,并且一个磁盘页的大小限制了一个B树结点可以含有的孩子个数。

3 B树的定义

任何和关键字相关联的数据将于关键字一样存放在同一个结点,实际上可能只是存放一个指针,这个指针存放该关键字对应的数据的磁盘页。B+树把所有的数据存在叶子结点,内部结点只存放关键字和孩子指针,因此最大化了内部节点的分支因子。

一棵B树是具有以下性质的有跟树:

  • 1.每个结点x有以下属性:a. 有x.n个关键字;b. 关键字以非降序顺序排列;c. x.leaf一个布尔值,true表示是叶结点;
    1. 结点x包含x.n+1个孩子指针;
    1. 关键字用于对子树关键字范围加以分割;
    1. 每个叶结点具有相同的高度;
    1. 每个结点所包含的关键字个数有上界和下界,用一个成为b树的最小度数的固定整数t>=2 来表示这些界,t越大树高度越小:
      a. 除根节点以外的每个结点x至少有t-1个关键字,至少t个孩子;
      b. 每个结点至多有2t-1个关键字,至多2t个孩子。

4 B树的高度

h <= log(t) (n+1)/2 (t为对数的底)

5 B树的基本操作

5.1 搜索

伪代码如下

B-TREE-SEARCH(x, k)  //x为节点,k为插入关键字
i = 1  //
while i <= x.n and k > x.key(i) //搜索k所在的节点位置
    i = i + 1
 if i <= x.n and k == x.key(i)
    return (x, i)
elseif x.leaf  //当前是子节点,返回NIL
    return NIL
else  DISK-READ(x, c(i)) //查找子节点
    return B-TREE-SEARCH(x.c(i), k) 
5.2 插入
创建一棵空的B树

为构造一棵B树T,先用B-TREE-CREATE来创建一个空的根结点,然后调用B-TREE-INSERT来添加新的关键字。这些过程都要用一个辅助过程ALLOCATE-NODE,它在O(1)时间内为一个新节点分配一个磁盘页,我们可以假定由ALLOCATE-NODE所创建的结点并不需要DISK-READ,因为磁盘上还没有关于该结点的有用信息。

B-TREE-CREATE(T)
x = ALLOCATE-NODE()
x.leaf = TRUE
x.n = 0
DISK-WRITE(x)
T.root = x
分裂

由于不能将关键字插入一个满的叶结点,故引入一个操作,将一个满的结点y(有2t-1个关键字)按中间的关键字y.key(t)分裂为两个各含t-1个关键字的结点。中间关键字被提升到y的父结点,以标识两棵新树的划分点。但是如果y的父结点也是满的,就必须在插入新的关键点之前将其分裂,最终满结点的分裂会沿着树向上传播。

与一个二叉搜索树一样,可以在从树根到叶子这个单程向下过程中将一个新的关键字插入B树中,为了做到这一点,我们并不是等到找出插入过程中实际要分裂的满结点时才做分裂。相反,当沿着树往下查找新的关键字所属位置时,就分裂沿途遇到的每个满结点(包括叶结点本身)。因此,每当要分裂一个满结点y时,就能确保它的父结点不是满的。

分裂B树中的结点
image.png
B-TREE-SPLIT-CHILD(x, i) //x是结点,i为x的满子节点的下标
z = ALLOCATE-NODE()
y = x.c(i)
z.leaf = y.leaf
z.n = t - 1
for j = 1 to t - 1  // 复制关键字
    z.key(j) = y.key(j + 1)
if not y.leaf // 复制孩子指针
    for j = 1 to t
        z.c(j) y.c(j+t)
y.n = t - 1    
for j = x.n + 1 downto i + 1 //上层增加一个子节点,关键字和孩子结点要向前推进1
    x.c(j + 1) = x.c(j)
x.c(i + 1) = z
for j = x.n downto i 
    x.key(j + 1) = x.key(j)
x.key(i) = y.key(i)
x.n = x.n + 1
DISK-WRITE(y)
DISK-WRITE(z)
DISK-WRITE(x)
沿树单程下行方式向B树插入关键字

在一颗高度为h的B树T中,沿树单程下行方式插入一个关键字k的操作需要O(h)次磁盘存取。所需要的CPU时间为O(th) = O(tlog(t)n)。过程B-TREE-INSERT利用B-TREE-SPLIT-CHILD来保证递归始终不会降至一个满结点上。

//把关键字k插入T树中
B-TREE-INSERT(T, k)
r = T.root
if r.n == 2t - 1  //如果根结点为满,则分裂
    s = ALLOCATE-NODE()
    T.root = s
    s.leaf = FALSE
    s.n = 0
    s.c1= r
    B-TREE-SPLIT-CHILD(s, 1) 
    B-TREE-INSERT-NONFULL(s, k)
else 
    B-TREE-INSERT-NONFULL(s, k)

//插入未满的结点
B-TREE-INSERT-NONFULL(x, k)
i = x.n
if x.leaf //如果是叶子结点,直接找到位置插进去
    while i >= 1 and k < x.key(i)
        x.key(i+1) = x.key(i)
        i = i - 1
    x.key(i + 1) = k
    x.n = x.n + 1
    DISK-WRITE(x)
else while  i >= 1 and k < x.key(i) //内叶结点则找到位置,判断所在的孩子结点是否已满,满则分裂,未满则对孩子结点递归调用自己
              i = i  - 1
        i = i + 1
        DISK-READ(x, c(i))
        if x.c(i).n ==  2t - 1
            B-TREE-SPLIT-CHILD(x, i) 
            if k > x.key(i)
                i = i + 1
       B-TREE-INSERT-NONFULL(x.c(i), k)
image.png
5.3 删除

必须防止删除操作而导致树的结构违反B树的性质。下面简要介绍删除操作如何工作:

  • 1 如果关键字k在结点x中,并且x是叶结点,则从x中删除k;

  • 2 如果关键字k在结点x中,并且x是内部结点,则有下面的情况:

    • a 如果结点x中前于k的子结点y至少包含t个关键字,则找出k在以y为根的子树中的前驱k'。递归地删除k',并且在x中用k'代替k。(找到k'并删除它可在沿树下降的单过程中完成)。
    • b 对称地,如果y有少于t个关键字,则坚持结点x中后于k的子结点z。如果z至少有t个关键字,则找出k在以z为根的子树中的后继k'。递归地删除k',并在x中用k'代替k。
    • c 否则,如果y和z都只含有t-1个关键字,则将k和z的全部合并进y,这样x就失去了k和指向z的指针,并且y现在包含2t-1关键字,然后释放z并递归地从y中删除k。
  • 3 如果关键字k当前不在内部结点x中,则确定比包含k的子树的根x.c(i)(如果k确实在树中)。如果x.c(i)只有t-1个关键字,必须指向步骤3a或3b来保证将至一个至少包含t个关键字的结点。然后,通过对x的某个合适的子结点进行递归而结束。

    • a 如果x.c(i)只含有t-1个关键字,但是它的一个相邻的兄弟至少包含t个关键字,则将x中的某一个关键字降至x.c(i)中,将x。c(i)的相邻左兄弟或右兄弟的一个关键字升至x,将该兄弟中相应的孩子指针移到x.c(i)中,这样就使得x.c(i)增加了一个额外的关键字。
    • b 如果x.c(i)以及x.c(i)中的所有邻兄弟都只包含t-1个关键字,则将x.c(i)与一个兄弟合并,即将x的一个关键字移至新合并的结点,使之成为该结点的中间关键字。


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

推荐阅读更多精彩内容

  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,161评论 0 25
  • B树 1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(Balan...
    铁甲依然在_978f阅读 1,443评论 0 4
  • 原文链接 B树 1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(...
    非典型程序员阅读 1,145评论 0 3
  • 原创/ 喵星电影 小动物 “三个智猴”的典故,源于佛教训诫—— 一个双手捂眼做惨不忍睹状(see no evil)...
    喵星电影阅读 978评论 0 0
  • “嗨” “想不想去看星星” ...
    予妳星陳阅读 759评论 0 0