B樹與B+詳解


承接上篇SQLite采用B樹結構使得SQLite內存占用資源較少,本篇將講述B樹的具體操作(建樹,插入,刪除等操作)。在看博客時,建議拿支筆和紙,一點一點操作,畢竟知識是自己的,自己也要消化的。本篇通讀下來,大約需要25-35分鍾,關鍵掌握B樹的具體操作思想,歡迎大家指正。

 

一、前言

動態查找樹主要包括:二叉查找樹,平衡二叉樹,紅黑樹,B樹,B-樹,查找的時間復雜度就為O(log2N),通過對數就可以發現降低樹的深度就會提高查找效率。在大數據存儲過程,大量的數據會存儲到外存磁盤,外存磁盤中讀取與寫入某數據的時候,首先定位到磁盤中的某一塊,這就有個問題:如何才能有效的查找磁盤中的數據呢,這就需要一種高效的外存數據結構,也就引出了下面的課題。

B樹為了存儲設備或者磁盤而設計的一種平衡查找樹,與紅黑樹類似(拓展會講)。

拓展:

B樹與紅黑樹的

不同在於:B樹的節點可以有很多子女,從幾個到幾萬個不等,

相同:一顆含有n個節點的B樹高度和紅黑樹是一樣的,都是O(lgn)。

 

二、定義

 

1.B樹

(1)一棵m階的B樹,特性如下:

利用書面的定義(參考書籍-《數據結構》)

1)樹中的每個結點最多含有m個孩子;

2)除了根結點和葉子結點,其他結點至少有[ceil(m / 2)(代表是取上限的函數)]個孩子;

3)若根結點不是葉子結點時,則至少有兩個孩子(除了沒有孩子的根結點)

4)所有的葉子結點都出現在同一層中,葉子結點不包含任何關鍵字信息;

 

(2)B樹的類型與節點定義

#define m  1024
struct BTNode;
typedef struct BTNode *PBTNode;
struct BTNode {
    int keyNum;//實際關鍵字個數,keyNum < m
    PBTNode parent;//指向父親節點
    PBTNode *ptr;
    keyType *key;//關鍵字向量
    
}

typedef struct BTNode *BTree;
typedef BTree *PBTree;

 

2.B+樹

B+樹可以說是B樹的一種變形,它把數據都存儲在葉結點,而內部結點只存關鍵字和孩子指針,因此簡化了內部結點的分支因子,B+樹遍歷也更高效,其中B+樹只需所有葉子節點串成鏈表這樣就可以從頭到尾遍歷,其中內部結點是並不存儲信息,而是存儲葉子結點的最小值作為索引,下面將講述到。

定義:參考數據《數據結構》與百度百科

B+樹用於數據庫和文件系統中,NTFS等都使用B+樹作為數據索引,

1)有n棵子樹的結點含有n個關鍵字,每個關鍵字都不會保存數據,只會用來索引,並且所有數據都會保存在葉子結點;

2)所有的葉子結點包含所有關鍵字信息以及指向關鍵字記錄的指針,關鍵字自小到大順序連接;

參考下圖(來自百度百科)

 

三、問答

1.為什么說B+樹比B樹更適合做操作系統的數據庫索引和文件索引?

(1)B+樹的磁盤讀寫的代價更低

B+樹內部結點沒有指向關鍵字具體信息的指針,這樣內部結點相對B樹更小。

(2)B+樹的查詢更加的穩定

因為非終端結點並不是最終指向文件內容的結點,僅僅是作為葉子結點中關鍵字的索引。這樣所有的關鍵字的查找都會走一條從根結點到葉子結點的路徑。所有的關鍵字查詢長度都是相同的,查詢效率相當。

 

四、B樹與B+樹操作(建議大家找張紙,跟着一起,畢竟知識是自己的)

1.B樹

1.1 B樹的插入

B樹的插入是指插入一條記錄,如果B樹已存在需要插入的鍵值時,用新的值替換舊的值;若B樹不存在這個值時,則是在葉子結點進行插入操作。

對高度為h的m階B樹,新結點一般插第h層。通過檢索可以確定關鍵碼應插入的位置,

