B+樹的實現
B+樹是B樹的變形,它在B樹的節點裡刪除了關鍵字的指標域,只保留了連結節點的指標域,在葉節點上,這個連結節點的指標域用來儲存關鍵字資訊。B+樹中的關鍵字在樹中可能不止出現一次(最多出現兩次),但一定在葉節點出現一次。相鄰的葉節點用單鏈表形式連線起來,也就是說,找到了最左的葉節點後,所有關鍵字資訊就可以按照遍歷單鏈表的形式遍歷出來。
雖然在B+樹中關鍵字可能會冗餘儲存(最多有兩個相同的關鍵字,分別在內節點和葉節點),而且查詢時必須查到葉節點才算結束,但相對於B樹來說,B+樹刪除了一個關鍵字的指標域,因此節點佔用空間更小,消耗I/O的時間更小,從而平均效率要高於B樹。由於所有關鍵字在葉節點上都有儲存,因此B+樹還能支援複雜度為O(logn)的範圍查詢,這點B樹是不能做到的。
B+樹的插入過程
B+樹的插入過程與B樹相似,都是插到葉節點內。只是在對葉節點分裂時稍有不同,B樹分裂葉節點的過程是:將該節點的中間關鍵字上移至其父親節點,剩下前半部分的關鍵字組成新的左葉節點,後半部分是令一個右葉節點,上移的關鍵字不在葉節點出現了,而B+樹分裂葉節點的過程是在上移中間關鍵字後,還在左葉節點儲存這個關鍵字。
B+樹的查詢過程
B+樹的查詢與B樹相似,但是由於B+樹只有在葉節點時指標域才儲存該關鍵字的資訊,所以必須查詢到葉節點才算結束,而B樹沒有這個限制,和B樹相比,B+樹的查詢比較穩定。
B+樹的刪除過程
在B+樹中刪除關鍵字是操作B+樹中最為複雜的部分,與B樹的刪除過程相比,B+樹不僅要將內節點的關鍵字刪除,而且必須把葉節點的關鍵字也刪除,因此顯得更為繁瑣。B+樹的刪除有很多不同的情況,以下列出了具體每種情況和解決過程,並且每種情況的操作過程都不違背B+樹的性質。為了保證刪除過程沒有回溯,因此在遞迴刪除孩子節點的關鍵字時,需要保證孩子節點的關鍵字超過半滿
If(在當前節點找到關鍵字)
{
If(當前節點是內節點)
{
If(當前找到的關鍵字的孩子是葉節點)
{
If(關鍵字的孩子節點達到半滿)
{
情況A
}
Else(關鍵字的孩子不到半滿)
{
If(關鍵字有左兄弟)
{
If(左兄弟的孩子達到半滿)
{
情況B
}
Else(左兄弟的孩子不到半滿)
{
情況C
}
}
Else(關鍵字只有右兄弟)
{
If(右兄弟的孩子達到半滿)
{
情況D
}
Else(右兄弟的孩子不到半滿)
{
情況E
}
}
}
}
Else(當前關鍵字的孩子是內節點)
{
情況F
}
}
Else(當前節點是葉節點)
{
情況G
}
}
Else(當前節點沒找到關鍵字)
{
If(當前節點是內節點)
{
If(如果關鍵字的孩子是內節點)
{
If(關鍵字的孩子達到半滿)
{
情況H
}
Else(關鍵字的孩子不到半滿)
{
If(關鍵字有左兄弟)
{
If(關鍵字左兄弟的孩子達到半滿)
{
情況I
}
Else(關鍵字左兄弟的孩子不到半滿)
{
情況J
}
}
Else(關鍵字只有右兄弟)
{
If(關鍵字右兄弟的孩子達到半滿)
{
情況K
}
Else(關鍵字右兄弟的孩子不到半滿)
{
情況L
}
}
}
}
Else(關鍵字的孩子是葉節點)
{
If(關鍵字的孩子達到半滿)
{
情況M
}
Else(關鍵字的孩子不到半滿)
{
If(關鍵字有左兄弟節點)
{
If(關鍵字的左兄弟節點的孩子達到半滿)
{
情況N
}
Else(關鍵字的左兄弟節點的孩子沒到半滿)
{
情況O
}
}
Else(關鍵字只有右兄弟節點)
{
If(關鍵字的右兄弟節點的孩子達到半滿)
{
情況P
}
Else(關鍵字的右兄弟節點的孩子沒到半滿)
{
情況Q
}
}
}
}
}
}
情況A:
由於在內節點找到了要刪除的關鍵字,並且該關鍵字的孩子是葉節點,由B+樹每個關鍵字都必須在葉節點出現的性質可知:x.key[i]必然等於其葉節點的孩子的最後一個關鍵字child.key[n-1]。此時用孩子節點的倒數第二個關鍵字替換x.key[i],再遞迴刪除孩子節點中的關鍵字即可。
情況B:
當發生情況B時,由於lbchild和child都是葉節點,因此必然有x.key[i-1]等於lbchild.key[n-1],key[i]等於child.key[0],由於關鍵字key[i]的左兄弟的孩子lbchild達到半滿,因此可以將左兄弟關鍵字x.key[i-1]插入到孩子節點child的頭部,用左兄弟孩子的倒數第二個關鍵字lbchild.key[n-2]替換左兄弟x.key[i-1]後,並將lbchild.key[n-1]在lbchild中刪掉。最後遞迴刪除child中的關鍵字。
情況C :
此時關鍵字的孩子節點和左兄弟孩子都沒達到半滿,此時需要將child合併到lbchild節點後部,釋放孩子節點child所佔用的空間,最後在x節點中刪除左兄弟key[i-1]關鍵字,其後的關鍵字前移一位,最後遞迴刪除lbchild中的關鍵字。
情況D:
此種情況下,關鍵字只有右兄弟,並且右兄弟的孩子節點達到半滿。這時可以用右兄弟的孩子節點中的最小關鍵字rbchild.key[0]替代x.key[i],並將rbchild.key[0]插入到child的末尾,再在richild中刪除rbchild.key[0],最後遞迴刪除child中的關鍵字。
情況E:
如果右兄弟的孩子節點不到半滿,此時需要將右兄弟的孩子節rbchild點合併到child的末尾,過程和情況C類似,只不過順序調換一下。
情況F:
此時在內節點找到要刪除的關鍵字,並且該關鍵字的孩子節點也是內節點,這時可以找到該關鍵字在B+樹葉節點的左兄弟關鍵字,用這個關鍵字替代x.key[i],然後遞迴刪除x.key[i]孩子中的關鍵字,遞迴刪除之前還需要保證孩子節點達到半滿。
情況G:
此時在葉節點找到關鍵字,直接將其在該葉節點刪除即可。
情況H:
直接遞迴刪除以x.key[i]為根的子樹中的關鍵字即可。
情況I:
此時x.key[i]的孩子child不到半滿,但是其左兄弟x.key[i-1]的孩子lbchild達到半滿,因此可以將左兄弟關鍵字x.key[i-1]移至其孩子節點child的首部,child的其他關鍵字和指標向後移一位,將左兄弟孩子節點lbchild的最後一個關鍵字lbchild.key[n-1]移至key[i-1],再將lbchild的最後一個指標域複製到child的第一個指標域,然後遞迴刪除x.key[i]子樹中的關鍵字即可。上述過程結束後,lbchild減少了一個關鍵字,child增加了一個關鍵字。
情況J:
如果x.key[i]的孩子child不到半滿,並且其左兄弟關鍵字的孩子也不到半滿,則將左兄弟關鍵字x.key[i-1]下移至其孩子lbchild的末尾,然後再將child節點拷貝到lbchild的末尾,這個過程結束後再將x.key[i-1]之後的關鍵字和指標前移一位,再遞迴刪除x.key[i-1]孩子中的關鍵字即可。上述過程結束後,當前節點x減少一個關鍵字,child節點被刪除。
情況K:
此時x.key[i]沒有左兄弟,只有右兄弟,並且右兄弟關鍵字x.key[i+1]的孩子rbchild達到半滿。這時將x.key[i]下移至child節點末尾,將rbchild的首指標移至child節點末尾,再將rbchild的首關鍵字拷貝到x.key[i],rbchild中關鍵字和指標前移一位後即調整完畢,然後遞迴刪除x.key[i]孩子中的關鍵字即可。上述操作後,child增加了一個關鍵字,rbchild減少了一個關鍵字。
情況L:
此時x.key[i]沒有左兄弟,只有右兄弟,但是右兄弟的孩子rbchild沒到半滿,這就需要將rbchild合併到child節點中,具體過程是:先將x.key[i]下移至child末尾,再將rbchild中的關鍵字和指標依次拷貝到child的末尾,然後將x,key[i]以後的關鍵字和指標前移一位即可。上述過程結束後,當前節點x減少一個關鍵字,rbchild節點被刪除。
情況M:
直接遞迴刪除child中的關鍵字即可。
情況N:
此時關鍵字有左兄弟,並且左兄弟節點的孩子lbchild達到半滿。此時將lbchild的最後一個關鍵字和指標移至child的首部,child的其他關鍵字和指標後移一位,再將lbchild.key[n-2]複製到左兄弟x.key[i-1]即可。上述過程結束後,lbchild減少一個關鍵字,child增加一個關鍵字。
情況O :
此時關鍵字有左兄弟,並且左兄弟的孩子lbchild沒到半滿,則需要將child與lbchild合併,合併的過程是:直接將child中的關鍵字和指標依次拷貝至lbchild的末尾,然後將x.key[i-1]後的關鍵字前移一位即可。
情況P:
此時關鍵字只有右兄弟,並且右兄弟的孩子達到半滿。這時將rbchild的第一個關鍵字和其指標拷貝至child末尾,並且將x.key[i]替換為rbchild的第一個關鍵字,最後rbchild中的關鍵字和指標前移一位。上述操作結束後,rbchild中減少一個關鍵字,child增加一個關鍵字。
情況Q:
此時關鍵字只有右兄弟,並且右兄弟的孩子沒到半滿,這是需要將child和rbchild合併,合併的過程是:直接將rbchild中的關鍵字和指標依次拷貝至child末尾,然後x.key[i]後的關鍵字前移一位即可。該過程結束後,x中減少一個關鍵字,rbchild被刪除。
/*
執行前需在程式目錄建立名為Bfile的檔案,否則崩潰
*/
#include<iostream>
#include<time.h>
using namespace std;
#define MAX_KEY 5 //B+樹的階,必須為大於3奇數
typedef __int64 KEYTYPE;
typedef unsigned long FILEP;
//B+樹節點的資料結構
typedef struct
{
KEYTYPE key[MAX_KEY] ; //關鍵字域
FILEP Pointer[MAX_KEY+1] ; //指標域
int nkey ; //關鍵字數
bool isleaf ; //是否為葉節點 葉節點:true 否則為false
}BPlusNode;
//插入關鍵字的資料結構
typedef struct
{
KEYTYPE key; //該記錄的關鍵字
FILEP Raddress; //該關鍵字對應記錄的地址
}TRecord;
//儲存查詢結果的資料結構
typedef struct
{
bool exist;
FILEP Baddress; //儲存包含該記錄的B+樹節點地址
FILEP Raddress; //該關鍵字的所指向的記錄地址
}SearchResult;
class BPlusTree
{
FILEP ROOT; //樹根在檔案內的偏移地址
FILE *Bfile; //B+樹檔案的指標
FILE *Rfile; //記錄檔案的指標
public:
FILEP GetBPlusNode() const;
void ReadBPlusNode(const FILEP ,BPlusNode& ) const;
void WriteBPlusNode(const FILEP ,const BPlusNode& );
void Build_BPlus_Tree();
void Insert_BPlus_Tree(TRecord& );
void insert_bplus_tree(FILEP ,TRecord& );
void Split_BPlus_Node(BPlusNode& ,BPlusNode& ,const int );
void Search_BPlus_Tree(TRecord& ,SearchResult& ) const;
void Delete_BPlus_Tree(TRecord& );
void delete_BPlus_tree(FILEP ,TRecord& );
void EnumLeafKey();
BPlusTree();
~BPlusTree();
};
BPlusTree :: BPlusTree()
{
Bfile = fopen("Bfile" ,"rb+" ); //開啟B+樹檔案
}
BPlusTree :: ~BPlusTree()
{
fclose(Bfile );
}
void BPlusTree :: Build_BPlus_Tree() //建立一棵空B+樹
{
ROOT = GetBPlusNode();
BPlusNode r;
r.Pointer[MAX_KEY] = 0 ;
r.nkey = 0;
r.isleaf = true ;
WriteBPlusNode(ROOT ,r );
}
void BPlusTree :: Insert_BPlus_Tree(TRecord &record ) //向B+樹插入關鍵字
{
BPlusNode r;
ReadBPlusNode(ROOT ,r );
if( r.nkey == MAX_KEY )
{
BPlusNode newroot ;
newroot.nkey = 0;
newroot.isleaf = false;
newroot.Pointer[0] = ROOT ;
Split_BPlus_Node(newroot ,r ,0 );
WriteBPlusNode(ROOT ,r );
ROOT = GetBPlusNode();
WriteBPlusNode(ROOT ,newroot );
//分裂根節點
}
insert_bplus_tree(ROOT ,record );
}
void BPlusTree :: insert_bplus_tree(FILEP current ,TRecord &record )
{
BPlusNode x ;
ReadBPlusNode(current ,x );
int i;
for(i = 0 ; i < x.nkey && x.key[i] < record.key ; i ++);
if(i < x.nkey && x.isleaf && x.key[i] == record.key ) //在B+樹葉節點找到了相同關鍵字
{
//關鍵字插入重複
return ;
}
if(!x.isleaf ) //如果不是葉節點
{
BPlusNode y;
ReadBPlusNode(x.Pointer[i] ,y );
if( y.nkey == MAX_KEY ) //如果x的子節點已滿,則這個子節點分裂
{
Split_BPlus_Node(x ,y ,i );
WriteBPlusNode(current ,x );
WriteBPlusNode(x.Pointer[i] ,y );
}
if( record.key <= x.key[i] || i == x.nkey )
{
insert_bplus_tree(x.Pointer[i] ,record );
}
else
{
insert_bplus_tree(x.Pointer[i+1] ,record );
}
}
else //如果是葉節點,則直接將關鍵字插入key陣列中
{
for(int j = x.nkey ; j > i ; j--)
{
x.key[j] = x.key[j-1] ;
x.Pointer[j] = x.Pointer[j-1] ;
}
x.key[i] = record.key ;
x.nkey ++;
//將記錄的地址賦給x.Pointer[i]
x.Pointer[i] = record.Raddress;
WriteBPlusNode(current ,x);
}
}
void BPlusTree :: Split_BPlus_Node(BPlusNode &father ,BPlusNode ¤t ,const int childnum) //分裂滿的B+樹節點
{
int half = MAX_KEY/2 ;
int i ;
for(i = father.nkey ; i > childnum ; i -- )
{
father.key[i] = father.key[i-1] ;
father.Pointer[i+1] = father.Pointer[i];
}
father.nkey ++;
BPlusNode t;
FILEP address = GetBPlusNode();
father.key[childnum] = current.key[half] ;
father.Pointer[childnum + 1] = address;
for( i = half + 1 ; i < MAX_KEY ; i ++ )
{
t.key[i-half-1] = current.key[i] ;
t.Pointer[i-half-1] = current.Pointer[i];
}
t.nkey = MAX_KEY - half - 1;
t.Pointer[t.nkey] = current.Pointer[MAX_KEY];
t.isleaf = current.isleaf ;
current.nkey = half ;
if(current.isleaf ) //如果當前被分裂節點是葉子
{
current.nkey ++;
t.Pointer[MAX_KEY] = current.Pointer[MAX_KEY];
current.Pointer[MAX_KEY] = address ;
}
WriteBPlusNode(address ,t );
}
void BPlusTree :: Search_BPlus_Tree(TRecord &record ,SearchResult &result ) const //在B+樹查詢一個關鍵字
{
int i;
BPlusNode a;
FILEP current = ROOT;
do
{
ReadBPlusNode(current ,a );
for(i = 0 ; i < a.nkey && record.key > a.key[i] ; i ++ );
if( i < a.nkey && a.isleaf && record.key == a.key[i] ) //在B+樹葉節點找到了等值的關鍵字
{
result.Baddress = current;
result.Raddress = a.Pointer[i]; //返回該關鍵字所對應的記錄的地址
result.exist = true;
return ;
}
current = a.Pointer[i] ;
}while(!a.isleaf);
result.exist = false;
}
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
{
x.key[i] = child.key[child.nkey - 2];
child.nkey --;
WriteBPlusNode(current ,x );
WriteBPlusNode(x.Pointer[i] ,child );
return ;
}
else //否則孩子節點的關鍵字數量不過半
{
if(i > 0) //有左兄弟節點
{
BPlusNode lbchild;
ReadBPlusNode(x.Pointer[i-1] ,lbchild );
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
{
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 --;
x.key[i-1] = lbchild.key[lbchild.nkey-2];
WriteBPlusNode(current ,x);
WriteBPlusNode(x.Pointer[i-1] ,lbchild );
i --;
}
}
else //只有右兄弟節點
{
BPlusNode rbchild;
ReadBPlusNode(x.Pointer[i+1] ,rbchild );
if(rbchild.nkey > MAX_KEY/2 ) //情況D
{
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
{
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
{
//找到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
{
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 );
}
}
}
}
delete_BPlus_tree(x.Pointer[i] ,record );
}
else //情況G
{
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 //否則其孩子節點是外節點
{
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 );
}
}
}
void BPlusTree :: Delete_BPlus_Tree(TRecord &record ) //在B+中刪除一個關鍵字
{
delete_BPlus_tree(ROOT ,record );
BPlusNode rootnode;
ReadBPlusNode(ROOT ,rootnode );
if( !rootnode.isleaf && rootnode.nkey == 0 ) //如果刪除關鍵字後根節點不是葉節點,並且關鍵字數量為0時根節點也應該被刪除
{
//釋放ROOT節點佔用的空間
ROOT = rootnode.Pointer[0]; //根節點下移,B+樹高度減1
}
}
void BPlusTree :: EnumLeafKey() //依次列舉B+樹葉節點的所有關鍵字
{
BPlusNode head;
ReadBPlusNode(ROOT ,head );
while(!head.isleaf )
{
ReadBPlusNode(head.Pointer[0] ,head );
}
while(1)
{
for(int i = 0 ; i < head.nkey ; i ++)
printf("%d\n",head.key[i] );
if(head.Pointer[MAX_KEY] == 0 )
break;
ReadBPlusNode(head.Pointer[MAX_KEY] ,head );
}
}
inline FILEP BPlusTree :: GetBPlusNode() const //在磁碟上分配一塊B+樹節點空間
{
fseek(Bfile ,0 ,SEEK_END);
return ftell(Bfile );
}
inline void BPlusTree :: ReadBPlusNode(const FILEP address ,BPlusNode &r ) const //讀取address地址上的一塊B+樹節點
{
fseek(Bfile ,address ,SEEK_SET );
fread((char*)(&r) ,sizeof(BPlusNode) ,1 ,Bfile);
}
inline void BPlusTree :: WriteBPlusNode(const FILEP address ,const BPlusNode &r ) //將一個B+樹節點寫入address地址
{
fseek(Bfile ,address ,SEEK_SET );
fwrite((char*)(&r) ,sizeof(BPlusNode) ,1 ,Bfile);
}
int main()
{
BPlusTree tree;
tree.Build_BPlus_Tree(); //建樹
TRecord record; SearchResult result;
int time1 = clock();
int i;
for(i = 0 ; i < 100000 ; i ++ )
{
record.key = i;
tree.Insert_BPlus_Tree(record );
// printf("%d\n",i );
}
for( i = 99997 ; i > 0; i--)
{
record.key = i;
tree.Delete_BPlus_Tree(record );
tree.Search_BPlus_Tree(record ,result );
if(result.exist )
break;
// printf("%d\n",i);
}
cout<<clock() - time1 <<endl;
system("pause");
tree.EnumLeafKey();
tree.~BPlusTree();
system("pause");
return 0;
}