【演算法和資料結構】平衡查詢樹之B樹
阿新 • • 發佈:2018-12-25
以B-樹的效能總是等價於二分查詢(與M值無關),也就沒有B樹平衡的問題;由於M/2的限制,在插入結點時,如果結點已滿,需要將結點分裂為兩個各佔M/2的結點;刪除結點時,需將兩個不足M/2的兄弟結點合併。
下面簡單說明分裂:
#pragma once //3階B樹 template<class K, int M = 3> struct BTreeNode { K _keys[M];//儲存有M-1個key,多一個是為了方便分裂 BTreeNode<K, M>* _subs[M + 1];//儲存有M個subs,多一個是為了方便分裂 BTreeNode<K, M>* _parent; size_t _size;//陣列中存在的有效關鍵字的個數 BTreeNode() :_parent(NULL) , _size(0) { int i = 0; for (; i <= M; ++i) { _keys[i] = 0; _subs[i] = NULL; } _keys[i] = 0; } }; template<class K,class V>//結構體實現K,V形式 struct Pair { K _first; V _second; Pair(const K& key = k(), const V& value = V()) :_first(key) , _second(value) {} }; template<class K, int M = 3> class BTree { typedef BTreeNode<K, M> Node; public: BTree() :_root(NULL) {} bool Insert(const K& key)//插入節點 { if (_root == NULL) { _root = new Node; _root->_size++; _root->_keys[0] = key; return true; } Pair<Node*, int> ret= Find(key); if (ret._second != -1)//判斷key是否已經存在 return false; //在節點cur中插入key和sub Node* cur = ret._first; K insertkey = key; Node* sub = NULL; while (1) { _InsertKey(cur, insertkey, sub); if (cur->_size < M) return true; //插入資料後,節點關鍵字個數大於M-1,需分裂節點,拷貝右半部分 int mid = (cur->_size - 1) / 2;//找到中間值,進行上移 int index = 0; Node* tmp = new Node;//tmp分裂出來的右半部分,左半部分在cur中 //拷貝key和subs for (size_t i = mid + 1; i < cur->_size; ++i) { //拷貝key tmp->_keys[index++] = tmp->_keys[i]; tmp->_size++; //拷貝subs tmp->_subs[index++] = cur->_subs[i]; if (cur->_subs[i]) cur->_subs[i]->_parent = tmp; } cur->_size = (cur->_size - 1) / 2;//更新cur(分裂後的左半部分)大小 if (cur->_parent == NULL)//插入分裂後上移的元素 { _root = new Node; _root->_keys[0] = cur->_keys[mid]; _root->_size = 1; _root->_subs[0] = cur; _root->_subs[1] = tmp; cur->_parent = _root; tmp->_parent = _root; break; } else { insertkey = cur->_keys[mid]; sub = tmp; cur = cur->_parent;//上移 } } return true; } Pair<Node*, int> Find(const K& key) //查詢key,返回節點及對應節點中陣列下標 { Node* parent = NULL; Node* cur = _root; while (cur) { size_t index = 0; while (index < cur->_size)//遍歷整個節點關鍵字 { if (key == cur->_keys[index]) return Pair<Node*, int>(cur, index); else if (key > cur->_keys[index]) index++; else//小於_key[index] --> 結束迴圈,在_key[index]所在節點查詢 break; } parent = cur; cur = cur->_subs[index]; } return Pair<Node*, int>(parent, -1);//沒有找到,注意返回cur的父結點和-1 } void InOrder()//中序遍歷輸出 { _InOrder(_root); } private: void _InsertKey(Node* cur,const K& key, Node* sub)//插入key值 { int index = cur->_size - 1;//從後向前比較移位 while (index >= 0 && key < cur->_keys[index])//後面的資料(包括_sub[])向後移 { cur->_keys[index + 1] = cur->_keys[index]; cur->_subs[index + 2] = cur->_subs[index + 1];//畫圖分析,_subs[]移動兩位 --index; } cur->_keys[index + 1] = key; cur->_subs[index + 2] = sub; if (sub) sub->_parent = cur; ++cur->_size; } void _InOrder(Node* root) { if (root == NULL) { return; } for (int i = 0; i < _root->_size; ++i) { _InOrder(root->_subs[i]); cout << root->_keys[i] << " "; } } protected: Node* _root; }; void BTreeTest() { int a[] = { 53, 75, 139, 49, 145, 36, 101 }; BTree<int, 3> bt; for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i) { bt.Insert(a[i]); } bt.InOrder(); }
B+樹
B+樹是B-樹的變體,也是一種多路搜尋樹:
1.其定義基本與B-樹同,除了以下幾點不同。
2.非葉子結點的子樹指標與關鍵字個數相同;
3.非葉子結點的子樹指標P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);
5.為所有葉子結點增加一個鏈指標;
6.所有關鍵字都在葉子結點出現;
B+的搜尋與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其效能也等價於在關鍵字全集做一次二分查詢。
B*樹
B*樹是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指標。