B+樹的實現,主要講解刪除操作
阿新 • • 發佈:2018-12-23
關於B+樹的基本定義,隨便一本資料結構的書或者演算法導論中都有,就不做介紹了。雖然網上和書本上都有很多對B+樹的介紹,但是有很多資料對於B+樹的操作或者介紹不全,或者有描寫錯誤的地方,我這裡參考這位大神的文章http://blog.csdn.net/xinghongduo/article/details/7059459,整理了B+樹刪除操作的所有情況。
我用visio畫了刪除操作可能出現的情況總結,如下圖(在網頁上看的話字型太小了,推薦下載到本地或者放大再看):
對應上圖的刪除部分程式碼如下所示:
void BPlusTree::delete_BPlus_tree(FILEP current, TRecord &record) { int i, j; BPlusNode x; ReadBPlusNode(current, x); for (i = 0; i < x.nkey && record.key > x.key[i]; i++); if (i < x.nkey && x.key[i] == record.key) //在當前節點找到關鍵字 { if (!x.isleaf) //在內節點找到關鍵字 { BPlusNode child; ReadBPlusNode(x.Pointer[i], child); if (child.isleaf) //如果孩子是葉節點 { if (child.nkey > MAX_KEY / 2) //情況A { //1、找到關鍵字,當前節點是內節點,孩子是葉子節點,孩子節點半滿 //直接刪除 x.key[i] = child.key[child.nkey - 2]; child.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); //刪除完就return了 return; } else //否則孩子節點的關鍵字數量不過半 { if (i > 0) //有左兄弟節點 { BPlusNode lbchild; ReadBPlusNode(x.Pointer[i - 1], lbchild); //2、找到關鍵字,當前節點是內節點,孩子是葉子節點,孩子節點不半滿,左孩子半滿 //向左孩子借record if (lbchild.nkey > MAX_KEY / 2) //情況B { //右移鍵值和指標 for (j = child.nkey; j > 0; j--) { child.key[j] = child.key[j - 1]; child.Pointer[j] = child.Pointer[j - 1]; } //下放父節點鍵值和指標 child.key[0] = x.key[i - 1]; child.Pointer[0] = lbchild.Pointer[lbchild.nkey - 1]; child.nkey++; lbchild.nkey--; //更新父節點鍵值和指標 x.key[i - 1] = lbchild.key[lbchild.nkey - 1]; x.key[i] = child.key[child.nkey - 2]; //寫回 WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i - 1], lbchild); WriteBPlusNode(x.Pointer[i], child); } else //情況C { //3、找到關鍵字,當前節點是內節點,孩子是葉子節點,孩子節點不半滿,左孩子也不半滿 //向左孩子合併,child頁加入freelist //拷貝 for (j = 0; j < child.nkey; j++) { lbchild.key[lbchild.nkey + j] = child.key[j]; lbchild.Pointer[lbchild.nkey + j] = child.Pointer[j]; } lbchild.nkey += child.nkey; lbchild.Pointer[MAX_KEY] = child.Pointer[MAX_KEY]; //更新當前內節點 for (j = i - 1; j < x.nkey - 1; j++) { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; //i-1指向新的lbchild右端(末端) x.key[i - 1] = lbchild.key[lbchild.nkey - 2]; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i - 1], lbchild); //遊標減1 i--; } } else //只有右兄弟節點 { BPlusNode rbchild; ReadBPlusNode(x.Pointer[i + 1], rbchild); if (rbchild.nkey > MAX_KEY / 2) //情況D { //4、找到關鍵字,當前節點是內節點,孩子是葉子節點,孩子節點不半滿,只有右兄弟節點,右兄弟節點半滿 //向右孩子借record x.key[i] = rbchild.key[0]; child.key[child.nkey] = rbchild.key[0]; child.Pointer[child.nkey] = rbchild.Pointer[0]; child.nkey++; for (j = 0; j < rbchild.nkey - 1; j++) { rbchild.key[j] = rbchild.key[j + 1]; rbchild.Pointer[j] = rbchild.Pointer[j + 1]; } rbchild.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); WriteBPlusNode(x.Pointer[i + 1], rbchild); } else //情況E { //5、找到關鍵字,當前節點是內節點,孩子是葉子節點,孩子節點不半滿,只有右兄弟節點,右兄弟節點不半滿 //右兄弟節點向左孩子節點合併 for (j = 0; j < rbchild.nkey; j++) { child.key[child.nkey + j] = rbchild.key[j]; child.Pointer[child.nkey + j] = rbchild.Pointer[j]; } child.nkey += rbchild.nkey; child.Pointer[MAX_KEY] = rbchild.Pointer[MAX_KEY]; //釋放rbchild佔用的空間x.Pointer[i+1] for (j = i; j < x.nkey - 1; j++) { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); } } } } else //情況F { //6、找到關鍵字key,當前節點是內節點,孩子也是內節點 //找到key在B+樹葉節點的左兄弟關鍵字,將這個關鍵字取代key的位置 TRecord trecord; trecord.key = record.key; SearchResult result; Search_BPlus_Tree(trecord, result); //找到了葉子節點左兄弟節點 BPlusNode last; ReadBPlusNode(result.Baddress, last); //更新當前節點後寫回 x.key[i] = last.key[last.nkey - 2]; WriteBPlusNode(current, x); //接下來要保證當前節點的孩子節點半滿,內部節點的借用以及合併和葉子節點的借用合併不一樣 if (child.nkey > MAX_KEY / 2) //情況H { } else //否則孩子節點的關鍵字數量不過半,則將兄弟節點的某一個關鍵字移至孩子 { if (i > 0) //x.key[i]有左兄弟 { BPlusNode lbchild; ReadBPlusNode(x.Pointer[i - 1], lbchild); if (lbchild.nkey > MAX_KEY / 2) //情況I { //7、找到關鍵字key,當前節點是內節點,孩子也是內節點,孩子節點不半滿,有左兄弟節點,左兄弟節點半滿 //向左兄弟節點借record //騰出1個單位的空間 for (j = child.nkey; j > 0; j--) { child.key[j] = child.key[j - 1]; child.Pointer[j + 1] = child.Pointer[j]; } child.Pointer[1] = child.Pointer[0]; child.key[0] = x.key[i - 1]; child.Pointer[0] = lbchild.Pointer[lbchild.nkey]; child.nkey++; x.key[i - 1] = lbchild.key[lbchild.nkey - 1]; lbchild.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i - 1], lbchild); WriteBPlusNode(x.Pointer[i], child); } else //情況J { //8、找到關鍵字key,當前節點是內節點,孩子也是內節點,孩子節點不半滿,有左兄弟節點,左兄弟節點不半滿 //向左兄弟節點合併,把清空後的child節點加入freelist lbchild.key[lbchild.nkey] = x.key[i - 1]; //將孩子節點複製到其左兄弟的末尾 lbchild.nkey++; for (j = 0; j < child.nkey; j++) //將child節點拷貝到lbchild節點的末尾, { lbchild.key[lbchild.nkey + j] = child.key[j]; lbchild.Pointer[lbchild.nkey + j] = child.Pointer[j]; } lbchild.Pointer[lbchild.nkey + j] = child.Pointer[j]; lbchild.nkey += child.nkey; //已經將child拷貝到lbchild節點 //釋放child節點的儲存空間,x.Pointer[i] //將找到關鍵字的孩子child與關鍵字左兄弟的孩子lbchild合併後,將該關鍵字前移,使當前節點的關鍵字減少一個 for (j = i - 1; j < x.nkey - 1; j++) { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i - 1], lbchild); i--; } } else //否則x.key[i]只有右兄弟 { BPlusNode rbchild; ReadBPlusNode(x.Pointer[i + 1], rbchild); if (rbchild.nkey > MAX_KEY / 2) //情況K { //9、找到關鍵字key,當前節點是內節點,孩子也是內節點,孩子節點不半滿,有右兄弟節點,右兄弟節點半滿 //從右兄弟節點借record child.key[child.nkey] = x.key[i]; child.nkey++; child.Pointer[child.nkey] = rbchild.Pointer[0]; x.key[i] = rbchild.key[0]; for (j = 0; j < rbchild.nkey - 1; j++) { rbchild.key[j] = rbchild.key[j + 1]; rbchild.Pointer[j] = rbchild.Pointer[j + 1]; } rbchild.Pointer[j] = rbchild.Pointer[j + 1]; rbchild.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); WriteBPlusNode(x.Pointer[i + 1], rbchild); } else //情況L { //10、找到關鍵字key,當前節點是內節點,孩子也是內節點,孩子節點不半滿,有右兄弟節點,右兄弟節點不半滿 //向左合併 child.key[child.nkey] = x.key[i]; child.nkey++; for (j = 0; j < rbchild.nkey; j++) //將rbchild節點合併到child節點後 { child.key[child.nkey + j] = rbchild.key[j]; child.Pointer[child.nkey + j] = rbchild.Pointer[j]; } child.Pointer[child.nkey + j] = rbchild.Pointer[j]; child.nkey += rbchild.nkey; //釋放rbchild節點所佔用的空間,x,Pointer[i+1] for (j = i; j < x.nkey - 1; j++) //當前將關鍵字之後的關鍵字左移一位,使該節點的關鍵字數量減一 { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); } } } } delete_BPlus_tree(x.Pointer[i], record); } else //情況G { //11、在當前節點找到關鍵字,當前節點是葉子節點,直接刪除返回 for (j = i; j < x.nkey - 1; j++) { x.key[j] = x.key[j + 1]; x.Pointer[j] = x.Pointer[j + 1]; } x.nkey--; WriteBPlusNode(current, x); //直接返回 return; } } else //在當前節點沒找到關鍵字 { if (!x.isleaf) //沒找到關鍵字,則關鍵字必然包含在以Pointer[i]為根的子樹中 { BPlusNode child; ReadBPlusNode(x.Pointer[i], child); if (!child.isleaf) //如果其孩子節點是內節點 { //遞迴下降,下降之前保證子節點是半滿的 if (child.nkey > MAX_KEY / 2) //情況H { } else //否則孩子節點的關鍵字數量不過半,則將兄弟節點的某一個關鍵字移至孩子 { if (i > 0) //x.key[i]有左兄弟 { BPlusNode lbchild; ReadBPlusNode(x.Pointer[i - 1], lbchild); if (lbchild.nkey > MAX_KEY / 2) //情況I { for (j = child.nkey; j > 0; j--) { child.key[j] = child.key[j - 1]; child.Pointer[j + 1] = child.Pointer[j]; } child.Pointer[1] = child.Pointer[0]; child.key[0] = x.key[i - 1]; child.Pointer[0] = lbchild.Pointer[lbchild.nkey]; child.nkey++; x.key[i - 1] = lbchild.key[lbchild.nkey - 1]; lbchild.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i - 1], lbchild); WriteBPlusNode(x.Pointer[i], child); } else //情況J { lbchild.key[lbchild.nkey] = x.key[i - 1]; //將孩子節點複製到其左兄弟的末尾 lbchild.nkey++; for (j = 0; j < child.nkey; j++) //將child節點拷貝到lbchild節點的末尾, { lbchild.key[lbchild.nkey + j] = child.key[j]; lbchild.Pointer[lbchild.nkey + j] = child.Pointer[j]; } lbchild.Pointer[lbchild.nkey + j] = child.Pointer[j]; lbchild.nkey += child.nkey; //已經將child拷貝到lbchild節點 //釋放child節點的儲存空間,x.Pointer[i] //將找到關鍵字的孩子child與關鍵字左兄弟的孩子lbchild合併後,將該關鍵字前移,使當前節點的關鍵字減少一個 for (j = i - 1; j < x.nkey - 1; j++) { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i - 1], lbchild); i--; } } else //否則x.key[i]只有右兄弟 { BPlusNode rbchild; ReadBPlusNode(x.Pointer[i + 1], rbchild); if (rbchild.nkey > MAX_KEY / 2) //情況K { child.key[child.nkey] = x.key[i]; child.nkey++; child.Pointer[child.nkey] = rbchild.Pointer[0]; x.key[i] = rbchild.key[0]; for (j = 0; j < rbchild.nkey - 1; j++) { rbchild.key[j] = rbchild.key[j + 1]; rbchild.Pointer[j] = rbchild.Pointer[j + 1]; } rbchild.Pointer[j] = rbchild.Pointer[j + 1]; rbchild.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); WriteBPlusNode(x.Pointer[i + 1], rbchild); } else //情況L { child.key[child.nkey] = x.key[i]; child.nkey++; for (j = 0; j < rbchild.nkey; j++) //將rbchild節點合併到child節點後 { child.key[child.nkey + j] = rbchild.key[j]; child.Pointer[child.nkey + j] = rbchild.Pointer[j]; } child.Pointer[child.nkey + j] = rbchild.Pointer[j]; child.nkey += rbchild.nkey; //釋放rbchild節點所佔用的空間,x,Pointer[i+1] for (j = i; j < x.nkey - 1; j++) //當前將關鍵字之後的關鍵字左移一位,使該節點的關鍵字數量減一 { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); } } } } else //否則其孩子節點是外節點 { //12、當前節點是內節點,當前節點中沒找到關鍵字,孩子節點是葉子節點 //則關鍵字必然包含在Pointer[i]指向的葉子節點中,保證當前節點的孩子節點半滿,然後遞迴刪除 if (child.nkey > MAX_KEY / 2) //情況M { } else //否則孩子節點不到半滿 { if (i > 0) //有左兄弟 { BPlusNode lbchild; ReadBPlusNode(x.Pointer[i - 1], lbchild); if (lbchild.nkey > MAX_KEY / 2) //情況N { for (j = child.nkey; j > 0; j--) { child.key[j] = child.key[j - 1]; child.Pointer[j] = child.Pointer[j - 1]; } child.key[0] = x.key[i - 1]; child.Pointer[0] = lbchild.Pointer[lbchild.nkey - 1]; child.nkey++; lbchild.nkey--; x.key[i - 1] = lbchild.key[lbchild.nkey - 1]; WriteBPlusNode(x.Pointer[i - 1], lbchild); WriteBPlusNode(x.Pointer[i], child); WriteBPlusNode(current, x); } else //情況O { for (j = 0; j < child.nkey; j++) //與左兄弟孩子節點合併 { lbchild.key[lbchild.nkey + j] = child.key[j]; lbchild.Pointer[lbchild.nkey + j] = child.Pointer[j]; } lbchild.nkey += child.nkey; lbchild.Pointer[MAX_KEY] = child.Pointer[MAX_KEY]; //釋放child佔用的空間x.Pointer[i] for (j = i - 1; j < x.nkey - 1; j++) { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; WriteBPlusNode(x.Pointer[i - 1], lbchild); WriteBPlusNode(current, x); i--; } } else //否則只有右兄弟 { BPlusNode rbchild; ReadBPlusNode(x.Pointer[i + 1], rbchild); if (rbchild.nkey > MAX_KEY / 2) //情況P { x.key[i] = rbchild.key[0]; child.key[child.nkey] = rbchild.key[0]; child.Pointer[child.nkey] = rbchild.Pointer[0]; child.nkey++; for (j = 0; j < rbchild.nkey - 1; j++) { rbchild.key[j] = rbchild.key[j + 1]; rbchild.Pointer[j] = rbchild.Pointer[j + 1]; } rbchild.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i + 1], rbchild); WriteBPlusNode(x.Pointer[i], child); } else //情況Q { for (j = 0; j < rbchild.nkey; j++) { child.key[child.nkey + j] = rbchild.key[j]; child.Pointer[child.nkey + j] = rbchild.Pointer[j]; } child.nkey += rbchild.nkey; child.Pointer[MAX_KEY] = rbchild.Pointer[MAX_KEY]; //釋放rbchild佔用的空間x.Pointer[i+1] for (j = i; j < x.nkey - 1; j++) { x.key[j] = x.key[j + 1]; x.Pointer[j + 1] = x.Pointer[j + 2]; } x.nkey--; WriteBPlusNode(current, x); WriteBPlusNode(x.Pointer[i], child); } } } } delete_BPlus_tree(x.Pointer[i], record); } //else樹中沒有這個值,所以直接返回就行 } }