B-Tree(B樹)原理及C++代碼實現


B樹是一種平衡搜索樹,它可以看做是2-3Tree和2-3-4Tree的一種推廣。CLRS上介紹了B樹目前主要針對磁盤等直接存取的輔存設備,許多數據庫系統也利用B樹或B樹的變種來存儲信息。

本文主要針對代碼實現作一些講解。如果對B樹性質或特點不了解的,請對照B樹的定義來閱讀本文。或先了解B樹的定義,對定義了然於胸后,可以更好地把注意力放在邏輯實現上。

本文實現思路來自於CLRS,但書中只給出了search和insert的偽代碼,和delete的思路,所以本文的實現細節都是自己想出來的,比較晦澀冗雜。(我自己都不能一下子看懂),所以特別針對自己深有體會的部分加以講述。

開始。

#include <iostream>
#include <vector>
#include <utility>
using namespace std;

class BTree {
private :
    struct Node {
        //int n; //the number of the keys in this node
        vector<int> key; //key.size() return n
        vector<Node*> pToChild; //the pointer to the children,p.empty() return isLeaf
        //bool isLeaf;
    };
    using PlaceOfChild = pair<Node*, int>;
private :
    Node * root;
    int t; //the minimum degree, more than or equal to 2
private :
    void SplitChild(Node * x, const int i);
    void Insert_notfull(Node * x, const int k);
    PlaceOfChild search(Node * p, const int k);
    PlaceOfChild predecessor(Node * x, int i);
    PlaceOfChild successor(Node * x, int i);
    Node * minimum(Node * p);
    Node * maximum(Node * p);
    void combine(Node * x, Node * y, PlaceOfChild z);
    void remove(Node * x, const int k);
    void inorderWalk(Node * p);
public :
    BTree(int deg) : root(new Node), t(deg >= 2 ? deg : 2) {}
    ~BTree() {delete root;}
    void insert(const int k);
    void remove(const int k) {remove(root, k);}
    bool search(const int k) {return (search(root, k).first ? true : false);}
    int minimum() {return minimum(root)->key[0];} //can't be empty
    int maximum() {return *(maximum(root)->key.end() - 1);} //can't be empty
    void inorderWalk() {inorderWalk(root);}
};

BTree類的定義如上,因為使用了pair記錄結點關鍵值的准確位置,所以需要包含頭文件<utility>,Btree的數據成員包含指向根結點的指針root和最小度數t,對B樹最小度數不了解的一定要先看看它的定義,Btree的很多性質都與最小度數有關。

結點的數據成員包括兩個vector,第一個用來存儲關鍵字,第二個用來存儲指向孩子結點的指針。書上給的實現還包括了兩個bool值,一個是標識該結點是否為葉子結點,另一個記錄結點key值的個數。因為我使用vector保存數據,所以直接可以用vector.size()和vector.empty()來判斷是否是葉子和key的個數,但也因此導致代碼比較復雜。另外我推薦大家自己實現的時候維護一個指向父結點的指針,后面實現刪除的時候有指向父結點的指針就會更簡單實現一些。

實現過程中值得注意的點和難點:

1.任何時候使用pToChild,一定要先檢查結點是否為葉子結點,即先判斷pToChild.empty()。

2.combine和split的時候,該pop的元素一定要記得pop。因為使用的是vector而不是固定數組,所以vector元素個數一定要保證絕對正確。

3.因為沒有設置parent指針,所以找前驅和后繼的時候使用stack來記錄沿途的結點,這也是常用的手法。

4.remove的情況太過復雜,寫的時候一定把每種情況都先寫下來,再寫代碼,不然debug的時候就很難受了。

5.只有根結點的合並和分裂才會導致B樹高度的變化。

6.刪除的時候會保證經過的結點key值個數最小為t(除了root),所以不必擔心葉子結點被刪除某一個key后,key的個數小於t-1。

7.刪除的時候,combine的過程中一定要遞歸刪除而不是直接從內部結點中直接刪除(當初我糾結了好久),因為直接從內部刪除結點會導致下一層結點,即combine最后留下的結點有兩個指針沒有關鍵字分割。

8.刪除的時候,如果是使用前驅(后繼)替換需要刪除的結點的情況,再遞歸刪除的時候也一定要一層一層地遞歸,而不是直接對前驅(后繼)所在的結點遞歸。因為需要一層一層的遞歸來保證刪除函數的前提(刪除函數訪問的結點的key值個數最小為t,除了根結點)被滿足。

