1. 程式人生 > >B樹的原理分析及實現

B樹的原理分析及實現

    B樹是為磁碟或其他直接存取的輔助裝置而設計的一種平衡搜尋樹。許多資料庫系統使用B樹或B樹的變種來儲存資訊。為何會採用這種樹結構進行設計呢,《演算法導論》18章講得很到位,值得細細品味。

   下面直接開始瞭解B樹的實現細節吧!

    一、B樹的定義

    它與二叉搜尋樹和紅黑樹一樣,把衛星資料和關鍵字都存放在同一個結點中。

    1、每個結點的屬性:

     (1)x.n: 每個結點包含的關鍵字個數;

     (2)x.key1, x.key2, x.key3.....x.keyn, 以非降序排列。

     (3)x.leaf, 表示x結點是否是葉子結點。

     (4)x.ci: 每個結點還包含x.n+1個孩子指標x.c1, x.c2, x.c3......x.cn+1

    2、x中的關鍵字對儲存在各子樹中的關鍵字範圍加以分割。

    3、每個葉結點具有相同的深度,為樹的高度

    4、我們可以為每棵B樹規定一個最小度數:T,那麼每個結點所包含的關鍵字個數的範圍:T-1 ~2T-1, 所包含的孩子結點個數為T ~ 2T。當結點關鍵字個數為2T - 1時,該結點滿。(根結點至少包含1個關鍵數,而其他結點至少包含T - 1個關鍵字。

    5.B樹的高度:h<=logt (n+1)/2。由來演算法導論上很清晰地給出了證明了,容易推導而得。

二、B樹上的基本操作

    1、B_Search

     B樹在每個結點做的是一個多路的分支選擇。對於每個內部結點x, 做的是一個(x.n+1)路的分支選擇。

     B_Tree_Search(x , k)  :  x為指向子樹根結點的指標,從該子樹中搜索 k, 返回k 所在結點 及其在結點中的索引(y,i)

 i =1
 while i<=x.n and k> x.keyi
    i++
 if i<=x.n and k = keyi
   return (x,i)
 if x.leaf
   return NULL
 else
    Disk_Read(x,ci) //讀取x的第i個孩子
    return B_Tree_Search(x.ci, k)

    2、B_Insert

    區別於二叉搜尋樹的插入,並非簡單地建立一個新的葉結點,然後將其插入。而是將新的關鍵字插入到一個已經存在的葉結點上。由於不能將關鍵字插入到一個滿的葉結點中,故引入一個操作,將一個滿的結點y,按照其中間關鍵字y.keyt分裂為各有t-1個關鍵字的結點, 而y.keyt則被提升到其父結點中。

3、B_Delete

   B樹中,刪除某個關鍵字後必須保證該結點的關鍵字個數>=T-1.。因此一個簡單的刪除演算法,當要刪除關鍵字的路徑上結點有最少的關鍵字數時,類似於插入演算法,也可能要向上回溯。

   分析刪除過程:

  (1)、如果關鍵字k在結點x中, 並且x是葉結點,那麼從x中刪除k。

  (2)、如果關鍵字k在結點x中,並且x是內部結點,那麼:

     a、如果x中小於k 的孩子結點y,至少包含t個關鍵字,那麼找出k在以y為根的子樹中的前驅k',將該值替換掉k,並遞迴地刪除k'

     b、如果y有小於t(即 t-1)個關鍵字,但是x中大於k的孩子結點z中,至少包含t個關鍵字,那麼找出k在以z為根的子樹中的後繼k',將該值替換掉k,並遞迴地刪除k'

     c、如果y和z均只有t-1個關鍵字,那麼合併y和z,並將k作為它們的中間關鍵字,同時從x中刪除k, 釋放掉z後,從y中遞迴地刪除k。

  (3)、如果關鍵字不在結點x中。那麼確定x.ci, 以它為根的子樹要包含k。如果x.ci只有t-1個關鍵字,必須執行一些操作,以保證它降至一個至少包含t個關鍵字的結點。然後,通過對x的某個子結點進行遞迴而結束:

     a、如果x.ci中只包含t-1個關鍵字,而它的左相鄰的兄弟x.ci-1至少包含t個關鍵字,那麼,將x中的x.keyi-1降至x.ci中,將x.ci-1中的最大關鍵字升至x中。對稱去處理x.ci的右相鄰結點。

    b、如果x.ci的左右相鄰結點均只包含t-1個關鍵字,那麼將x.ci與一個兄弟合併,將x的一個恰當的關鍵字移至新的結點中,使之成為它們的中間關鍵字。

具體實現程式碼:

// B_Tree.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include <stdio.h>
#include <iostream>

using namespace std;

#define  T 4
typedef struct B_Tree_Node
{
	int n;							//指示該結點的關鍵字個數
	int *keys;						//該結點關鍵字陣列
	bool isLeaf;					//該結點是否是葉子結點
	struct B_Tree_Node **child ;    //該結點的所有孩子結點
	struct B_Tree_Node *p;		    //該結點的父結點
}B_Tree_Node, *p_B_Tree_Node;

B_Tree_Node *alloact_Node()
{
	B_Tree_Node *newNode = new B_Tree_Node;
	newNode->n = 0;
	newNode->isLeaf = true;
	newNode->keys = new int[2*T-1];
	newNode->child = new p_B_Tree_Node[2*T];
	newNode->p = NULL;
	for(int i=0;i<2*T;i++)
		newNode->child[i] = NULL;
	return newNode;
}
//從以當前結點為根結點的子樹中,尋找k所在的結點,
/*
curNode 表示該當前子樹的根結點,k 是要查詢的關鍵字, index用來儲存k在該結點中的索引
首先在當前結點中查詢 ,若在該結點中,則直接返回該結點指標;如果沒有找到,而且該結點是葉子結點,則返回NULL
否則,在它的適當的孩子結點中查詢
*/
B_Tree_Node * searchNode(B_Tree_Node *curNode, int k, int &index)
{
	int i = 0;
	while(i<=curNode->n && k >curNode->keys[i]) 
		i++;
	if(i<curNode->n && k == curNode->keys[i])  //找到了k
	{
		index = i;
		return curNode;
	}
	if(curNode->isLeaf) //如果該結點是葉子結點,則k不存在
		return NULL;
	searchNode(curNode->child[i],k,index);

}

//splitNode_p是被分裂結點的父結點,i是索引為i的孩子為滿,需要被分裂
//被分裂的結點是滿的,那麼它的n = 2*T - 1;被分裂為兩個T-1個關鍵字的子結點,同時它的中間元素被提升到其父節點中
void BTree_Child_Split(B_Tree_Node *splitNode_p, int index_child)
{
	B_Tree_Node *newChild = alloact_Node();
	newChild->n = T-1;
	for(int i = 0;i<T-1;i++)
	{
		newChild->keys[i] = splitNode_p->child[index_child]->keys[T+i];
	}
	splitNode_p->child[index_child]->n = T-1;
	if(splitNode_p->child[index_child]->isLeaf!=true)  //如果它的第i個孩子不是葉子結點,則將它的後T個孩子也送給newChild
	{
		newChild->isLeaf = false;
		for(int i=0;i<T-1;i++)
			newChild->child[i] = splitNode_p->child[i+T];
	}
	//將newChild 新增為splitNode_p的第i+1個孩子結點,將中間關鍵字提升到它中
	for(int i = splitNode_p->n; i>=index_child;i--)
	{
		splitNode_p->child[i+1] = splitNode_p->child[i];
	}
	splitNode_p->n++;
	splitNode_p->child[index_child+1] = newChild;
	for(int i = splitNode_p->n-1; i>=index_child;i--)
	{
		splitNode_p->keys[i+1] = splitNode_p->keys[i];
	}
	splitNode_p->keys[index_child] = splitNode_p->child[index_child]->keys[T-1];
}

void BTree_Insert_NonFull(B_Tree_Node *nonfull, int k)
{
	int i = nonfull->n - 1;
	if(nonfull->isLeaf)
	{
		while(i>=0&&k<nonfull->keys[i])
		{
			nonfull->keys[i+1] = nonfull->keys[i];
			i--;
		}
		i = i+1;
		(nonfull->n)++;
		nonfull->keys[i] = k;
	}
	else
	{
		while(i>=0&&k<nonfull->keys[i])
			i--;
		i = i+1;
		if(nonfull->child[i]->n == 2*T-1)
		{
			BTree_Child_Split(nonfull,i);
			if(k>nonfull->keys[i])
				i = i+1;
		}
		BTree_Insert_NonFull(nonfull->child[i],k);
	}
}

//在B_Tree中加入新的關鍵字,主要由BTree_Insert_NonFull來實現,確保每次插入時所訪問的結點都是非滿結點;
//首先,若根結點為滿,則分裂根結點
void BTree_Insert_Node(p_B_Tree_Node *root,int k)
{
	B_Tree_Node *p = *root;
	if(p->n == 2*T - 1) //如果根結點滿
	{
		B_Tree_Node *newRoot =alloact_Node();
		newRoot->child[0] = (*root);
		newRoot->isLeaf = false;
		*root = newRoot;
		
		BTree_Child_Split(newRoot,0);
		BTree_Insert_NonFull(newRoot,k);
	}
	else 
		BTree_Insert_NonFull(*root,k);
}

void printBFS(B_Tree_Node *t)
{
	if(NULL == t)
		return;

	//輸出當前節點所有關鍵字
	cout << "[";
	for(int i = 0;i < t->n;++i)
	{
		cout << t->keys[i];
		if(t->n - 1 != i)
			cout << " ";
	}
	cout << "]" << endl;

	//遞迴輸出所有子樹
	for(int i = 0;i <= t->n;++i)
		printBFS(t->child[i]);

}

//delete
/*
subNode,當前結點,以它為根的樹中刪除k
1.看k是否存在於以subNode為根的子樹中,不存在則返回;存在則繼續
2.看k是否存在於當前結點:(1)存在且該節點為葉子節點,則直接刪除k;(2)存在且該節點為內部節點,分情況討論;(3)不存在,則找出以它的孩子x.ci為根的子樹中包含k,

Different Case:
1.存在於當前結點,且該結點為內部結點:
case 1:該結點中前於k的子結點y中有至少包含T個元素,則找出k'替換k,並遞迴地刪除k';
case 2:該結點中前於k的子結點z中有隻包含T-1個元素,但大於k的子結點中有至少包含T個元素,同上找出k'替換k,遞迴地刪除k';
case 3:以上兩個子結點均只包含T-1個元素,那麼將k與z子結點中的元素均歸併到y中。再遞迴刪除k
2.k不存在於當前的結點中,存在於以它的孩子x.ci為根的子樹中。
case 1:若x.ci中至少包含有T個元素,遞迴找刪除k
case 2:若x.ci中只有T-1個元素,而x.ci-1或x.ci+1中至少有T個元素,剛將x中合適的元素取出來給x.ci,x.ci-1或x.ci+1中合適的元素取出來給x,遞迴刪除k
case 3:若x.ci-1和x.ci+1中均只有T-1個元素,那麼將x.ci-1與x.ci合併,或另外兩個合併,並將x中合適的元素作為它們的中間關鍵字。

*/
void BTree_delete_key(B_Tree_Node *subNode, int k)
{
	int index = 0;
	B_Tree_Node *deleteNode = NULL;
	if((deleteNode = searchNode(subNode,k,index)) == NULL)
		return;
	int keyIndex = -1;
	for(int i=0;i<subNode->n;i++)
	{
		if(k == subNode->keys[i])
		{
			keyIndex = i;
			break;
		}
	}
	 //如果在當前結點,且當前結點為葉子結點,則直接刪除k
	//OK******************************
	if(keyIndex != -1 && subNode->isLeaf)
	{
		for(int i=keyIndex;i<subNode->n-1;i++)
		{
			subNode->keys[i] = subNode->keys[i+1];
		}
		(subNode->n)--;
	}
	//如果在當前結點中,且當前結點不為葉子結點
	else if(keyIndex != -1 && subNode->isLeaf!= true)
	{
		B_Tree_Node *processorNode = subNode->child[keyIndex];
		B_Tree_Node *succssorNode = subNode->child[keyIndex+1];
		//如果小於k的孩子結點關鍵字數大於T
		if(processorNode->n >= T)
		{
			int k1 = processorNode->keys[processorNode->n-1];
			subNode->keys[keyIndex] = k1;
			BTree_delete_key(processorNode,k1);
		}
		//如果大於k的孩子結點關鍵字數大於T
		else if(succssorNode->n >=T)
		{
			int k1 = succssorNode->keys[0];
			subNode->keys[keyIndex] = k1;
			BTree_delete_key(succssorNode,k1);
		}
		//如果兩個孩子結點關鍵字數均不大於T,則將k與右孩子結點的關鍵字歸併到左孩子中
		else
		{
			for(int j=0;j<T-1;j++)
			{
				//processorNode->keys[T-1] = k;這裡最好不要使用T表示,因為如果是根結點的話,可能它的關鍵字數不為T
				processorNode->keys[processorNode->n] = k;
				processorNode->keys[processorNode->n+1+j] = succssorNode->keys[j];
			}
			
			processorNode->n = 2*T -1 ;
			//將subNode->child[keyIndex+1]的孩子傳給左鄰孩子
			if(!processorNode->isLeaf)
			{
				for(int j=0;j<T;j++)
				{
					processorNode->child[T+j] = succssorNode->child[j];
				}
			}
			//修改subNode中的key值
			for(int j = keyIndex;j<subNode->n-1;j++)
			{
				subNode->keys[j] = subNode->keys[j+1];
			}
			subNode->n = subNode->n - 1;
			delete succssorNode;
			BTree_delete_key(processorNode,k);

			
		}

	}
	else if(keyIndex == -1) //不在當前結點中
	{
		int childIndex = 0;
		B_Tree_Node *deleteNode = NULL;
		//尋找合適的子孩子,以該子孩子為根的樹包含k
		for(int j = 0;j<subNode->n;j++)
		{
			if(k<subNode->keys[j])
			{
				childIndex = j;
				deleteNode = subNode->child[j];
				break;
			}
		}
		//如果該子孩子的關鍵字數小於T,考慮那兩種情況
		if(deleteNode->n <= T-1)
		{
			//deleteNode的左兄弟結點
			B_Tree_Node *LeftNode = subNode->child[childIndex-1];
			//deleteNode的右兄弟結點
			B_Tree_Node *RightNode = subNode->child[childIndex+1];
			//如果左兄弟結點關鍵字數大於T,將父結點中的第childIndex-1個元素送給deleteNode,將Left中的最大元素送給父結點,
			if(childIndex>=1 && LeftNode->n >= T)
			{
				for(int i = deleteNode->n;i>0;i--)
				{
					deleteNode->keys[i] = deleteNode->keys[i-1];
				}
				deleteNode->keys[0] = subNode->keys[childIndex];
				subNode->keys[childIndex] = LeftNode->keys[LeftNode->n - 1];
				(LeftNode->n)--;
				(deleteNode->n)++;
				BTree_delete_key(deleteNode,k);
			}
			//如果右兄弟關鍵字大於T,將父結點中的第childIndex個元素送給deleteNode,將Right中的最小元素送給父結點,
			else if(childIndex<subNode->n && RightNode->n >= T)
			{
				deleteNode->keys[deleteNode->n] = subNode->keys[childIndex];
				subNode->keys[childIndex] = RightNode->keys[0];
				for(int i=0;i<RightNode->n-1;i++)
					RightNode[i] = RightNode[i+1];
				(RightNode->n)--;
				(deleteNode->n)++;
				BTree_delete_key(deleteNode,k);
			}
			//如果左兄弟和右兄弟的關鍵字數均不在於T,則將左兄弟或右兄弟與其合併
			else 
			{
				if(childIndex>=1)//左兄弟存在,合併
				{
					//將keys合併
					for(int i=0;i<deleteNode->n;i++)
					{
						LeftNode->keys[LeftNode->n+i] = deleteNode->keys[i];
					}
					//如果非葉子結點,則將葉子也合併
					if(!deleteNode->isLeaf)
					{
						for(int i=0;i<deleteNode->n+1;i++)
						{
							LeftNode->child[LeftNode->n+1+i] = deleteNode->child[i];
						}

					}
					LeftNode->n = LeftNode->n + deleteNode->n;

					//調整subNode的子節點
					for(int i = childIndex;i<subNode->n;i++)
					{
						subNode->child[i] = subNode->child[i+1];
					}
					BTree_delete_key(LeftNode,k);
				}
				else //合併它和右兄弟
				{
					//將keys合併
					for(int i=0;i<RightNode->n;i++)
					{
						deleteNode->keys[i+deleteNode->n] = RightNode->keys[i];
					}
					//如果非葉子結點,則將葉子合併
					if(!deleteNode->isLeaf)
					{
						for(int i = 0;i<RightNode->n+1;i++)
						{
							deleteNode->child[deleteNode->n + 1 + i] = RightNode->child[i];
						}
					}
					deleteNode->n = deleteNode->n + RightNode->n;

					//調整subNode的子節點
					for(int i = childIndex+1;i<subNode->n;i++)
					{
						subNode->child[i] = subNode->child[i+1];
					}
					BTree_delete_key(deleteNode,k);
				}
			}
			
		}
		BTree_delete_key(deleteNode,k);
	}
}

void createBTree(p_B_Tree_Node *root)
{
	int a[] = {12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17};
	for(int i = 0;i<20;i++)
	{
		BTree_Insert_Node(root,a[i]);
		printBFS(*root);
	}
}


int _tmain(int argc, _TCHAR* argv[])
{
	B_Tree_Node *root = alloact_Node();
	createBTree(&root);
	cout<<"B_Tree after delete 12:"<<endl;
	BTree_delete_key(root,12);
	printBFS(root);
	cout<<"B_Tree after delete 1:"<<endl;
	BTree_delete_key(root,1);
	printBFS(root);
	cout<<"B_Tree after delete 9:"<<endl;
	BTree_delete_key(root,9);
	printBFS(root);
	cout<<"B_Tree after delete 2:"<<endl;
	BTree_delete_key(root,2);
	printBFS(root);

	return 0;
}