【寒江雪】B树的一般知识与简单实现

来源:互联网 发布:阿里云幕布 高清下载 编辑:程序博客网 时间:2024/05/16 08:00

B树的定义

一棵B树T是具有如下性质的有根树

  • 每个结点x具有如下性质
    • x.n表示当前存储在结点x中的关键字个数
    • x.n个关键字本身x.key1,x.key2,x.key3…,x.keyn,以非降序存放使得x.key1 ≤ x.key2≤x.key3 ≤…≤x.keyn
    • x.leaf是一个布尔值,如果x是叶结点则为true,否则为fakse
  • 每个内部结点x还包含x.n+1个指向其孩子的指针x.c1,x.c2,x.c3…x.cn+1。叶子结点没有孩子,所以它们的ci属性没有定义
  • 关键字x.keyi对存储在各子树中的关键字的范围加以分割:如果ki为任意一个存储在以x.ci为根的子树中的关键字,那么

    k1 ≤ x.key1 ≤ k2 ≤ x.key2 ≤ k3 ≤ … ≤ x.keyn ≤ kn+1
  • 每个叶结点具有相同的深度,即树高h
  • 每个结点所包含的关键字个数有个上界和下界。用一个被称为B树的最小度数的固定整数t ( t ≥ 2) 来表示这些界:
    • 除了根结点以外的每个结点必须至少有t-1个关键字。因此,除了根节点意外的每个内部结点至少有 t 个孩子。如果树非空,根结点至少有一个关键字。
    • 每个结点至多可包含2t-1个关键字。因此,一个内部结点至多可有2t个孩子。当一个结点恰好有2t-1个关键字时,称该结点是满的。

【多说一句】t=2的B树是最简单的。每个内部结点有2个,3个或4个孩子,即一棵2-3-4树。然而在实际中,t越大,B树的高度越小。

B树中插入一个结点

在B树中,不能简单地创建一个新的叶结点,然后将其插入,因为这样得到的树将不再是合法的B树。相反,我们是将新的关键字插入一个已经存在的叶结点上。由于不能将关键字插入一个满的叶结点,故引入一个操作,将一个满的结点y按其中间关键字分裂为两个各含t-1个关键字的结点。中间关键字被提升到父亲结点中.

分裂伪代码

    B-TREE-SPLIT-CHILD(x,i)        z=ALLOCATE-NODE()        y=x.ci      z.leat=y.leat      z.n=t-1      for j = 1 to t-1        z.keyj=y.keyj+t      if not y.leaf        for j = 1 to t          z.ci=y.cj+t      y.n=t - 1      for j = x.n + 1 downto i + 1        x.cj+1=x.cj      x.ci+1=z      for j = x.n downto i        x.keyj+1=x.keyj      x.keyi=y.keyt      x.n=x.n+1

以沿树向下方式向B树中插入关键字

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

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(s,1)  B-TREE-INSERT-NONFULL(s,k) else  B-TREE-INSERT-NONFULL(r,k)
B-TREE-INSERT-NONFULL(x,k) i=x.n if x.leaf  while i ≥ t and k < x.keyi   x.keyi+1=x.keyi   i=i-1  x.keyi+1=k  x.n=x.n+1 else while i ≥ 1 and k < x.keyi     i=i-1  i=i+1  if x.ci == 2t-1   B-TREE-SPLIT-CHILD(x,i)   if k > x.keyi    i=i+1  B-TREE-INSERT-NONFULL(x.ci,k)

从B树中删除关键字

我们来简要地介绍删除操作是如何工作的

  • 如果关键字k在叶子结点中,并且x是叶结点,则从x中删除k。
  • 如果关键字k在内部结点中,并且x是内部结点,则做如下操作
    • 如果结点x中前于k的子节点y至少包含t个关键字,则找出k在以y为根的子树中的前驱k’,递归地删除k’,并在x中用k’代替k。
    • 对称的,如果结点x中后于k的子结点y至少包含t个关键字,则找出k在以y为根的子树中的后继k’,递归地删除k’,并在x中用k’代替k
    • 否则,如果y和z都只含有t-1个关键字,则将k和z的全部合并进y,这样x就失去了k和指向z的指针,并且y现在包含2t-1个关键字。然后释放z并递归地从y中删除k
  • 如果关键字k不在内部结点x中,则必须确定包含k的子树的根x.ci。如果x.ci只有t-1个关键字,必须执行以下操作来保证递归不会降至一个关键字少于t的结点。然后,通过对x的某个合适的子结点进行递归而结束。
    • 如果x.ci只含有t-1个关键字,但是它的一个相邻的兄弟至少含有t个关键字,则将x中的某一个关键字降至x.ci中,将x.ci的相邻左兄弟或右兄弟的一个关键字升至x,将该兄弟中相应的孩子指针移到x.ci中,这样就使得x.ci增加了一个额外的关键字。
    • 如果x.ci以及x.ci的所有相邻兄弟都只包含t-1个关键字,则将x.ci与一个兄弟合并,即将x的一个关键字移至新合并的新节点,使之成为该结点的中间关键字。