9.自己動手模擬了100個結點的insert和remove過程,而且還模擬了好幾遍。(因為不得不debug...)到最后對各種情況基本上可以胸有成竹了。建議有耐心的小伙伴也試試自己模擬構建B樹,一定會有更深地領悟。

代碼如下:(僅供參考)

  1 //if child is full and the parent is not full, split the child.
  2 void BTree::SplitChild(Node * x, const int i) { //O(t)
  3     Node * y = x->pToChild[i];
  4     Node * z = new Node;
  5 
  6     for (int j = 0; j < t - 1; ++j) //right half side of key
  7         z->key.push_back(y->key[j+t]);
  8 
  9     if (!y->pToChild.empty()) {//y is not a leaf
 10         for (int j = 0; j < t; ++j) //right half side of pointer
 11             z->pToChild.push_back(y->pToChild[j+t]);
 12         for (int j = 0; j < t; ++j)
 13             y->pToChild.pop_back();
 14     }
 15 
 16     x->key.insert(x->key.begin() + i, y->key[t-1]);
 17     x->pToChild.insert(x->pToChild.begin() + i + 1, z);
 18     for (int j = 0; j < t; ++j)
 19         y->key.pop_back();
 20 }
 21 
 22 void BTree::Insert_notfull(Node * x, const int k) {
 23     int i = x->key.size() - 1;
 24     while (i >= 0 && k < x->key[i])  //find insertion place
 25             --i;
 26     if (x->pToChild.empty()) {
 27         x->key.insert(x->key.begin() + i + 1, k);
 28     }
 29     else {
 30         ++i;
 31         if (x->pToChild[i]->key.size() == 2 * t - 1) {
 32             SplitChild(x, i);
 33             if (k >= x->key[i])
 34                 ++i;
 35         }
 36         Insert_notfull(x->pToChild[i], k);
 37     }
 38 }
 39 
 40 void BTree::insert(const int k) { //O(t*(logn to t))
 41     Node * r = root;
 42     if (r->key.size() == 2 * t - 1) { //root is full
 43         Node * s = new Node;
 44         root = s;
 45         s->pToChild.push_back(r);
 46         SplitChild(s, 0);
 47         Insert_notfull(s, k);
 48     }
 49     else
 50         Insert_notfull(r, k);
 51 }
 52 
 53 BTree::PlaceOfChild BTree::search(Node * p, const int k) {
 54     int i = 0;
 55     while (i < p->key.size() && k > p->key[i])
 56         ++i;
 57     if (i < p->key.size() && k == p->key[i])
 58         return make_pair(p, i);
 59     else if (p->pToChild.empty())
 60         return make_pair(nullptr, 0);
 61     else
 62         return search(p->pToChild[i], k);
 63 }
 64 
 65 BTree::Node * BTree::minimum(Node * p) {
 66     while (!p->pToChild.empty())
 67         p = p->pToChild[0];
 68     return p;
 69 }
 70 
 71 BTree::Node * BTree::maximum(Node * p) {
 72     while (!p->pToChild.empty())
 73         p = p->pToChild[p->pToChild.size()-1];
 74     return p;
 75 }
 76 
 77 BTree::PlaceOfChild BTree::predecessor(Node * x, int i) {
 78     if (!x->pToChild.empty()) {
 79         x = maximum(x->pToChild[i]);
 80         return make_pair(x, x->key.size() - 1);
 81     }
 82     else if (i != 0) {
 83         return make_pair(x, i - 1);
 84     }
 85     int key = x->key[i];
 86     Node * y = root;
 87     vector<PlaceOfChild> stk;
 88     while (1) {
 89         if (y->key[0] == key)
 90             break;
 91         for (i = 0; i < y->key.size() && key > y->key[i]; ++i)
 92             ;
 93         stk.push_back(make_pair(y, i));
 94         y = y->pToChild[i];
 95     }
 96     PlaceOfChild p;
 97     while (!stk.empty()) {
 98         p = stk.back();
 99         stk.pop_back();
100         if (p.second != 0)
101             return p;
102     }
103     return make_pair(nullptr, 0);
104 }
105 
106 BTree::PlaceOfChild BTree::successor(Node * x, int i) {
107     if (!x->pToChild.empty()) {
108         x = minimum(x->pToChild[i+1]);
109         return make_pair(x, 0);
110     }
111     else if (i != x->key.size() - 1) {
112         return make_pair(x, i + 1);
113     }
114     int key = x->key[i];
115     Node * y = root;
116     vector<PlaceOfChild> stk;
117     while (1) {
118         if (y->key.back() == key)
119             break;
120         for (i = 0; i < y->key.size() && key > y->key[i]; ++i)
121             ;
122         stk.push_back(make_pair(y, i));
123         y = y->pToChild[i];
124     }
125     PlaceOfChild p;
126     while (!stk.empty()) {
127         p = stk.back();
128         stk.pop_back();
129         if (p.second != p.first->key.size())
130             return p;
131     }
132     return make_pair(nullptr, 0);
133 }
134 
135 void BTree::combine(Node * x, Node  * y, PlaceOfChild z) {
136     x->key.push_back(z.first->key[z.second]);
137     for (int i = 0; i < t - 1; ++i)
138         x->key.push_back(y->key[i]);
139     if (!x->pToChild.empty())
140         for (int i = 0; i < t; ++i) {
141             x->pToChild.push_back(y->pToChild[i]);
142         }
143     delete y;
144 
145     z.first->key.erase(z.first->key.begin() + z.second);
146     z.first->pToChild.erase(z.first->pToChild.begin() + z.second + 1);
147     if (z.first->key.empty()) {
148         root = z.first->pToChild[z.second];
149         delete z.first;
150     }
151 }
152 
153 void BTree::remove(Node * x, const int k) { //This function guarantees x->key.size() >= t,except root
154     int i = 0;
155     while (i < x->key.size() && x->key[i] < k)
156         ++i;
157     if (i < x->key.size() && x->key[i] == k) {
158         if (x->pToChild.empty())
159             x->key.erase(x->key.begin() + i);
160         else {
161             if (x->pToChild[i]->key.size() >= t) {
162                 PlaceOfChild preOfk = predecessor(x, i);
163                 x->key[i] = preOfk.first->key[preOfk.second];
164                 remove(x->pToChild[i], x->key[i]); //recursive in the child ,not the successor
165             }
166             else if (x->pToChild[i+1]->key.size() >= t) {
167                 PlaceOfChild sucOfk = successor(x, i);
168                 x->key[i] = sucOfk.first->key[sucOfk.second];
169                 remove(x->pToChild[i+1], x->key[i]); //recursive in the child ,not the successor
170             }
171             else {
172                 combine(x->pToChild[i], x->pToChild[i+1], make_pair(x, i));
173                 remove(x->pToChild[i], k);
174             }
175         }
176     }
177     else {
178         if (x->pToChild.empty())
179             return ;
180         else if (x->pToChild[i]->key.size() != t - 1)
181             remove(x->pToChild[i], k);
182         else {
183             Node *y, *z;
184             if (i > 0 && x->pToChild[i-1]->key.size() != t - 1) {
185                 y = x->pToChild[i-1];
186                 z = x->pToChild[i];
187                 z->key.insert(z->key.begin(), x->key[i-1]);
188                 if (!y->pToChild.empty()) {
189                     z->pToChild.insert(z->pToChild.begin(), y->pToChild.back());
190                     y->pToChild.pop_back();
191                 }
192                 x->key[i-1] = y->key.back();
193                 y->key.pop_back();
194                 remove(z, k);
195             }
196             else if (i < x->pToChild.size() - 1 && x->pToChild[i+1]->key.size() != t - 1){
197                 y = x->pToChild[i+1];
198                 z = x->pToChild[i];
199                 z->key.push_back(x->key[i]);
200                 if (!y->pToChild.empty()) {
201                     z->pToChild.push_back(y->pToChild[0]);
202                     y->pToChild.erase(y->pToChild.begin());
203                 }
204                 x->key[i] = y->key[0];
205                 y->key.erase(y->key.begin());
206                 remove(z, k);
207             }
208             else if (i > 0) {
209                 y = x->pToChild[i-1];
210                 z = x->pToChild[i];
211                 combine(y, z, make_pair(x, i-1));
212                 remove(y, k);
213             }
214             else if (i < x->pToChild.size() - 1) {
215                 y = x->pToChild[i];
216                 z = x->pToChild[i+1];
217                 combine(y, z, make_pair(x, i));
218                 remove(y, k);
219             }
220         }
221     }
222 }
223 
224 void BTree::inorderWalk(Node * p) {
225     int i;
226     if (!p->pToChild.empty()) {
227         for (i = 0; i < p->key.size(); ++i) {
228             inorderWalk(p->pToChild[i]);
229             cout << p->key[i] << ' ';
230         }
231         inorderWalk(p->pToChild[i]);
232     }
233     else {
234         for (i = 0; i < p->key.size(); ++i)
235             cout << p->key[i] << ' ';
236     }
237 }


免責聲明!

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



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