B树的定义、插入和删除

B树基本定义:

B树是为磁盘或其他直接存取的辅助存储设备而设计的一种平衡搜索树。 一棵B树是具有以下性质的有根树: 1. 每个结点x有下面的属性:     a. x.n,当前储存在结点x中的关键字个数;     b. x.n个关键字本身,x.key[1], x.key[2], …, x.key[x.n],以非降序存放,使得:         x.key[1]<=x.key[2]<=…<=x.key[x.n]     c. s.leaf 一个布尔值,如果x为叶结点则为true,如果为内部结点,则为false

2. 每个内部结点x还包含x.n+ 1个指向孩子结点的指针。叶结点没有孩子,其孩子指针没有意义。

3. 关键字x.key[i]对存储在各子树中的关键字的范围加以分割 :如果k[i]为任意一个存储在以x.c[i]为根的子树中的关键字,那么     k1<=x.key[i]<=k2<=x.key[2]<=…<=x.key[x.n]<=k[x.n+1]

4. 每个孩子结点都具有相同的深度,即树高h

5. 每个孩子结点的所包含的关键字的个数有上届和下届,用一个被称为B树的最小度数t来固定表示。     a.  除了根结点以外的每个结点必须包含至少 t –  1 个关键字,因此,除了根结点以外,每个内部结点必须包含至少 t 个孩子

    b. 每个结点至多可以包含 2t – 1个关键字,因此,每个内部结点至多可以包含 2t 个孩子。当一个内部结点包含 2t-1个关键字的时候,我们就说这个结点是满的。


给出源代码的下载链接:http://download.csdn.net/detail/sky453589103/9041779


B树的插入操作:

在进行B树的插入操作的时候,要防止插入的结点的关键字个数超过 2t – 1。当要插入一个满结点的时候,要进行分裂操作。

将整个满结点分裂成各含有 t- 1 个结点的两个结点,和中间的一个关键字(不要忘记它的孩子)。 在算法导论中是用单程算法进行分裂操作的(单程算法:从树的根开始往下,没有任何返回向上的操作)。 从根开始插入,将沿途遇到的所有的满结点进行分裂,这样就保证了插入的结点不是满结点。

当根结点是满结点的时候需要分裂根结点,并生成新的根。

bool BSubTree::Insert(const KeyType key) {
  BSubTreeNodePtr r = _root;
  if (r->_MaxChildNum - 1 == r->_n) {
    BSubTreeNodePtr s = new BSubTreeNode(r->_MaxChildNum);
    s->Init();
    _root = s;
    s->_isleaf = false;
    s->_child[0] = r;
    Split(s, 0);
    return Insert_notfull(s, key);
  } else {
    return Insert_notfull(r ,key);
  }

  return false;
}

插入操作的辅助操作,当不是在根结点插入的时候,需要用这个辅助操作进行递归插入。