1)若該結點中關鍵碼個數小於等於m-1,則直接插入就可

2)若該結點中關鍵碼個數等於m-1,則將引起結點的分裂,以中間的關鍵碼為界將結點一分為二,產生了一個新的結點,並將中間關鍵碼插入到父結點中;

重復上述過程,最壞情況一直分裂到根結點, 建立一個新的根結點,整個B樹就增加一層。

舉例如下:

》〉》〉下面以5階B樹舉例,根據B樹的定義,結點最多有4個值,最少有2個值。

a)在空樹插入39,此時就有一個值,根結點也是葉子結點

b)繼續插入22,97和41值,根結點變為4個值,符合要求

c)插入53值

插入之后發現超過結點最多只有4個值,所以要以中間值進行分開,分開后當前結點要指向父結點,分裂之后,發現符合要求

d)插入13,21,40,同樣造成分裂,

e)緊接着插入30,27,33,36,24,34,35

f)將26再次插入進去

發現有5個值,超過B樹的定義,需要以27為中心分裂,27進軍父結點

發現父結點也超過4個,再次分裂

g)最后插入17,28,29,31,32的記錄

 

1.2 B樹的刪除

B樹刪除:首先要查找該值是否在B樹中存在,如果存在,判斷該元素是否存在左右孩子結點,如果有,則上移孩子結點中的相近結點(左孩子最右邊的結點或者有孩子最左邊的結點)到父結點中,然后根據移動之后的情況;如果沒有,進行直接刪除;如果不存在對應的值,則刪除失敗。

1)如果當前要刪除的值位於非葉子結點,則用后繼值覆蓋要刪除的值,再用后繼值所在的分支刪除該后繼值。(該后繼值必須位於葉子結點上)

2)該結點值個數不小於Math.ceil(m/2)-1(取上線函數),結束刪除操作,否則下一步

3)如果兄弟結點值個數大於Math.ceil(m/2)-1,則父結點中下移到該結點,兄弟的一個值上移,刪除操作結束。

將父結點的key下移與當前的結點和他的兄弟姐妹結點key合並,形成一個新的結點,

有些結點可能有左兄弟,也有右兄弟,我們可以任意選擇一個兄弟結點即可。

 

》〉》〉下面以5階B樹舉例進行刪除,根據B樹的定義,結點最多有4個值,最少有2個值。

a)原始狀態

b)在上面的B樹刪除21,刪除之后結點個數大於等於2,所以刪除結束

c)刪除27之后為

27處於非葉子結點,用27的后繼替換。也即是28替換27,然后在右孩子結點刪除28,如上。

發現刪除,當前葉子結點的記錄的個數已經小於2,而兄弟結點中有3個記錄我們可以從兄弟結點中借取一個key,父結點中的28就下移,兄弟結點中的26就上移,刪除結束,結果如下

d)刪除32

刪除之后發現,當前結點中有key,而兄弟都有兩個key,所以只能讓父結點的30下移到和孩子一起合並,成為新的結點,並指向父結點,經拆封發現符合要求

 

2.B+樹

2.1 B+樹的插入

B+樹插入:

1)若為空樹,直接插入,此時也就是根結點

2)對於葉子結點:根據key找葉子結點,對葉子結點進行插入操作。插入后,如果當前結點key的個數不大於m-1,則插入就結束。反之將這個葉子結點分成左右兩個葉子結點進行操作,左葉子結點包含了前m/2個記錄,右結點包含剩下的記錄key,將第m/2+1個記錄的key進位到父結點中(父結點必須是索引類型結點),進位到父結點中的key左孩子指針向左結點,右孩子指針向右結點。

3)針對索引結點:如果當前結點key的個數小於等於m-1,插入結束。反之將這個索引類型結點分成兩個索引結點,左索引結點包含前(m-1)/2個數據,右結點包含m-(m-1)/2個數據,然后將第m/2個key父結點中,進位到父結點的key左孩子指向左結點, 父結點的key右孩子指向右結點。

