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]+1
4)每個葉結點具有相同的深度,即樹的高度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 once
template<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數據合併到pChild1
pChild1->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樹的根節點
};