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 }