》〉》〉下面以5階B+樹舉例進行插入,根據B+樹的定義,結點最多有4個值,最少有2個值。

a)空樹插入5,8,10,15

b)插入16

超過了最大值4,所以分裂,以中間為准

c)插入17,18

結點的關鍵字等於5,大於4,進行分裂。

符合條件,插入完成。

 

2.2 B+樹刪除

 

》〉》〉下面以5階B+樹舉例進行刪除,根據B+樹的定義,結點最多有4個值,最少有2個值。

下面是初始狀態

a)刪除22,刪除后個數為2,刪除結束

b)刪除15,結果如下:

刪除之后,只有一個值,而兄弟有三個值,所以從兄弟結點借一個關鍵字,並更新索引結點

大家可以考慮刪除7.我在這里直接給出結果

 

以上就是B樹和B+樹的操作,建議大家拿支筆操作一下,畢竟提高能力是沒有錯的。

 

五、代碼實現

//測試程序1  
#include <iostream>  
#include <cstdlib>  
#include <ctime>  
#include "BTree.h"  
using namespace std;  
  
int main()  
{     
    char iKey[] = {'C','N','G','A','H','E','K','Q','M','F','W','L','T','Z','D','P','R','X','Y','S'};  
    char dKey[] = {'C','N','G','A','H','E','K','Q','M','F','W','L','T','Z','D','P','R','X','Y','S'};  
    int iSize = sizeof(iKey)/sizeof(char);  
    int dSize = sizeof(dKey)/sizeof(char);  
  
    int i;  
    BTree<char> btree(5, NULL);     
    cout<<"----------插入測試----------"<<endl;  
    for(i = 0; i < iSize; i++) //插入測試  
    {  
        cout<<"插入"<<iKey[i]<<"以后"<<endl;  
        btree.Insert(iKey[i]);  
        btree.PrintBTree();  
    }  
    cout<<"----------刪除測試----------"<<endl;  
    for(i = 0; i < dSize; i++) //刪除測試  
    {  
        cout<<"刪除"<<dKey[i]<<"以后"<<endl;  
        btree.Delete(dKey[i]);  
        btree.PrintBTree();  
    }  
    return 0;  
}
//測試程序2  
#include <iostream>  
#include <cstdlib>  
#include <ctime>  
#include "BTree.h"  
using namespace std;  
  
