1. 程式人生 > >B樹的定義、插入和刪除

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個關鍵字的時候,我們就說這個結點是滿的。

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;
}