bool BSubTree::Insert_notfull(BSubTreeNodePtr pnode, const KeyType key) {
  unsigned int i = pnode->_n - 1;
  if (true == pnode->_isleaf) {
    //  从后面往前开始搜索第一个小于key的关键字
    //  在搜索的过程中往后移动元素,腾出位置给插入的元素。
    while (i >= 0 && key < pnode->_key[i]){
      pnode->_key[i + 1] = pnode->_key[i];
      --i;
    }
    pnode->_key[i + 1] = key;
    ++pnode->_n;

    return true;
  } else {
    while (i >= 0 && key < pnode->_key[i]) {
      --i;
    }
    //  执行这一步之后,pnode->_key[i - 1] <= key <= pnode->_key[i]
    //  key <= pnode->_key[i] <= pnode->_child[i + 1]->key[0]<=
    //  pnode->_child[i + 1]->_key[pnode->_child[i]->_n - 1] <= pnode->key[i + 1]
    ++i;
    if (pnode->_MaxChildNum - 1 == pnode->_child[i]->_n) {
      //  从大于key的关键字开始分裂
      //  分裂之后 key 可能小于_key[i]也可能大于_key[i]
      Split(pnode, i);
      //  将i调整到合适的位置,使得pnode->_key[i - 1] <= key <= pnode->_key[i]
      if(key > pnode->_key[i]) {
        ++i;
      }
    }
    return Insert_notfull(pnode->_child[i], key);
  }

B树的分裂操作:

B树的分裂操作是将一个满结点的后半部分(t-1个关键字,t个孩子)直接剪贴到一个新结点中,并中间结点(第t个关键字,从第一个开始)上移到满结点的父结点中。时间复杂度为O(n)。

BSubTreeNodePtr rightchild = new BSubTreeNode(pnode->_MaxChildNum);
  if (false == rightchild->Init()) {
    return false;
  }

  BSubTreeNodePtr leftchild  = pnode->_child[index];
  unsigned int t = leftchild->_MaxChildNum / 2;
  rightchild->_isleaf = leftchild->_isleaf;
  rightchild->_n = t - 1;
  //  分裂leftchild,把leftchild的后半部分复制到rightchild
  for (unsigned int j = 0; j < t - 1; ++j) {
    rightchild->_key[j] = leftchild->_key[j + t];
  }
  if (!leftchild->_isleaf) {
    for (unsigned int j = 0; j < t; ++j) {
      rightchild->_child[j] = leftchild->_child[j + t];
    }
  }
  leftchild->_n = t - 1;
  //  把从index + 1开始的孩子一直往后挪,也就是大于pnode->key[index]的孩子往后挪
  //  这时候pnode->_child[index + 2] = pnode->nodep[index+ 1]
  for (unsigned int j = pnode->_n; j > index; --j) {
    pnode->_child[j + 1] = pnode->_child[j];
  }
  pnode->_child[index + 1] = rightchild;
  //  把从index开始的关键字往后挪,也就是大于pnode->key[index]的关键字往后挪
  //  这时候pnode->_key[index + 1] = pnode->_key[index]
  for (unsigned int j = pnode->_n - 1; j > index - 1; --j) {
    pnode->_key[j + 1] = pnode->_key[j];
  }
  //  将中间关键字上移到父结点中。 
  pnode->_key[index] = leftchild->_key[t - 1];
  ++pnode->_n;

  return true;
}

B树的删除操作:

B树的删除操作比较复杂。
B树的删除包括以下三个情况: 1. 如果关键字k在叶节点x中,则从x中删除; 2. 如果关键字k在内部结点x中,则:     a. 如果x的左兄弟y至少包含 t 个关键字,则找出以y为根的子树中最大的关键字k1,递归的删除k1,并用k1代替k     b.如果x的由兄弟z至少包含 t 个关键字,则找出以z为根的子树中最小的关键字k1,递归的删除k1,并用k1代替k     c. 否则就合并左右兄弟和关键字k到y中,这样y结点中就含有了 2t – 1 个关键字,x也失去了指向z的孩子。删除x中指向z的孩字,释放z的内存,并递归的从y中删除k。 3如果关键字k当前不在内部结点x中,则确定包含k的子树的根x.c[i]。如果,x.c[i]的关键字个数为t – 1,那么就要用下面两个方法为x.c[i]补充一个关键字:     a. 如果x.[i]只有 t – 1个关键字,但是它的左右兄弟至少包含t个关键字,则将x中的某个关键字下移到x.c[i]中,将x.c[i]的左右兄弟中的某个关键字上移到x中,相应的孩子指针也要移动。     b. 如果x.c[i]的左右兄弟都只有 t – 1个关键字,那么就将x.c[i]与其中一个兄弟合并在一起变成新的结点。将x的一个关键字下移到新结点中,使下移的结点称为新结点的中间关键字。