代码实现

树上的节点定义

//B树上的节点struct Node {    int* keys;    int key_size;    Node** childs;    int child_size;    bool leaf;//标记是否是叶子节点};typedef Node BTREE_NODE;typedef Node* PBTREE_NODE;

树的定义

const int t;//B树的度数,除了根之外的节点至少有t-1个关键字和t个孩子,至多有2t-1个关键字和2t个孩子PBTREE_NODE root;

初始化构造函数

BTree(int t_=2):t(t_){//度数至少为2        root = new BTREE_NODE();        root->leaf = true;        root->keys = new int[2*t];        root->key_size = 0;        root->childs = new PBTREE_NODE[2*t+1];        root->child_size = 0;    }

插入一个元素k

void Insert(int k) {        PBTREE_NODE r = root;        if (r->key_size == 2 * t-1) {            PBTREE_NODE s = new BTREE_NODE();            s->childs = new PBTREE_NODE[2 * t + 1];            s->keys = new int[2 * t];            root = s;            s->leaf = false;            s->key_size = 0;            s->childs[1] = r;            s->child_size = 1;            Split_Child(s, 1);            Insert_NONFull(s, k);        }        else {            Insert_NONFull(r, k);        }    }

分裂x的第i个孩子结点

void Split_Child(PBTREE_NODE x, int i) {        PBTREE_NODE z = new BTREE_NODE();//新生成的节点z        PBTREE_NODE y = x->childs[i];//x的第i个孩子        z->childs = new PBTREE_NODE[t * 2 + 1];//初始化指针数组        z->child_size = 0;        z->keys = new int[2 * t];//初始化关键字数组        z->key_size = 0;        z->leaf = y->leaf;//节点z是否是叶子可以根据y是否为叶子来判断        z->key_size=t-1;        for (int j = 1; j <= t - 1; j++) {//将y中的后t-1个关键字移到z中            z->keys[j] = y->keys[j + t];        }        if (!z->leaf) {//如果z不是叶子节点,就把y中的后t个孩子指针复制到z中            for (int j = 1; j <= t; j++) {                z->childs[j] = y->childs[j + t];            }            z->child_size = t;        }        y->key_size=t-1;        //将x的孩子后移,准备插入z的指针        for (int j = x->key_size + 1; j >= i + 1;j--) {            x->childs[j + 1] = x->childs[j];        }        x->childs[i+1] = z;        for (int j = x->key_size; j >= i; j--) {            x->keys[j + 1] = x->keys[j];        }        x->keys[i] = y->keys[t];        x->key_size++;    }

将k插入到x中或x的某个非满的子树上。(这段代码防止递归落到满子树中)

void Insert_NONFull(PBTREE_NODE x, int k) {        int i = x->key_size;        if (x->leaf) {            while (i >= 1 && x->keys[i] > k) {                x->keys[i + 1] = x->keys[i];                i--;            }            x->keys[i + 1] = k;            x->key_size++;        }        else {            while (i >= 1 && x->keys[i] > k) {                i--;            }            i++;            if (x->childs[i]->key_size == 2 * t - 1) {                Split_Child(x, i);                if (k > x->keys[i])                    i++;            }            Insert_NONFull(x->childs[i], k);        }    }

在当前结点中查找k并返回k所在位置,若不存在则返回-1

int Find(PBTREE_NODE now, int k) {        int l = 1;        int r = now->key_size;        int mid;        while (l<r) {            mid = (l + r) >> 1;            if (now->keys[mid] >= k) {                r = mid;            }            else {                l = mid+1;            }        }        if (now->keys[l] == k)            return l;        else            return -1;    }

查找某前驱结点,返回结点的指针

PBTREE_NODE FindForwardNode(PBTREE_NODE _root) {        if (_root->leaf) {            return root;        }        else {            return FindForwardNode(_root->childs[root->child_size]);        }    }

查找后继结点,返回结点指针

PBTREE_NODE FindBackwardNode(PBTREE_NODE root) {        if (root->leaf) {            return root;        }        else {            return FindBackwardNode(root->childs[1]);        }    }

从树中删除元素k

void Delete(int k) {        Delete_Inside(root, k);    }

内部调用的删除函数。递归删除元素k。

void Delete_Inside(PBTREE_NODE now, int k) {        int i = Find(now, k);        if (i != -1) {            //在当前结点中找到了k            if (now->leaf) {                //如果当前结点是叶子结点                //则从当前结点中删除k                for (int j = i; j < now->key_size; j++) {                    now->keys[j] = now->keys[j + 1];                }                now->key_size--;            }//以上是对叶子结点处理的代码块            else {//k在内部节点                if (now->childs[i]->key_size >= t) {//前于k的结点中至少包含t的关键字                    PBTREE_NODE y=FindForwardNode(now->childs[i]);                    int k_ = y->keys[y->key_size];                    now->keys[i] = k_;                    Delete_Inside(now->childs[i], k_);                }                else if (now->childs[i + 1]->key_size >= t) {//后于k的结点中至少包含t个关键字                    PBTREE_NODE z = FindBackwardNode(now->childs[i + 1]);                    int k_ = z->keys[1];                    now->keys[i] = k_;                    Delete_Inside(now->childs[i + 1], k_);                }            }//以上是对内部结点处理的代码块            //以上是在结点中有k的情况的处理        }        else {//在当前结点中找不到k            int j = now->key_size;            while (j >= 1 && now->keys[j] > k) {                j--;            }            j++;            //now.cj即是包含k的子树            if (now->childs[j]->key_size >= t) {//如果包含k的子树中有至少t个关键字,则递归删除k                Delete_Inside(now->childs[j], k);            }            else {//否则,为了避免递归降至只包含t-1个关键字的结点,执行以下步骤进行补充                //先对极端情况进行处理                if (j == 1) {//k位于最左边子树中                    if (now->childs[j + 1]->key_size >= t) {//右兄弟至少拥有t个关键字                        PBTREE_NODE y = now->childs[j];                        PBTREE_NODE z = now->childs[j + 1];                        y->keys[t] = now->keys[j];//将当前结点中的关键字下降到y中                        now->keys[j] = z->keys[1];//将z中的关键字提升到当前结点                        y->key_size++;                        for (int p = 1; p < z->key_size; p++) {//收缩z的关键字                            z->keys[p] = z->keys[p + 1];                        }                        z->key_size--;                        if (!y->leaf) {//如果不是叶子结点,还要拷贝孩子结点                            y->childs[y->child_size + 1] = z->childs[1];                            y->child_size++;                            for (int p = 1; p < z->child_size; p++) {                                z->childs[p] = z->childs[p + 1];                            }                            z->child_size--;                        }                        Delete_Inside(y, k);                    }                    else {//所有子树都只包含t-1个关键字                        PBTREE_NODE y = now->childs[j];                        PBTREE_NODE z = now->childs[j + 1];                        y->keys[t] = now->keys[j];                        for (int p = 1; p <= t - 1; p++) {                            y->keys[p + t] = z->keys[p];                        }                        y->key_size = 2 * t - 1;                        if (!y->leaf) {                            for (int p = 1; p <= t; p++) {                                y->childs[p + t] = z->childs[p];                            }                            y->child_size = 2 * t;                        }                        for (int p = j; p < now->key_size; p++) {                            now->keys[p] = now->keys[p + 1];                        }                        for (int p = j + 1; p < now->child_size; p++) {                            now->childs[p] = now->childs[p + 1];                        }                        now->key_size--;                        now->child_size--;                        delete[] z->childs;                        delete[] z->keys;                        delete z;                        Delete_Inside(y, k);                        if (now == root&&now->key_size == 0) {                            root = y;                            delete[] now->childs;                            delete[] now->keys;                            delete now;                        }                    }                }                else if (j > now->key_size) {//k位于最右边子树中                    if (now->childs[j - 1]->key_size >= t) {//左兄弟有至少t个结点                        PBTREE_NODE z = now->childs[j - 1];                        PBTREE_NODE y = now->childs[j];                        //在y中为关键字腾出位置                        for (int p = y->key_size + 1; p > 1; p--) {                            y->keys[p] = y->keys[p - 1];                        }                        y->keys[1] = now->keys[j - 1];                        now->keys[j - 1] = z->keys[z->key_size];                        y->key_size++;                        z->key_size--;                        if (!y->leaf) {//在y中为孩子腾出位置                            for (int p = y->child_size + 1; p > 1; p--) {                                y->childs[p] = y->childs[p - 1];                            }                            y->childs[1] = z->childs[z->child_size];                            y->child_size++;                            z->child_size--;                        }                        Delete_Inside(y, k);                    }                    else {//所有子树只包含t-1个关键字                        //将z合并到y中                        j--;                        PBTREE_NODE y = now->childs[j];                        PBTREE_NODE z = now->childs[j + 1];                        y->keys[t] = now->keys[j];                        for (int p = 1; p <= t - 1; p++) {                            y->keys[p + t] = z->keys[p];                        }                        y->key_size = 2 * t - 1;                        if (!y->leaf) {                            for (int p = 1; p <= t; p++) {                                y->childs[p + t] = z->childs[p];                            }                            y->child_size = 2 * t;                        }                        for (int p = j; p < now->key_size; p++) {                            now->keys[p] = now->keys[p + 1];                        }                        for (int p = j + 1; p < now->child_size; p++) {                            now->childs[p] = now->childs[p + 1];                        }                        now->key_size--;                        now->child_size--;                        delete[] z->childs;                        delete[] z->keys;                        delete z;                        Delete_Inside(y, k);                        if (now == root&&now->key_size == 0) {                            root = y;                            delete[] now->childs;                            delete[] now->keys;                            delete now;                        }                    }                }                else {//包含k的子树在区间中间                    PBTREE_NODE y = now->childs[j];                    PBTREE_NODE left = now->childs[j - 1];                    PBTREE_NODE right = now->childs[j + 1];                    if (right->key_size >= t) {                        //右兄弟有至少t个关键字                        y->keys[t] = now->keys[j];                        now->keys[j] = right->keys[1];                        y->key_size++;                        for (int p = 1; p < right->key_size; p++) {                            right->keys[p] = right->keys[p + 1];                        }                        right->key_size--;                        if (!y->leaf) {                            y->childs[t + 1] = right->childs[1];                            for (int p = 1; p < right->child_size; p++) {                                right->childs[p] = right->childs[p + 1];                            }                            right->child_size--;                            y->child_size++;                        }                        Delete_Inside(y, k);                    }                    else if (left->key_size >= t) {                        //左兄弟中至少含有t个关键字                        //腾出位置准备存放关键字                        for (int p = t; p > 1; p--) {                            y->keys[p] = y->keys[p - 1];                        }                        y->keys[1] = now->keys[j - 1];                        y->key_size++;                        now->keys[j - 1] = left->keys[left->key_size];                        left->key_size--;                        if (!y->leaf) {                            for (int p = t + 1; p > 1; p--) {                                y->childs[p] = y->childs[p - 1];                            }                            y->childs[1] = left->childs[left->child_size];                            left->child_size--;                        }                        Delete_Inside(y, k);                    }                    else {                        //左右兄弟都只包含t-1个关键字                        y->keys[t] = now->keys[j];                        for (int p = j; p < now->key_size; p++) {                            now->keys[p] = now->keys[p + 1];                        }                        now->key_size--;                        for (int p = 1; p <= t - 1; p++) {                            y->keys[p + t] = right->keys[p];                        }                        y->key_size = 2 * t - 1;                        if (!y->leaf) {                            for (int p = 1; p <= t; p++) {                                y->childs[p + t] = right->childs[p];                            }                            y->child_size = 2 * t;                        }                        delete[] right->childs;                        delete[] right->keys;                        delete right;                        Delete_Inside(y, k);                        if (now == root&&now->key_size == 0) {                            root = y;                            delete[] now->childs;                            delete[] now->keys;                            delete now;                        }                    }                }            }        }    }

(这段代码有很多冗余的部分,也是最难最复杂的部分,由于我头昏眼花,在递归中脑子打结,导致代码长这样了。如果大家有更好的写法可以发邮件给我,希望能得到大家的指点。)


Copyright ©By 寒江雪
QQ:211392413
Email:211392413@qq.com
Date:2016-11-21

1 0