B樹的定義一棵B樹T是具有如下性質的有根樹(根為root[T]):1)每個結點x有如下域: a)n[x],當前儲存在結點x中的關鍵字個數; b)n[x]個關鍵字本身,以非降序存放,因此key1 [x]≤key2[x]≤…≤keyn[x][x]; c)leaf[x],是一個布爾值,如果x是葉子結點的話,則它為TRUE,如果x為一個內結點,則它為FALSE。2)每個內結點x還包含n[x]+1個指向其子女的指標c1[x],c2[x],…,cn[x]+1[x]。葉結點沒有子女,故它們的ci域無定義。3)各關鍵字Keyi[x]對儲存在各子樹中的關鍵字範圍加以分隔:如果ki為儲存在以ci[x]為根的子樹中的關鍵字,則 k1≤key1[x]≤k2≤key2[x]≤…≤keyn[x][x]≤kn[x]+14)每個葉結點具有相同的深度,即樹的高度h。5)每一個結點能包含的關鍵字數有一個上界和下界。這些界可用一個稱作B樹的最小度數(即一個結點中可指向的孩子結點個數)的固定整數t≥2來表示。 a)每個非根的結點必須至少有t-1個關鍵字,每個非根的內結點必須至少有t個子女。如果樹是非空的,則根結點至少包含一個關鍵字。 b)每個結點可包含至多2t-1個關鍵字。所以一個內結點至多可有2t個子女。我們說一個結點是滿的,如果它恰好有2t-1個關鍵字。
對B樹的基本操作搜尋B樹搜尋B樹與搜尋二叉樹很相似,只是在每個結點所做的不是二叉或者“兩路”分支決定,二是根據該結點的子女數所做的多路分支決定。更準確的說,在每個內結點x處,要做n[x]+1路的分支決定。
向B樹插入關鍵字向B樹中插入關鍵字,同二叉尋找樹中插入一個關鍵字類似,要尋找插入新關鍵字的葉子結點位置。因為不能把關鍵字插入到一個已滿的葉子結點中,故需要將一個已滿的結點按其中間關鍵字分裂成兩個結點,中間關鍵字被提升到該結點的父結點中。但是這種滿結點的分裂動作會沿著樹向上傳播。為瞭解決這個問題,可以採取這樣一種策略:當沿著樹根往下尋找新關鍵字所屬位置時,就沿途分裂遇到的每個滿結點。因此,每當要分裂一個滿結點時,就能確保它的父結點不是滿的。
從B樹中刪除關鍵字B樹上的刪除操作與插入操作類似,只是稍微複雜點,因為一個關鍵字能夠從任意一個結點中刪除,而不只是葉結點。就要我們必須保證一個結點不會因為插入而變得太大一樣,必須保證一個結點不會因為刪除而變得太小。下面,大致描述一下刪除關鍵字的各種情況:1)如果關鍵字k在結點x中而且x是個葉結點,則從x中刪除k。2)如果關鍵字k在結點x中而且x是個內結點,則作如下操作: a)如果結點x中前於k的子結點y包含至少t個關鍵字,則找出k在以y為根的子樹中的前驅k‘。遞迴的刪除k’,並在x中用k‘取代k。 b)對稱地,如果結點x中位於k之後的子結點z包含至少t個關鍵字,則找出k在以z為根的子樹中的後繼k’。遞迴的刪除k‘,並在x中使用k’取代k。 c)否則,如果y和z都只有t-1個關鍵字,則將k和z中所有關鍵字合并進y,使得x失去k和指向z的指標,這使y包含2t-1個關鍵字。然後,釋放z並將k從y中遞迴刪除。3)如果關鍵字k不在內結點x中,則確定包含k的正確的子樹的根ci[x]。如果ci[x]只有t-1個關鍵字,執行步驟3a或3b以保證我們降至一個包含至少t個關鍵字的結點。然後,通過對x的某個合適的子結點遞迴而結束。 a)如果ci[x]只包含t-1個關鍵字,但它的一個相鄰兄弟結點包含至少t個關鍵字,則將x中的某一個關鍵字降至ci[x]中,將ci[x]的相鄰左兄弟或右兄弟中的某一關鍵字升至x,將該兄弟中合適的子結點指標移到ci[x]中,這樣使得ci[x]增加一個額外的關鍵字。 b)如果ci[x]以及ci[x]的所有相鄰兄弟結點都只包含t-1個關鍵字,則將ci[x]與任意一個兄弟合并,則將x的一個關鍵字移至新合并的結點,使之成為新結點的中間關鍵字。
B樹的c++實現代碼
#pragma oncetemplate<class T>class CBTree{private:static const int M = 3; //B樹的最小度數static const int KEY_MAX = 2*M-1; //節點包含關鍵字的最大個數static const int KEY_MIN = M-1; //非根節點包含關鍵字的最小個數static const int CHILD_MAX = KEY_MAX+1; //孩子節點的最大個數static const int CHILD_MIN = KEY_MIN+1; //孩子節點的最小個數struct Node{bool isLeaf; //是否是葉子節點int keyNum; //節點包含的關鍵字數量T keyValue[KEY_MAX]; //關鍵字的值數組Node *pChild[CHILD_MAX]; //子樹指標數組Node(bool b=true, int n=0):isLeaf(b), keyNum(n){}};public:CBTree(){m_pRoot = NULL; //建立一棵空的B樹}~CBTree(){clear();}bool insert(const T &key) //向B數中插入新結點key{if (contain(key)) //檢查該關鍵字是否已經存在{return false;}else{if (m_pRoot==NULL)//檢查是否為空白樹{m_pRoot = new Node();}if (m_pRoot->keyNum==KEY_MAX) //檢查根節點是否已滿{Node *pNode = new Node(); //建立新的根節點pNode->isLeaf = false;pNode->pChild[0] = m_pRoot;splitChild(pNode, 0, m_pRoot);m_pRoot = pNode; //更新根節點指標}insertNonFull(m_pRoot, key);return true;}}bool remove(const T &key) //從B中刪除結點key{if (!search(m_pRoot, key)) //不存在{return false;}if (m_pRoot->keyNum==1)//特殊情況處理{if (m_pRoot->isLeaf){clear();return true;}else{Node *pChild1 = m_pRoot->pChild[0];Node *pChild2 = m_pRoot->pChild[1];if (pChild1->keyNum==KEY_MIN&&pChild2->keyNum==KEY_MIN){mergeChild(m_pRoot, 0);deleteNode(m_pRoot);m_pRoot = pChild1;}}}recursive_remove(m_pRoot, key);return true;}void display()const //列印樹的關鍵字{displayInConcavo(m_pRoot,KEY_MAX*10);}bool contain(const T &key)const //檢查該key是否存在於B樹中{return search(m_pRoot, key);}void clear() //清空B樹{recursive_clear(m_pRoot);m_pRoot = NULL;}private://刪除樹void recursive_clear(Node *pNode){if (pNode!=NULL){if (!pNode->isLeaf){for(int i=0; i<=pNode->keyNum; ++i)recursive_clear(pNode->pChild[i]);}deleteNode(pNode);}}//刪除節點void deleteNode(Node *&pNode){if (pNode!=NULL){delete pNode;pNode = NULL;}}//尋找關鍵字bool search(Node *pNode, const T &key)const {if (pNode==NULL) //檢測節點指標是否為空白,或該節點是否為葉子節點{return false;}else{int i;for (i=0; i<pNode->keyNum && key>*(pNode->keyValue+i); ++i)//找到使key<=pNode->keyValue[i]成立的最小下標i{}if (i<pNode->keyNum && key==pNode->keyValue[i]){return true;}else{if (pNode->isLeaf) //檢查該節點是否為葉子節點{return false;}else{return search(pNode->pChild[i], key);}}}}//分裂子節點void splitChild(Node *pParent, int nChildIndex, Node *pChild) {//將pChild分裂成pLeftNode和pChild兩個節點Node *pRightNode = new Node();//分裂後的右節點pRightNode->isLeaf = pChild->isLeaf;pRightNode->keyNum = KEY_MIN;int i;for (i=0; i<KEY_MIN; ++i)//拷貝關鍵字的值{pRightNode->keyValue[i] = pChild->keyValue[i+CHILD_MIN];}if (!pChild->isLeaf) //如果不是葉子節點,拷貝孩子節點指標{for (i=0; i<CHILD_MIN; ++i){pRightNode->pChild[i] = pChild->pChild[i+CHILD_MIN];}}pChild->keyNum = KEY_MIN; //更新左子樹的關鍵字個數for (i=pParent->keyNum; i>nChildIndex; --i)//將父節點中的nChildIndex後的所有關鍵字的值和子樹指標向後移一位{pParent->pChild[i+1] = pParent->pChild[i];pParent->keyValue[i] = pParent->keyValue[i-1];}++pParent->keyNum; //更新父節點的關鍵字個數pParent->pChild[nChildIndex+1] = pRightNode; //儲存右子樹指標pParent->keyValue[nChildIndex] = pChild->keyValue[KEY_MIN];//把節點的中間值提到父節點}//在非滿節點中插入關鍵字void insertNonFull(Node *pNode, const T &key){int i = pNode->keyNum; //擷取節點內關鍵字個數if (pNode->isLeaf) //pNode是葉子節點{while (i>0&&key<pNode->keyValue[i-1]) //從後往前,尋找關鍵字的插入位置{pNode->keyValue[i] = pNode->keyValue[i-1]; //向後移位--i;}pNode->keyValue[i] = key; //插入關鍵字的值++pNode->keyNum; //更新節點關鍵字的個數}else//pNode是內節點{while(i>0&&key<pNode->keyValue[i-1]) //從後往前,尋找關鍵字的插入的子樹--i;Node *pChild = pNode->pChild[i]; //目標子樹結點指標 if (pChild->keyNum==KEY_MAX) //子樹節點已滿{splitChild(pNode, i, pChild);//分裂子樹節點if(key>pNode->keyValue[i]) //確定目標子樹pChild = pNode->pChild[i+1];}insertNonFull(pChild, key); //插入關鍵字到目標子樹節點}}//用括弧列印樹void displayInConcavo(Node *pNode, int count)const{if (pNode!=NULL){int i, j;for (i=0; i<pNode->keyNum; ++i){if (!pNode->isLeaf){displayInConcavo(pNode->pChild[i], count-2);}for (j=count; j>=0; --j){cout<<"-";}cout<<pNode->keyValue[i]<<endl;}if (!pNode->isLeaf){displayInConcavo(pNode->pChild[i], count-2);}}}//合并兩個子節點void mergeChild(Node *pParent, int index){Node *pChild1 = pParent->pChild[index];Node *pChild2 = pParent->pChild[index+1];//將pChild2資料合併到pChild1pChild1->keyNum = KEY_MAX;pChild1->keyValue[KEY_MIN] = pParent->keyValue[index];//將父節點index的值下移int i;for (i=0; i<KEY_MIN; ++i){pChild1->keyValue[i+KEY_MIN+1] = pChild2->keyValue[i];}if (!pChild1->isLeaf){for (i=0; i<CHILD_MIN; ++i){pChild1->pChild[i+CHILD_MIN] = pChild2->pChild[i];}}//父節點刪除index的key,index後的往前移一位--pParent->keyNum;for(i=index; i<pParent->keyNum; ++i){pParent->keyValue[i] = pParent->keyValue[i+1];pParent->pChild[i+1] = pParent->pChild[i+2];}deleteNode(pChild2); //刪除pChild2}//遞迴的刪除關鍵字 void recursive_remove(Node *pNode, const T &key){int i=0;while(i<pNode->keyNum&&key>pNode->keyValue[i])++i;if (i<pNode->keyNum&&key==pNode->keyValue[i])//關鍵字key在節點pNode中{if (pNode->isLeaf)//pNode是個分葉節點{//從pNode中刪除k--pNode->keyNum;for (; i<pNode->keyNum; ++i){pNode->keyValue[i] = pNode->keyValue[i+1];}return;}else//pNode是個內節點{Node *pChildPrev = pNode->pChild[i];//節點pNode中前於key的子節點Node *pChildNext = pNode->pChild[i+1];//節點pNode中後於key的子節點if (pChildPrev->keyNum>=CHILD_MIN)//節點pChildPrev中至少包含CHILD_MIN個關鍵字{T prevKey = getPredecessor(pChildPrev); //擷取key的前驅關鍵字 recursive_remove(pChildPrev, prevKey);pNode->keyValue[i] = prevKey; //替換成key的前驅關鍵字return;}else if (pChildNext->keyNum>=CHILD_MIN)//節點pChildNext中至少包含CHILD_MIN個關鍵字{T nextKey = getSuccessor(pChildNext); //擷取key的後繼關鍵字recursive_remove(pChildNext, nextKey);pNode->keyValue[i] = nextKey; //替換成key的後繼關鍵字return;}else//節點pChildPrev和pChildNext中都只包含CHILD_MIN-1個關鍵字{mergeChild(pNode, i);recursive_remove(pChildPrev, key);}}}else//關鍵字key不在節點pNode中{Node *pChildNode = pNode->pChild[i];//包含key的子樹根節點if (pChildNode->keyNum==KEY_MIN)//只有t-1個關鍵字{Node *pLeft = i>0 ? pNode->pChild[i-1] : NULL; //左兄弟節點Node *pRight = i<pNode->keyNum ? pNode->pChild[i+1] : NULL;//右兄弟節點int j;if (pLeft&&pLeft->keyNum>=CHILD_MIN)//左兄弟節點至少有CHILD_MIN個關鍵字{//父節點中i-1的關鍵字下移至pChildNode中for (j=pChildNode->keyNum; j>0; --j) {pChildNode->keyValue[j] = pChildNode->keyValue[j-1];}pChildNode->keyValue[0] = pNode->keyValue[i-1];if (!pLeft->isLeaf) {for (j=pChildNode->keyNum+1; j>0; --j) //pLeft節點中合適的子女指標移植到pChildNode中{pChildNode->pChild[j] = pChildNode->pChild[j-1];}pChildNode->pChild[0] = pLeft->pChild[pLeft->keyNum];}++pChildNode->keyNum;pNode->keyValue[i] = pLeft->keyValue[pLeft->keyNum-1];//pLeft節點中的最大關鍵字上升到pNode中--pLeft->keyNum;}else if (pRight&&pRight->keyNum>=CHILD_MIN)//右兄弟節點至少有CHILD_MIN個關鍵字{//父節點中i的關鍵字下移至pChildNode中pChildNode->keyValue[pChildNode->keyNum] = pNode->keyValue[i];++pChildNode->keyNum;pNode->keyValue[i] = pRight->keyValue[0];//pRight節點中的最小關鍵字上升到pNode中--pRight->keyNum;for (j=0; j<pRight->keyNum; ++j){pRight->keyValue[j] = pRight->keyValue[j+1];}if (!pRight->isLeaf) {pChildNode->pChild[pChildNode->keyNum] = pRight->pChild[0];//pRight節點中合適的子女指標移植到pChildNode中 for (j=0; j<=pRight->keyNum; ++j) {pRight->pChild[j] = pRight->pChild[j+1]; }}}//左右兄弟節點都只包含CHILD_MIN-1個節點else if (pLeft)//與左兄弟合并{mergeChild(pNode, i-1);pChildNode = pLeft;}else if (pRight)//與右兄弟合并{mergeChild(pNode, i);}}recursive_remove(pChildNode, key);}}T getPredecessor(Node *pNode)//找到前驅關鍵字{while (!pNode->isLeaf){pNode = pNode->pChild[pNode->keyNum];}return pNode->keyValue[pNode->keyNum-1];}T getSuccessor(Node *pNode)//找到後繼關鍵字{while (!pNode->isLeaf){pNode = pNode->pChild[0];}return pNode->keyValue[0];}private:Node * m_pRoot; //B樹的根節點};