【資料結構】淺析B樹
阿新 • • 發佈:2018-12-25
一、B樹的概念
B樹,概括來說是一個節點可以擁有多於2個子節點的平衡多叉樹。
特點:
1> 根節點至少有兩個子節點
2>每個非跟節點節點有(M/2)-1至M-1個key
3>每個非根節點有[M/2 ,M]個孩子
4>key[i]和key[i+1]之間的孩子節點的值介於key[i]、key[i+1]之間
5> 所有的葉子節點都在同一層
二、B樹的應用
由於增刪查改的效率十分高(時間複雜度log M-1為底N的對數的樣子),B樹普遍應用於資料庫和檔案系統。一般會將M定義的非常大,這樣B樹的高度就很低,每一個節點中可以使用二分查詢,所以B樹效率高。缺點就是比較耗記憶體。
三、實現一個B樹
1.節點內容
根據B樹的特點,一個節點應該包含一個大小為M-1的key陣列,但是考慮到B樹在插入的時候需要先插入節點然後在進行分裂,所以key陣列給M大的空間。其次,應該包含一個節點指標型別的陣列,儲存指向孩子的指標,大小應為M+1。還有,一個指向父節點的指標,一個表示當前結點key值實際數量的size。
2.節點插入
1>空樹時,直接呼叫節點的建構函式,new一個root。
2>非空時,先查詢樹,若已經存在要插入的關鍵碼,則插入失敗。若不存在,讓Find()函式返回插入位置的指標。那麼,Find不僅要返回key值是否在樹內(bool),還要返回要插入節點的位置(Node*)。所以,我們使用庫裡存在的一個型別pair,它可以帶回兩個返回值。
3>接下來,把key值插入Find返回的位置。
4>檢測插入後key值的個數是否超過M-1,若沒有,則插入成功,若超過,則需要分裂。根據B樹的特點,可以知道,插入的位置只能是葉子節點。
分裂是怎麼一回事呢?看下圖
分裂是插入的難點,理解了分裂,插入就沒什麼問題了。
3.程式碼實現
#include<iostream> using namespace std; template<class K , int M> struct BTreeNode { typedef BTreeNode<K, M> Node; K _keys[M]; //多給出一個位置是為了方便分裂。 Node* _sub[M + 1]; Node* _parent; size_t _size; //記錄實際關鍵字的個數 BTreeNode() :_parent(NULL) , _size(0) { for (size_t i = 0; i < M; i++) { _keys[i] = K(); _sub[i] = 0; } _sub[M] = 0; } }; template<class K,int M> class BTree { public: typedef BTreeNode<K, M> Node; BTree() :_root(NULL) {} void InOrder() { _InOrder(_root); } ~BTree() { _Destory(_root); } bool Insert(const K& key) { if (NULL == _root) { _root = new Node(); _root->_keys[0] = key; _root->_size++; return true; } pair<Node*, size_t> tmp = _Find(key); if (tmp.second != -1) //已經存在關鍵值為key,則不能插入 { return false; } Node* cur = tmp.first; Node* sub = NULL; K newkey = key; while (1) { _Insertkey(cur,newkey,sub); //先將key放進要插的節點 //判斷結點的關鍵字數目是否符合標準。 if (cur->_size < M) //該節點上插入後關鍵字數目正常 return true; //數目超過規定值需要進行分裂 while (cur->_size >= M) { size_t mid = cur->_size / 2; //1.分裂出新的結點 Node* NewNode = new Node; for (size_t i = mid+1; i < cur->_size; i++)//mid之後的key值給新結點 { int j = 0; NewNode->_keys[j] = cur->_keys[i]; NewNode->_size++; //cur->_size--; //注意此處先不要哦改動cur的size,否則會影響下一個迴圈 cur->_keys[i] = K(); //賦給新結點後cur對應的key應置成初始值 j++; } int j = 0; for (size_t i = mid+1 ; i < cur->_size+1; i++) { NewNode->_sub[j] = cur->_sub[i]; if (NewNode->_sub[j]) NewNode->_sub[j]->_parent = NewNode; j++; cur->_sub[i] = NULL; } if (cur == _root) //創建出新的根節點 { Node* tmp = new Node(); tmp->_keys[0] = cur->_keys[mid]; cur->_keys[mid] = K(); cur->_size=mid; tmp->_size++; tmp->_sub[0] = cur; cur->_parent = tmp; tmp->_sub[1] = NewNode; NewNode->_parent = tmp; _root = tmp; return true; } newkey = cur->_keys[mid]; cur->_keys[mid] = K(); cur->_size = mid; sub = NewNode; } cur = cur->_parent; } } protected: void _Destory(Node* root) { if (NULL == root) return; size_t i = 0; for (; i < root->_size; i++) { _Destory(root->_sub[i]); delete root->_sub[i]; } _Destory(root->_sub[i]); delete root->_sub[i]; } void _InOrder(Node* root) { if (NULL == root) return; size_t i = 0; for (; i < root->_size; i++) { _InOrder(root->_sub[i]); cout << root->_keys[i]<<" "; } _InOrder(root->_sub[i]); } pair<Node*,size_t> _Find(const K& key) { Node* cur = _root; Node* parent = NULL; while (cur) { size_t i = 0; while (i< cur->_size) //找當前結點的key { if (cur->_keys[i] < key) i++; else if (cur->_keys[i]>key) //cur = cur->_sub[i]; break; else //找到了和傳入key相等的關鍵字 return pair<Node* ,size_t>(cur,i); } parent = cur; cur = cur->_sub[i]; } return pair<Node* , size_t>(parent, -1); //沒找到 } void _Insertkey(Node* cur,const K& key,Node*sub) { int i = cur->_size-1; while (i >=0) { if (cur->_keys[i] > key) { //移動關鍵字的位置 cur->_keys[i + 1] = cur->_keys[i]; //移動子樹的位置 cur->_sub[i + 2] = cur->_sub[i + 1]; i--; } else break; } //i記錄著要插入位置的前一個 cur->_keys[i + 1] = key; cur->_sub[i + 2] = sub; if (sub) sub->_parent = cur; cur->_size++; } private: Node* _root; }; void TestBTree() { BTree<int, 3> bt; int a[] = { 53, 75, 139, 49, 145, 36, 101 }; for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { bt.Insert(a[i]); } bt.InOrder(); }