1. 程式人生 > >B+樹的實現,主要講解刪除操作

B+樹的實現,主要講解刪除操作

關於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樹中沒有這個值,所以直接返回就行

	}


}