int main()  
{     
    srand((int)time(0));  
    const int iSize = 100000;  //插入次數   
    const int dSize = 100000;  //刪除次數  
    const int num = 100;       //測試組數  
    int *iKey = new int[iSize];  
    int *dKey = new int[dSize];   
    int i, j;  
    for(j = 0; j < num; j++)  //測試組數,每次測試都是插入iSize次,刪除dSize次  
    {  
        for(i = 0; i < iSize; i++)  //插入數據生成  
            iKey[i] = rand()%iSize;  
        for(i = 0; i < dSize; i++)  
            dKey[i] = rand()%iSize; //刪除數據生成  
  
        int m = rand()%400 + 3;  //隨機生成3階到402階  
        BTree<int> btree(m, NULL);      
        cout<<"----------第"<<j<<"組插入測試----------"<<endl;  
        for(i = 0; i < iSize; i++) //插入測試  
            btree.Insert(iKey[i]);  
        cout<<""<<j<<"組插入測試成功,為"<<m<<"階B樹"<<endl;  
        cout<<"----------第"<<j<<"組刪除測試----------"<<endl;  
        for(i = 0; i < dSize; i++) //刪除測試  
            btree.Delete(dKey[i]);  
        cout<<""<<j<<"組刪除測試成功,為"<<m<<"階B樹"<<endl<<endl;  
    }  
    delete [] iKey;  
    delete [] dKey;  
    return 0;  
}  
  1 //BTree.h文件,由於使用了模板所以沒法將聲明與實現分離  
  2 #pragma once  
  3 #include <queue>  
  4 using namespace std;  
  5   
  6 //B樹的結點定義  
  7 template <typename T>  
  8 struct BTreeNode  
  9 {  
 10     int num;               //關鍵字個數    
 11     T *K;                  //指向關鍵字數組  
 12     BTreeNode<T> *parent;  //指向父親結點  
 13     BTreeNode<T> **A;      //指向孩子結點數組的指針  
 14     BTreeNode(int n, int m, BTreeNode<T>  *p)  
 15     {  
 16         num = n;  
 17         parent = p;  
 18         K = new T[m+1];           //最多有m-1個關鍵字,K0不用,Km用來當哨兵  
 19         A = new BTreeNode *[m+1]; //最多有m個分支,Am用來當哨兵  
 20         for(int i = 0; i <= m; i++)  
 21             A[i] = NULL;  
 22     }  
 23     ~BTreeNode()  
 24     {  
 25         delete [] K; K = NULL;    
 26         delete [] A; A = NULL;  
 27     }  
 28 };  
 29   
 30 //搜索結果的三元組定義  
 31 template <typename T>  
 32 struct Triple  
 33 {  
 34     BTreeNode<T> * node;  //關鍵字所在結點  
 35     int i;                //關鍵字下標位置  
 36     bool tag;             //搜索是否成功  
 37     Triple(BTreeNode<T> *nd, int pos, bool t)   
 38     { node = nd; i = pos; tag = t;}  
 39 };  
 40   
 41 //B樹定義  
 42 template <typename T>  
 43 class BTree  
 44 {  
 45 public:  
 46     BTree();  
 47     BTree(int m , BTreeNode<T> * root);  
 48     ~BTree();  
 49     Triple<T> Search(const T& x); //搜索核心函數  
 50     bool Insert(const T& x);      //插入核心函數  
 51     bool Delete(const T& x);      //刪除核心函數  
 52     void InsertKey(BTreeNode<T> *p, T k, BTreeNode<T> *a, int i);    //插入一個二元組(K,A)  
 53     void SpliteNode(BTreeNode<T> *p, T *k, BTreeNode<T> **a, int i); //分裂結點  
 54     void RightAdjust(BTreeNode<T> *p, BTreeNode<T> *q, int i);  //從右子女取關鍵字  
 55     void LeftAdjust(BTreeNode<T> *p, BTreeNode<T> *q, int i);   //從左子女取關鍵字  
 56     void LeftCompress(BTreeNode<T> *p, int i);  //往左移動1個位置  
 57     void RightCompress(BTreeNode<T> *p, int i); //往右移動1個位置  
 58     void MergeNode(BTreeNode<T> *p, BTreeNode<T> *q, BTreeNode<T> *pR, int i); //合並兩個結點  
 59     void PrintBTree(); //打印B樹  
 60 private:  
 61     int m_m;                //路數,即最大子樹棵數  
 62     BTreeNode<T> *m_pRoot;  //B樹的根結點  
 63 };  
 64 template<typename T>  
 65 BTree<T>::BTree() //默認構造函數  
 66 {  
 67     m_m = 5;         //默認是5階  
 68     m_pRoot = NULL;  //根結點初始為空  
 69 }  
 70 template<typename T>  
 71 BTree<T>::BTree(int m , BTreeNode<T> * root)  
 72 {  
 73     m_m = m;        
 74     m_pRoot = root;  
 75 }  
 76 template<typename T>  
 77 BTree<T>::~BTree() //釋放所有的空間  
 78 {  
 79     if(m_pRoot != NULL)  
 80     {  
 81         queue<BTreeNode<T> *> nodeQueue; //利用隊列,按層次遍歷B樹  
 82         nodeQueue.push(m_pRoot);         //放入根結點  
 83         while(nodeQueue.size())  
 84         {  
 85             BTreeNode<T> * p = nodeQueue.front();  
 86             if(p->A[0] != NULL) //不是葉結點,需考慮子女結點的刪除  
 87             {  
 88                 for(int i = 0; i <= p->num; i++)  
 89                     nodeQueue.push(p->A[i]);  
 90             }  
 91             nodeQueue.pop();  
 92             delete p;  
 93             p = NULL;  
 94         }  
 95     }  
 96 }  
 97 //函數功能: 查找關鍵字x是否在B樹中  
 98 //函數參數: x為查找的關鍵字  
 99 //返回值:   一個Triple對象(node, i, tag),tag=true表示x等於結點r中的Ki;tag=false表示x不在樹中,r是最后一個被搜索的結點  