bool BSubTree::Delete(BSubTreeNodePtr pnode, const KeyType key) {
  unsigned int i = 0;
  unsigned int t = pnode->_MaxChildNum / 2;
  for (; i < pnode->_n; ++i) {
    if (key >= pnode->_key[i]) {
      break;
    }
  }
  if (key == pnode->_key[i]) {
    if (true == pnode->_isleaf) {
      while (i < pnode->_n - 1) {
        pnode->_key[i] = pnode->_key[i + 1];
        --pnode->_n;
        ++i;
      }
      return true;
    } else {
      if (pnode->_child[i]->_n > t - 1) {
        BSubTreeNodePtr p = pnode;
        while (!p->_isleaf) {
          p = p->_child[p->_n];
        }
        pnode->_key[i] = p->_key[p->_n - 1];

        return Delete(p,  p->_key[p->_n - 1]);
      }
      else if (pnode->_child[i + 1]->_n > t  -1) {
        BSubTreeNodePtr p = pnode;
        while (!p->_isleaf) {
          p = p->_child[0];
        }
        pnode->_key[i + 1] = p->_key[0];

        return Delete(p ,p->_key[0]);
      }
      else {
        //  为什么这个判断之后不能直接从当前结点删除关键字,然后合并左右孩子?
        unsigned int index;
        unsigned int j = pnode->_child[i]->_n;
        pnode->_child[i]->_key[j] = pnode->_key[i];
        ++j;
        for (index = 0; index < pnode->_child[i + 1]->_n; ++index, ++j) {
          pnode->_child[i]->_key[j] = pnode->_child[i + 1]->_key[index];
        }
        pnode->_child[i]->_n += pnode->_child[i + 1]->_n + 1;
        delete pnode->_child[i + 1];
        for (index = i; index < pnode->_n - 1; ++index) {
          pnode->_key[index] = pnode->_key[index + 1];
          pnode->_child[index + 1] = pnode->_child[index + 2];
        }

        return Delete(pnode->_child[i], key);
      }
     
    }
  }
  else if (false == pnode->_isleaf){
    if (pnode->_child[i]->_n > t - 1) {
      return Delete(pnode->_child[i], key);
    } else {
      if (i - 1 >=0 && pnode->_child[i - 1]->_n > t - 1) {
        //  将当前位置的关键字下移到pnode->_child[i]中,
        //  pnode->_child[i - 1]->_key[pnode->_child[i - 1]->_n]上移到当期位置
        for (unsigned int temp = pnode->_child[i]->_n - 1; temp >= 0; --temp) {
          pnode->_child[i]->_key[temp + 1] = pnode->_child[i]->_key[temp];
          pnode->_child[i]->_child[temp + 2] = pnode->_child[i]->_child[temp + 1];
        }
        pnode->_child[i]->_child[1] = pnode->_child[i]->_child[0];
        pnode->_child[i]->_key[0] = pnode->_key[i];
        pnode->_child[i]->_child[0] = pnode->_child[i - 1]->_child[pnode->_child[i - 1]->_n];
        ++pnode->_child[i]->_n;
        pnode->_key[i] = pnode->_child[i - 1]->_key[pnode->_child[i - 1]->_n];
        --pnode->_child[i - 1]->_n;
      }
      else if (pnode->_child[i + 1]->_n > t - 1) {
        //  将当前位置的关键字下移到pnode->_child[i]中,
        //  pnode->_child[i + 1]->_key[pnode->_child[i + 1]->_n]上移到当期位置
        pnode->_child[i]->_key[pnode->_child[i]->_n] = pnode->_key[i];
        pnode->_child[i]->_child[pnode->_child[i]->_n + 1] =
          pnode->_child[i + 1]->_child[0];
        ++pnode->_child[i]->_n;
        pnode->_key[i] = pnode->_child[i + 1]->_key[0];
        for (unsigned int temp = 0; temp < pnode->_child[i + 1]->_n - 1; ++temp) {
          pnode->_child[i + 1]->_key[temp] = pnode->_child[i + 1]->_key[temp + 1];
          pnode->_child[i + 1]->_child[temp] = pnode->_child[i + 1]->_child[temp + 1];
        }
        pnode->_child[i + 1]->_child[pnode->_child[i + 1]->_n - 1] =
          pnode->_child[i + 1]->_child[pnode->_child[i + 1]->_n];
        --pnode->_child[i + 1]->_n;
      }
      else {
        //  如果pnode->_child[i]所有的相邻兄弟的关键字个数都等于 t - 1
        //  则将pnode->_child[i]与右相邻的兄弟合并。
        pnode->_child[i]->_key[pnode->_child[i]->_n] = pnode->_key[i];
        pnode->_child[i]->_child[pnode->_child[i]->_n + 1] = pnode->_child[i + 1]->_child[0];
        for (unsigned int temp = i; temp < pnode->_n - 1; ++temp) {
          pnode->_key[temp] = pnode->_key[temp + 1];
          pnode->_child[temp + 1] = pnode->_child[temp + 2];
        }
        --pnode->_n;

        return Delete(pnode->_child[i], key);
      }
    }
  }
  else {
    return false;
  }

  return true;
}

    原文作者:B树
    原文地址: https://blog.csdn.net/SKY453589103/article/details/47952499
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