五一前就籌劃着寫下這篇文章,可是迫於本身歷來沒有實現過B-樹(若是你們感興趣,我能夠考慮寫一篇B+樹的文章),手中沒有源代碼,另外本身之前對B-樹也是隻知其一;不知其二狀態中,擔憂誤人子弟,在4月30日終於把代碼寫完,今天調完以前的bug以後,那種感受就像在鳥無人煙的大荒漠中走了很久,看到一間有水的屋子,長舒一口氣!好的廢話很少說,下面直接切入正題!html
鏈表,樹,圖是最基本的數據結構了,鏈表有單鏈表、雙鏈表,有環和無環等等;樹有二叉樹、多叉樹,平衡樹、不平衡樹等等;圖有有向、無向圖等等。若是把算法建模當作是建築師建造一座大廈,那麼數據結構就是地基,地基的好壞直接關係到房子能建多高。node
今天咱們要講到的是樹的一種,即B-樹。要說B-樹,就必然要說平衡二叉樹,不然這是不科學的,沒有平衡二叉樹哪來B-樹。它的定義是這樣的:web
平衡二叉樹(Balanced Binary Tree)又被稱爲AVL樹(有別於AVL算法),且具備如下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。算法
圖一數據庫
這個是一顆典型的平衡二叉樹,對根節點50而言,左右子樹的高度都是3,高度差絕對值不超過1;對左子樹而言,根節點是17,可是他的左右兩子樹高度都是2,差的絕對值也不超過1.咱們遞歸的觀察,顯然它符合平衡二叉樹的基本條件。數據結構
若是咱們把右子樹剪掉,他就不是平衡二叉樹了。左邊高度爲3,右邊高度爲0;3-0=3>1.學習
圖二大數據
OK,說到這裏,我想咱們能夠講講B-樹了。B-樹其實能夠理解爲多叉平衡樹!它的定義是這樣的:(其中T通常大於3)ui
1)不只高度平衡,並且全部的葉子節點都在同一層。spa
2)除根節點外其餘每一個節點最少保存(T-1)個關鍵字,至多保存(2T-1)個關鍵字;至少保存(T)個孩子結點,至多(2T)個孩子結點。
3)對於關鍵字K而言,左孩子結點的全部關鍵字都小於K,右孩子結點的全部關鍵字都大於K。在同一個結點上,關鍵字是從小到大依次排列。結點中除葉子節點關鍵字外,均有左右孩子。
隨便提一下,如今有不少B-樹的變形,有些要求每一個節點至少保存2/3(2T-1)個關鍵字等等,也是能夠的。
以下圖就是一顆典型的B-樹:
圖三
OK,對於B-樹,想必你們有了一個直觀的認識。那麼B-樹有什麼用呢?當今社會太浮躁了,沒用的東西留着幹嗎?果斷棄之。顯然B-樹是很是有用的。你們用數據庫進行開發嗎?若是你用,那麼其實你天天都在用B-樹有沒有,由於大部分數據庫的底層實現都是用B-樹。
B-樹對大數據特別有用,特別是查詢狀況多的時候(好比數據庫);由於一旦數據量特別大以後,內存確定不能把全部數據都裝到內存中,咱們惟有經過硬盤存儲,存儲在硬盤中以後,怎樣才能高效的進行查詢呢?咱們仔細觀察B-絕對是一個很好的選擇,由於它最糟糕的時候效率都是logT(N);硬盤的速度相對內存來講是比較慢的,較少一次硬盤讀取對用戶體驗是一個莫大的支持!
老規矩:若是須要所有源代碼請點贊後留下email地址。
其實基於B-的數據庫,數據操做基本能夠概括爲四個部分:
讀取硬盤數據:根據給定頁面Id讀取硬盤數據到內存,而後返回內存相應位置;
硬盤寫操做:給定內存需寫入數據地址信息,寫入到給定頁面id硬盤中。
申請硬盤頁面:若是頁面中id不存在,開拓硬盤頁面,而後返回內存地址。
釋放頁面:給定一個硬盤頁面id,釋放該資源。
OK,咱們瞭解到了B-樹的重要性以後,也就知道了學習B-樹的現實意義了。那咱們如今能夠繼續開扒了。
B-樹應該怎麼設計呢?看了B-樹的定義以後,B-樹數據結構應該包括:1)關鍵字個數,2)指向父節點的指針,3)關鍵字集合,4)孩子結點集合。
根據以上論述,咱們把B-樹的數據結構代碼化以後以下:
1 struct BTreeNode{ 2 int keyNum;//關鍵字個數。
3 BTreeNode* parentNode;//父結點指針
4 vector<NodeValueType> nodeValues; 5 vector<BTreeNode*> childs; 6 BTreeNode(){ 7 keyNum=0; 8 parentNode=NULL; 9 } 10 };
對於B-樹而言最經常使用的就是增刪改查了。改能夠認爲是刪,而後增。因此能夠理解成主要操做有增、刪、查!下面分三部分詳解他們。
B-樹的查詢操做:
B-樹的查詢能夠從根節點開始查找,根節點沒有查到,再查找某一個孩子結點。
由於B-樹結點都有不少孩子節點,查找哪一個孩子結點就顯得特別重要了,總不能亂查吧。它應該是查找第一個比關鍵字大的左結點,若是沒有一個比該關鍵字大的關鍵字,則查找最右孩子。如此遞歸。直到查到葉子結點,若是葉子結點也沒有找到,那麼B-樹中不存在該關鍵字。
好比對於圖三,查找關鍵字G。首先在根節點中查找,根節點只有M一個關鍵字,顯然不符合條件。查找到第一個大於G的關鍵字,這裏M比G大,全部積蓄查找M的左子樹,左子樹的根節點關鍵字有:D、H。一樣第一個比G大的是H。H的左孩子結點關鍵字是F、G。這樣,G被找到了。返回。
圖四
算法具體描述爲:
1 bool SearchBTreeByValue(BTreeNode* q,NodeValueType k){ 2 int i=0; 3 while(i<q->keyNum&&q->nodeValues[i]<=k){ 4 if (q->nodeValues[i]==k){ 5 return true;//相等則返回q
6 } 7 i++; 8 } 9 if (!q->childs.size()){ 10 return false;//沒有找到直接返回NULL
11 } 12 return SearchBTreeByValue(q->childs[i],k); 13 }
B-樹的增長(插入)操做
【插入操做要尋找到葉子節點插入】由於B-樹中最多隻能保存2T-1個關鍵字,若是當前的關鍵字個數已經達到2T-1,還需插入一個的話就須要分裂成兩個結點。以下圖,T=2.若是還需插入一個18.因爲結點X已經有3個關鍵字了。已經full了。若是還須要插入18,就不符合條件了,就須要分裂成兩個結點,同時增長一層。再進行插入。也能夠插入完成後,再分裂。最終仍是要分裂。
若是B-樹沒有滿的話,直接插入就好。
圖五
圖六
1 BTreeNode* InsertNodeToBTreeByValue(BTreeNode* p,NodeValueType theValue){ 2 if (SearchBTreeByValue(p,theValue)){ 3 cout<<theValue<<"已經存在了,騷年!"<<endl; 4 return p; 5 } 6 while(p->childs.size()!=0){//一直找到葉子結點
7 int i=0; 8 while(i<p->keyNum&&p->nodeValues[i]<theValue){ 9 i++; 10 } 11 p=p->childs[i]; 12 } 13 p->nodeValues=InsertKeyToNodeByValue(p->nodeValues,theValue);//插入到當前葉子結點
14 p->keyNum++; 15 return adjustBTree(p);//使得最大不超過maxKey
16 }
B-樹的刪除操做
說實話,刪除操做比增長操做複雜多了,這也是個人程序一直出現bug的地方。假設要在結點x中刪除關鍵字k。
由於刪除操做須要考慮刪除後結點會少於T-1和樹不平衡的狀況。
刪除操做主要考慮三種狀況:
1)x是葉子結點,包含k
2)x是內部結點,包含k
3)x是內部結點,不包含k.
對於1),直接刪除,同時更新keynum;
對於2),考慮k的左右孩子,
如有一個孩子的關鍵字個數大於T-1,若左孩子y,用最右邊的關鍵字和k交換,同時遞歸刪除delete(y,k);
若右孩子(z)數大於T-1,則遞歸刪除delete(z,k);
若左右孩子都等於T-1,則須要合併左孩子,k,右孩子,同時刪除右孩子。
對於3),y=x.child[k];若y的關鍵字個數大於T-1,則遞歸刪除delete(y,k)
若y的關鍵字個數等於T-1,則尋找他的兄弟節點,兄弟節點如有大於T-1個數的,補一個過來。
若沒有的話,就進行和其中兄弟節點合併,再遞歸delete.
1 BTreeNode* DeleteBTreeNode(BTreeNode* p,NodeValueType theValue){ 2 int theValueOrderInNode=GetOrderInNodeByValue(p,theValue);//返回當前序號,theValueOrderInNode 3 //BTreeNode* w=p->parentNode; 4 if (theValueOrderInNode!=-1&&p->childs.size()==0){ 5 p->nodeValues.erase(p->nodeValues.begin()+theValueOrderInNode,p->nodeValues.begin()+theValueOrderInNode+1); 6 p->keyNum--; 7 return GetBTreeRoot(p); 8 }else if (theValueOrderInNode==-1){//該結點中不存在theValue 9 int i=0; 10 while(i<p->keyNum&&p->nodeValues[i]<theValue){ 11 i++;//獲得第I個孩子。 12 } 13 BTreeNode* y=p->childs[i];//找到i+1個孩子結點 14 if (y->keyNum>atLeastKeyNum){ 15 return DeleteBTreeNode(y,theValue); 16 }else if(y->keyNum==atLeastKeyNum){ 17 BTreeNode* leftSlibing=new BTreeNode(),*rightSlibing=new BTreeNode();//左右結點出現啦! 18 leftSlibing=NULL,rightSlibing=NULL; 19 if (i!=0){//有左兄弟 20 leftSlibing=p->childs[i-1]; 21 } 22 if (i!=p->keyNum){//有右兄弟 23 rightSlibing=p->childs[i+1]; 24 } 25 if (leftSlibing!=NULL&&leftSlibing->keyNum>atLeastKeyNum){//左兄弟移動一個到y中 26 y->nodeValues.insert(y->nodeValues.begin(),p->nodeValues[i-1]); 27 y->keyNum++; 28 p->nodeValues[i-1]=leftSlibing->nodeValues[leftSlibing->keyNum-1]; 29 if (y->childs.size()!=0){ 30 y->childs.insert(y->childs.begin(),*(leftSlibing->childs.end()-1)); 31 leftSlibing->childs.erase(leftSlibing->childs.end()-1,leftSlibing->childs.end()); 32 } 33 leftSlibing->nodeValues.erase(leftSlibing->nodeValues.end()-1,leftSlibing->nodeValues.end()); 34 leftSlibing->keyNum--; 35 return DeleteBTreeNode(y,theValue); 36 }else if(rightSlibing!=NULL&&rightSlibing->keyNum>atLeastKeyNum){//右兄弟移動一個到y中 37 y->nodeValues.insert(y->nodeValues.end(),p->nodeValues[i]); 38 y->keyNum++; 39 if (y->childs.size()!=0){ 40 y->childs.insert(y->childs.end(),*(rightSlibing->childs.begin())); 41 rightSlibing->childs.erase(rightSlibing->childs.begin(),rightSlibing->childs.begin()+1); 42 } 43 p->nodeValues[i]=rightSlibing->nodeValues[0]; 44 rightSlibing->nodeValues.erase(rightSlibing->nodeValues.begin(),rightSlibing->nodeValues.begin()+1); 45 rightSlibing->keyNum--; 46 return DeleteBTreeNode(y,theValue); 47 }else{//合併成一個 48 if (leftSlibing!=NULL){//同左合併 49 leftSlibing->nodeValues.insert(leftSlibing->nodeValues.end(),p->nodeValues[i-1]); 50 leftSlibing->keyNum++; 51 p->nodeValues.erase(p->nodeValues.begin()+i-1,p->nodeValues.begin()+i); 52 MoveBTreeNode(leftSlibing,y); 53 p->childs.erase(p->childs.begin()+i-1,p->childs.begin()+i); 54 p->keyNum--; 55 if (p->keyNum==0){ 56 leftSlibing->parentNode=NULL; 57 } 58 return DeleteBTreeNode(leftSlibing,theValue); 59 }else{//同右合併 60 y->nodeValues.insert(y->nodeValues.end(),p->nodeValues[i]); 61 y->keyNum++; 62 p->nodeValues.erase(p->nodeValues.begin()+i,p->nodeValues.begin()+i+1); 63 MoveBTreeNode(y,rightSlibing); 64 p->childs.erase(p->childs.begin()+i+1,p->childs.begin()+i+2); 65 p->keyNum--; 66 if (p->keyNum==0){ 67 y->parentNode=NULL; 68 } 69 return DeleteBTreeNode(y,theValue); 70 } 71 } 72 } 73 }else if (theValueOrderInNode!=-1){//在該結點中找到了。 74 BTreeNode* leftChild=GetPreChildByNodeValue(p,theValue); 75 BTreeNode* rightChild=GetSuccessByNodeValue(p,theValue); 76 if (leftChild!=NULL&&leftChild->keyNum>atLeastKeyNum){//左 77 p->nodeValues[theValueOrderInNode]=leftChild->nodeValues[leftChild->keyNum-1]; 78 leftChild->nodeValues[leftChild->keyNum-1]=theValue; 79 //leftChild->parentNode=p; 80 return DeleteBTreeNode(leftChild,theValue); 81 } 82 else if (rightChild!=NULL&&rightChild->keyNum>atLeastKeyNum){//右 83 p->nodeValues[theValueOrderInNode]=rightChild->nodeValues[0]; 84 rightChild->nodeValues[0]=theValue; 85 //rightChild->parentNode=p; 86 return DeleteBTreeNode(rightChild,theValue); 87 } 88 else if (leftChild!=NULL){//須要合併了 89 leftChild->nodeValues.insert(leftChild->nodeValues.end(),p->nodeValues[theValueOrderInNode]); 90 leftChild->keyNum++; 91 MoveBTreeNode(leftChild,rightChild);//從左移到右 92 p->nodeValues.erase(p->nodeValues.begin()+theValueOrderInNode,p->nodeValues.begin()+theValueOrderInNode+1); 93 p->childs.erase(p->childs.begin()+theValueOrderInNode+1,p->childs.begin()+theValueOrderInNode+2); 94 p->keyNum--; 95 if (p->keyNum==0){ 96 leftChild->parentNode=NULL; 97 } 98 return DeleteBTreeNode(leftChild,theValue); 99 } 100 } 101 return NULL; 102 }
操做數據:用106,103,109,130,145,165,42,60,136,107,108對B-樹進行初始化。而後再進行查找刪除操做。
圖七
參考文獻:http://zgking.com:8080/home/donghui/publications/books/dshandbook_BTree.pdf
http://webdocs.cs.ualberta.ca/~holte/T26/del-b-tree.html
http://www.cs.nott.ac.uk/~nza/G52ADS/btrees2.pdf
版權全部,歡迎轉載,可是轉載請註明出處:瀟一