100 template <typename T>  
101 Triple<T> BTree<T>::Search(const T &x)  
102 {  
103     int i = 0;  //下標  
104     BTreeNode<T> *p = m_pRoot, *q = NULL;  //用來保存當前結點和它的父結點  
105   
106     while(p != NULL) //一直檢查到葉結點  
107     {  
108         //n, A0,(K1, A1), (K2, A2), ... (Kn, An)  
109         //確定i,使得Ki <= x < Ki+1,K[0]不放數據  
110         //下面這條語句當然也可以寫成 for(i = 1; i <= n && x >= p->K[i]; i++)  
111         //但是為了與Ki <= x < Ki+1這個關系式統一,采用了下述寫法,觀察后面的程序,發現這樣寫還避免了下標溢出的判斷   
112         int n = p->num;   //當前結點的關鍵字個數     
113         for(i = 0; i < n && x >= p->K[i+1]; i++)  //可以改進一下,用二分查找  
114             ;  
115         if(x == p->K[i]) //是否已找到,不用判斷下標,i最大為n   
116             return Triple<T>(p, i, true);  
117         q = p;  
118         p = p->A[i];     //搜索下一層,Ki與Ki+1中間的指針  
119     }  
120     return Triple<T>(q, i, false); //x不在樹中,找到了可以插入的結點位置  
121 }  
122 //函數功能: 插入關鍵字x到B樹中  
123 //函數參數: x為插入的關鍵字  
124 //返回值:   插入是否成功  
125 template <typename T>  
126 bool BTree<T>::Insert(const T &x)  
127 {  
128     if(m_pRoot == NULL) //空樹  
129     {  
130         m_pRoot = new BTreeNode<T>(1, m_m, NULL);  //新的根含有1個關鍵字  
131         m_pRoot->K[1] = x;    //根的關鍵字  
132         return true;  
133     }  
134   
135     Triple<T> triple = Search(x);     //檢查是否已存在  
136     if(triple.tag == true) //x已在B樹中  
137         return false;  
138   
139     BTreeNode<T> *p = triple.node, *q; //結點地址  
140     //構造插入的兩元組(k,a) 其中k為關鍵字,a為右鄰指針  
141     BTreeNode<T> *a = NULL;  
142     T k  = x;  
143     int i = triple.i;   
144   
145     while(1) //插入過程  
146     {  
147         if(p->num < m_m-1) //關鍵字個數未到達上限,可以直接插入  
148         {  
149             InsertKey(p, k, a, i); //(k, a)插入到位置(Ki, Ai)后面  
150             return true;  
151         }  
152         SpliteNode(p, &k, &a, i); //將p結點分裂成兩個結點,一個結點仍為p,另外一個變為兩元組(k,a),以便插入到父結點  
153         if(p->parent != NULL)     //父結點不為空  
154         {  
155             q = p->parent; //獲得父結點  
156             for(i = 0; i < q->num && x >= q->K[i+1]; i++) //確定新的插入位置i  
157                 ;  
158             p = q;   //進入上一層  
159         }  
160         else  
161         {  
162             //已經到達了根,需要新建一個結點  
163             m_pRoot = new BTreeNode<T>(1, m_m, NULL);  //新的根含有1個關鍵字  
164             m_pRoot->K[1] = k; //新根的關鍵字  
165             m_pRoot->A[0] = p; //左指針  
166             m_pRoot->A[1] = a; //右指針  
167             p->parent = a->parent = m_pRoot; //更新左右指針的父結點  
168             return true;  
169         }  
170     }  
171 }  
172 //函數功能: 插入關鍵字x到B樹中,這是實際的插入函數  
173 //函數參數: p指向插入關鍵字所在結點,k為插入的關鍵字,a為關鍵字的右鄰,i為插入位置  
174 //返回值:   無  
175 template <typename T>  
176 void BTree<T>::InsertKey(BTreeNode<T> *p, T k, BTreeNode<T> *a, int i)  
177 {  
178     for(int j = p->num; j > i; j--) //將K[i],A[i]以后的元素都往后移一個位置  
179     {  
180         p->K[j + 1] = p->K[j];  
181         p->A[j + 1] = p->A[j];  
182     }  
183     p->num++;        //結點的關鍵字個數加1  
184     p->K[i + 1] = k; //插入兩元組在K[i],A[i]以后  
185     p->A[i + 1] = a;  
186     if(a != NULL)    //若為為空,需更新父結點指針  
187         a->parent = p;  
188 }  
189 //函數功能: 分裂結點  
190 //函數參數: p指向要分裂的結點,k指向插入的關鍵字,a指向關鍵字的右鄰,i為插入位置  
191 //返回值:   無  
192 template <typename T>  
193 void BTree<T>::SpliteNode(BTreeNode<T> *p, T *k, BTreeNode<T> **a, int i)  
194 {  
195     InsertKey(p, *k, *a, i); //先插了再說  
196     int mid = (m_m + 1)/2;   //[ceil(m/2)]  
197     int size = (m_m & 1)? mid : mid + 1; //奇偶性決定了分裂時拷貝的關鍵字個數  
198   
199     BTreeNode<T> *q = new BTreeNode<T>(0, m_m, p->parent); //新結點  
200     //將p的K[mid+1...m]和A[mid..m]移到q的K[1...mid-1]和A[0...mid-1]  
201     q->A[0] = p->A[mid];  
202     for(int j = 1; j < size; j++)  
203     {  
204         q->K[j] = p->K[mid + j];    
205         q->A[j] = p->A[mid + j];    
206     }  
207     //修改q中的子女的父結點為q,這里很重要,因為這些子女原來的父結點為p  
208     if(q->A[0] != NULL)  
209     {     
210         for(int j = 0; j < size; j++)  
211             q->A[j]->parent = q;  
212     }  
213     //更新結點的關鍵字個數  
214     q->num = m_m - mid;  //結點q:m –[ceil(m/2)], A[ceil(m/2)],(K [ceil(m/2)]+1, A [ceil(m/2)]+1), …, (Km, Am)  
215     p->num = mid - 1;    //結點p:[ceil(m/2)]–1, A0, (K1, A1), (K2,A2), …, (K[ceil(m/2)]–1, A[ceil(m/2)]–1)   
216     //構建新的兩元組(k,a)  
217     *k = p->K[mid];  
218     *a = q;  
219 }  
220   
221 //函數功能: 刪除關鍵字x  
222 //函數參數: x為要刪除的關鍵字  
223 //返回值:   刪除是否成功  
224 template <typename T>  
225 bool BTree<T>::Delete(const T& x)  
226 {  
227     Triple<T> triple = Search(x); //檢查是否已存在  
228     if(triple.tag == false)       //x不在B樹中  
229         return false;  
230     BTreeNode<T> *p = triple.node, *q; //要刪除的關鍵字所在結點  
231     int i = triple.i;  
232   
233     if(p->A[i] != NULL) //非葉結點  
234     {  
235         q = p->A[i];    //找右子樹的最小關鍵碼  
236         while(q->A[0] != NULL)  
237             q = q->A[0];  
238         p->K[i] = q->K[1];   //用葉結點替換  
239         LeftCompress(q, 1);  //刪除K[1],其實只是用后面的結點覆蓋一下即可  
240         p = q;               //轉換為葉結點的刪除  
241     }  
242     else  
243         LeftCompress(p, i);  //葉結點直接刪除,其實只是用后面的結點覆蓋一下即可  
244   
245     int mid = (m_m + 1) / 2; //求[ceil(m/2)]  
246     //下面開始調整  
247     while(1)  
248     {  
249         if(p == m_pRoot || p->num >= mid-1) //情形1和情形2  
250             break;  
251         else  
252         {  
253             q = p->parent; //父親結點  
254             for(i = 0; i <= q->num && q->A[i] != p; i++) //找到p在父結點中的位置Ai  
255                 ;  
256             if(i == 0)     //p為最左指針  
257                 RightAdjust(p, q, i);  //結點p、父結點q、p的右兄弟結點進行旋轉調整  
258             else  
259                 LeftAdjust(p, q, i);   //結點p、父結點q、p的左兄弟結點進行旋轉調整  
260             p = q;         //向上調整  
261         }  
262     }  
263     if(m_pRoot->num == 0) //一顆空樹  
264     {  
265         p = m_pRoot->A[0];  
266         delete m_pRoot;  
267         m_pRoot = p;  
268         if(m_pRoot != NULL)  
269             m_pRoot->parent = NULL;  
270     }  
271     return true;  
272 }  
273 //函數功能: 通過右子女調整,如果右子女有多余結點,從右子女取一個關鍵字  
274 //函數參數: p指向被刪除的關鍵字所在結點,q指向父結點,i為p在q中的位置  
275 //返回值:   無  
276 template <typename T>  
277 void BTree<T>::RightAdjust(BTreeNode<T> *p, BTreeNode<T> *q, int i)  
278 {  
279     BTreeNode<T> *pR = q->A[i+1];  //p的右兄弟  
280     if(pR->num >= (m_m+1)/2)       //情形3,兄弟有足夠多的關鍵字,即至少還有[ceil(m/2)]  
281     {  
282         //調整p  
283         p->num++;                  //p的關鍵字個數加1  
284         p->K[p->num] = q->K[i+1];  //父結點相應關鍵碼下移  
285         p->A[p->num] = pR->A[0];   //右兄弟最左指針移到p的最右  
286         if(p->A[p->num] != NULL)  
287             p->A[p->num]->parent = p;  //修改父結點,原來是pR  
288         //調整父結點  
289         q->K[i+1] = pR->K[1];      //右兄弟的最小關鍵碼上移到父結點  
290         //調整右兄弟  
291         pR->A[0] = pR->A[1];       //右兄弟剩余關鍵字與指針前移  
292         LeftCompress(pR, 1);       //覆蓋K[1],A[1],關鍵字個數減1,LeftCompress中自動會減1    
293     }  
294     else  
295         MergeNode(p, q, pR, i + 1);//情形4 (...p Ki+1 pR...)  
296 }  
297 //函數功能: 通過左子女調整,如果左子女有多余結點,從左子女取一個關鍵字  
298 //函數參數: p指向被刪除的關鍵字所在結點,q指向父結點,i為p在q中的位置  
299 //返回值:   無  
300 template <typename T>  
301 void BTree<T>::LeftAdjust(BTreeNode<T> *p, BTreeNode<T> *q, int i)  
302 {  
303     BTreeNode<T> *pL = q->A[i-1]; //p的左兄弟  
304     if(pL->num >= (m_m+1)/2)      //情形3  
305     {  
306         //調整p  
307         RightCompress(p, 1);     //p的關鍵字和指針往右移動,空出位置放左子女的關鍵字,RightCompress會自動加1  
308         p->A[1] = p->A[0];     
309         p->K[1] = q->K[i];        //父結點相應關鍵碼下移  
310         p->A[0] = pL->A[pL->num]; //左兄弟最右指針移到p的最左  
311         if(p->A[0] != NULL)  
312             p->A[0]->parent = p;      //修改父結點,原來是pL  
313         //調整父結點  
314         q->K[i] = pL->K[pL->num]; //左兄弟的最大關鍵碼上移到父結點  
315         //調整左兄弟  
316         pL->num--;   //左兄弟的關鍵字個數減1  
317     }  
318     else  
319     {  
320         //左右互換一下,以符合合並函數的參數要求  
321         BTreeNode<T> *pR = p;  
322         p = pL;  
323         MergeNode(p, q, pR, i);   //情形4,注意這里i,而不是i+1 (...p Ki pR...)  
324     }  
325 }  
326 //函數功能: 將結點p自i+1開始的關鍵字和指針往左移動1,原來的K[i],A[i]其實被覆蓋掉了  
327 //函數參數: p指向結點,i為被覆蓋的位置  
328 //返回值:   無  
329 template <typename T>  
330 void BTree<T>::LeftCompress(BTreeNode<T> *p, int i)  
331 {  
332     int n = p->num;   //結點關鍵字個數  
333     for(int j = i; j < n; j++)  
334     {  
335         p->K[j] = p->K[j + 1];  
336         p->A[j] = p->A[j + 1];  
337     }  
338     p->num--; //關鍵字個數減1  
339 }  
340 //函數功能: 將結點p自i開始的關鍵字和指針往右移動1,原來的K[i],A[i]空出來了  
341 //函數參數: p指向結點,i為空出來的位置,用於放新的關鍵字  
342 //返回值:   無  
343 template <typename T>  
344 void BTree<T>::RightCompress(BTreeNode<T> *p, int i)  
345 {  
346     for(int j = p->num; j >= i; j--) //K[i],A[i]空出來用以放插入的二元組  
347     {  
348         p->K[j + 1] = p->K[j];  
349         p->A[j + 1] = p->A[j];  
350     }  
351     p->num++; //關鍵字個數加1  
352 }  
353 //函數功能: 合並兩個結點  
354 //函數參數: p指向結點,q指向父親,pR指向p的右兄弟,i為(...p,K,pR...)中的K位置  
355 //返回值:   無  
356 template <typename T>  
357 void BTree<T>::MergeNode(BTreeNode<T> *p, BTreeNode<T> *q, BTreeNode<T> *pR, int i)  
358 {  
359     int n = p->num + 1;   //p結點下一個放關鍵字的位置  
360     p->K[n] = q->K[i];    //下降父結點的關鍵字  
361     p->A[n] = pR->A[0];   //從右兄弟左移一個指針  
362     for(int j = 1; j <= pR->num; j++) //將右兄弟剩余關鍵字和指針移到p中  
363     {  
364         p->K[n + j] = pR->K[j];  
365         p->A[n + j] = pR->A[j];  
366     }  
367     if(p->A[0]) //修改p中的子女的父結點為p,這里很重要,因為這些子女原來的父結點為pR,與分裂相對  
368     {  
369         for(int j = 0; j <= pR->num; j++)  
370             p->A[n + j]->parent = p;  
371     }  
372     LeftCompress(q, i);            //父結點的關鍵字個數減1  
373     p->num = p->num + pR->num + 1; //合並后關鍵字的個數  
374     delete pR;  
375     pR = NULL;  
376 }  
377 //函數功能: 打印B樹  
378 //函數參數: 無  
379 //返回值:   無  
380 template <typename T>  
381 void BTree<T>::PrintBTree()  
382 {  
383     if(m_pRoot != NULL)  
384     {  
385         queue<BTreeNode<T> *> nodeQueue; //利用隊列  
386         nodeQueue.push(m_pRoot);         //放入根結點  
387         while(nodeQueue.size())  
388         {  
389             BTreeNode<T> * p = nodeQueue.front();  
390             if(p->A[0] != NULL) //非葉結點  
391             {  
392                 nodeQueue.push(p->A[0]);  //將子女結點的指針放入隊列中  
393                 for(int i = 1; i <= p->num; i++)  
394                 {  
395                     nodeQueue.push(p->A[i]);  
396                     cout<<p->K[i]<<' ';  
397                 }  
398             }  
399             else  
400             {  
401                 for(int i = 1; i <= p->num; i++)  
402                     cout<<p->K[i]<<' ';  
403             }     
404   
405             if(p->parent) //打印父結點的第一個關鍵字  
406                 cout<<"-----First key of their parent:"<<p->parent->K[1]<<endl;  
407             else  
408                 cout<<endl;  
409             nodeQueue.pop();  
410         }  
411     }  
412 } 

可以直接運行,大家可以復制粘貼進行效果查看(算法思想很重要)

 

上面就是B樹和B+樹從概念到代碼應用,B樹從數據庫引出的,講完之后,也會重回數據庫。下一篇將繼續講解針對SQLite進行封裝的FMDB第三方的講解並附帶項目中實際使用。

歡迎大家指正。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM