真c++创建B树(非c with class)
??首先要感謝清華大學鄧公的教材(數據結構c++語言版第三版)和視頻
??鄧公的視頻網址如下:https://next.xuetangx.com/course/THU08091002048/1515966
??由于鄧公實現b樹是用自定義的vector實現,網上大部分也都是用數組實現。我嘗試了一下用stl的vector實現。也能實現b樹的功能。
??在此,為了方便,用了一些教材中的插圖(侵刪)。
如果要看懂此代碼,你需要擁有的基礎為:
- 對c++模板有基本了解
- 對c++stl中的vector有一定了解
- 熟悉stl迭代器,逆向迭代器的用法,在本代碼的查找,插入和刪除操作中,用了大量的迭代器操作,如果你不熟悉,可能會有點繞。
- 對c++11的新特性,如auto ,lambdas,基于范圍的for循環等有所了解
??本代碼在vs2019版能運行,其他平臺未測試,但應該能支持c++11的編譯器都能運行。如果你沒接觸過b樹,要理解本代碼,你至少需要一個小時甚至以上,b樹真的十分復雜。
此B樹只適合存放不相同元素,如果想放相同的元素,請自行改進。
文章目錄
- 一.B樹的定義~
- 1.多路搜索樹~
- 2.多路平衡搜索樹~
- 3.B樹的性質~
- 二.B樹節點類~
- BTNode.h~
- 三.B樹類~
- (一)定義變量和接口~
- 1.需要的變量
- 2.需要的接口
- 3.必備的輔助函數
- 4.BTree.h~
- (二)B樹查找~
- 1.代碼~
- 2.效率分析~
- (三)B樹插入~
- 1.簡單插入圖示~
- 2.代碼~
- (四)上溢與分裂~
- 1.只上溢一次,不會造成父親上溢~
- 2.上溢之后,父親也會上溢~
- 3.上溢到根節點~
- 4.show you my code~
- 5.復雜度分析~
- (五)刪除~
- 1.節點為葉節點刪除圖示~
- 2.節點不為葉節點刪除圖示~
- 3.代碼~
- (六)下溢與合并~
- 1.V的左兄弟L存在,左兄弟至少包含?m/2?個關鍵碼(動圖gif)~
- 2.V的右兄弟R存在,右兄弟至少包含?m/2?個關鍵碼(動圖gif)~
- 3.左兄弟為空,并且右兄弟沒有足夠孩子(動圖gif)~
- 4.右兄弟為空,并且左兄弟沒有足夠孩子(動圖gif)~
- 5.經過(3)(4)后,父親發生下溢~
- 6.show you my code~
- 7.復雜度分析~
- (七)前序遍歷測試~
- 四.結果測試~
- 1.插入測試~
- 2.刪除測試~
- 五.結語~
一.B樹的定義~
1.多路搜索樹~
比如以二叉搜索樹(BST)的兩層為間隔,將各節點與其左右孩子合并成一個大節點,就能得到一個四路搜索樹。
當以三層為間隔時,可以得到八路搜索樹。因此可以推廣到2^k路搜索樹
接下來的代碼,能創建任意路搜索樹,如果創建的為4路搜索樹,我們不妨稱之為(2,4)樹。
2.多路平衡搜索樹~
若樹中所有葉節點的深度均相等,就可以稱之為多路平衡搜索樹。
深度即從根節點到這個節點的長度。
如下圖所示
3.B樹的性質~
舉例說明:
即在一顆4階的B樹中,其孩子個數至少為2,最多為4.關鍵碼個數至少為1,最多為3.
即在一顆3階的B樹中,其孩子個數至少為2,最多為3.關鍵碼個數至少為1,最多為2.
二.B樹節點類~
從B樹的結構和性質來看,在B樹節點中,至少需要一個存放關鍵碼的容器和一個存放孩子節點的容器。
所以我們把同一節點的所有孩子組織為一個vector,各相鄰孩子之間的關鍵碼也組織為一個vector。當然,按照B-樹的定義,孩子vector的實際長度總是比關鍵碼vector多一。
為了方便插入和刪除,同樣也需要一個parent指針,來指向父節點。
同樣,為了規范,將節點定義包含在一個命名空間my_btree里面。
由于沒有在堆區構造,所以不需要寫析構函數來delete
BTNode.h~
#pragma once #include <vector> using std::vector;namespace my_btree {/*B-樹節點模板類*/template<typename T = int>class BTNode {public:using BTNodePtr = BTNode<T>*;public:BTNodePtr _parent;//父節點vector<T> _key;//關鍵碼vectorvector<BTNodePtr> _child;//孩子vector,其長度總比key多一private:int _order;//B樹的order,方便擴容//可不要這個,讓vector自動擴容public://用于創建根節點,初始時有0個關鍵碼和一個空孩子指針BTNode(int order=3) :_order(order),_parent(nullptr), _key(0){_key.reserve(order);//并且給key和child的vector預留容量//用空間換時間。_child.reserve(order + 1);_child.push_back(nullptr);}};//class BTNode}//namespace my_btree三.B樹類~
(一)定義變量和接口~
1.需要的變量
int _size;//存放的關鍵碼總數int _order;//B-樹的階次,比如3階b樹,可以在構造的時候進行修改。BTNodePtr _root;//根節點BTNodePtr _hot;//BTree::search()最后訪問的非空(除非樹空)的節點位置如果看過鄧老師的課的同學,應該能明白_hot節點的作用。在這里我簡單解釋一下_hot的作用。即其對應查找之后,訪問的最后一個非空節點位置。有了_hot,節點的插入和刪除操作就能變得更加簡單。
2.需要的接口
構造函數 析構函數 查找 插入 刪除 遍歷(前序遍歷,方便看結果) 其他接口如 獲取階次,返回規模,判空,返回根節點等。3.必備的輔助函數
因插入而造成節點上溢所需的解決上溢函數 因刪除而造成節點下溢所需的解決下溢函數4.BTree.h~
為了方便起見,以下均以3階b樹為基準 #pragma once #include "BTNode.h" #include <algorithm> using std::cout; using std::endl;namespace my_btree{template<typename T=int>class BTree {public:using BTNodePtr = BTNode<T>*;protected:int _size;//存放的關鍵碼總數int _order;//B-樹的階次,比如3階b樹,可以在構造的時候進行修改。BTNodePtr _root;//根節點BTNodePtr _hot;//BTree::search()最后訪問的非空(除非樹空)的節點位置protected:void solveOverFlow(BTNode<T>*);//處理因插入而上溢之后的分裂處理void solveUnderFlow(BTNode<T>*);//處理因刪除而下溢之后的合并處理public:BTree(int order=3):_order(order),_size(0),_root(new BTNode<T>(_order)),_hot(nullptr){}~BTree() {if (_root){delete _root;_root = nullptr;}}public:constexpr int order()const {//獲取階次return _order;}constexpr int size()const {return _size;}inline BTNodePtr root()const{//返回樹根return _root;} constexpr bool empty()const {return !_root;}public:BTNode<T>* search(const T& data);//查找bool insert(const T& data);//插入bool remove(const T& data);//刪除public:void show_BTree(BTNode<T>* BT)const;};//class BTree }//namespace my_btree(二)B樹查找~
1.代碼~
??從根節點開始,通過關鍵碼的比較不斷深入至下一層,直到某一關鍵碼命中(查找成功),或者到達某一外部節點(查找失敗)。
??此時各節點內通常都包含多個關鍵碼,故有可能需要經過多次比較,才能確定應該轉向下一層的哪個節點并繼續查找。
??如果查找失敗,返回不大于data(圖中的key)的最大節點(由于節點數最多也不過幾百個,故用順序查找還是二分查找,都可以,這里用的是順序查找)。從而轉至下一層,直到找到或者到達葉子節點。
??由于c++標準庫并沒有提供find_last的算法給vector使用。因此返回不大于data的最大節點,我用的是逆向迭代器結合find_if算法,并結合lambdas表達式作為仿函數來實現。
??并且由于標準庫中的算法,需要傳的是迭代器,返回的也是迭代器,因此,如果你對迭代器不是很懂,那么就建議你看看鄧公原版的代碼,那個稍微容量理解點。
template<typename T>BTNode<T>* BTree<T>::search(const T& data){BTNode<T>* v = _root;//從根節點出發_hot = nullptr;//首先將_hot置空while (v) {//返回第一個不大于data的迭代器auto key_Iter = std::find_if(v->_key.rbegin(), v->_key.rend(),[data](T key_value){return key_value <= data;});if ((key_Iter != v->_key.rend()) && data == *key_Iter)//若找到,則返回return v;_hot = v;//否則就將當前節點設為_hot,并且去對應的孩子節點找//由于孩子的個數始終比關鍵碼個數多一,所以不會越界v = v->_child.at(std::distance(key_Iter,v->_key.rend()));}return nullptr;//失敗,抵達外部節點}2.效率分析~
??vector的查找十分迅速,因此其耗時幾乎可以忽略不計。
??b樹的查找的耗時主要在于去其孩子節點找相應的值。并且在查找的過程中,在每一高度上,至多訪問一個節點。因此,b樹查找的復雜度即為b樹的高度。
??在這里,證明略,查閱資料可得b樹的平均高度為O(logmN)。
??故b樹查找的平均復雜度為O(logmN)。
(三)B樹插入~
1.簡單插入圖示~
在上圖中插入節點232.代碼~
template<typename T> bool BTree<T>::insert(const T& data) {//搜尋了一遍之后,_hot節點所在的位置的關鍵碼vector即為要插入的數值應該屬于的vector。BTNode<T>* v = search(data);if (v)//確認目標不存在return false;//找到要插入的位置auto key_r_Iter = std::find_if(_hot->_key.rbegin(), _hot->_key.rend(), [data](T key_value) {return key_value <= data;});int distance = std::distance(key_r_Iter, _hot->_key.rend())+1; //提前算好距離并且必須要加1//由于insert只接受正向迭代器,所以要轉成正向的,就是要插入到后一個位置_hot->_key.insert(key_r_Iter.base(), data); auto child_Iter = _hot->_child.begin();child_Iter += distance;//找到要插入的孩子節點的迭代器位置_hot->_child.insert(child_Iter, nullptr);//在對應孩子節點vector中,插入一個空指針_size++;//當此時_hot節點的孩子的個數大于原來的設定的B樹的階次時if(_order < static_cast<int>(_hot->_child.size()))solveOverFlow(_hot);//需做分裂,處理上溢return true; }(四)上溢與分裂~
??由b樹的定義可知,剛發生上溢的節點,應恰好含有m個關鍵碼。不妨取s=m/2的下界。則它們依次為:
{ k0, ..., ks-1; ks; ks+1, ..., km-1 } ??可見,以ks為界,可將該節點分前、后兩個子節點,且二者大致等長。??于是,可令關鍵碼ks上升一層,歸入其父節點(若存在)中的適當位置,并分別以這兩個子節點作為其左、右孩子。這一過程,稱作節點的分裂(split)。
以下分三種情況進行考慮
1.只上溢一次,不會造成父親上溢~
在上圖中插入節點29
可以觀察到,由于19 23 29有了三個key,超過了3階b樹的限制,3階b樹單個節點的關鍵碼數最多為2
因此此時要發生上溢。
??我們之前設置的s的作用就體現出來了,由于m=3,所以s=1,因此,以下標為1的關鍵碼為界限進行分裂,在19 23 29中,23的下標為1,所以23提升到父節點中,而19 和29 就成為其左右兩個孩子。
??父親節點36插入23之后,符合3階b樹的標準,所以上溢終止,調整結束。
2.上溢之后,父親也會上溢~
在上圖中插入節點45
??同理,插入了45之后,41 45 51三個節點由于有3個關鍵碼,因此也會發生上溢,經過上溢方式1的調整。可得
這時候,不難觀察到其父親23 36 45此時也有了3個關鍵碼,因此也會發生上溢,因此,可得。
這樣就調整完畢。
3.上溢到根節點~
倘若此時我們插入87 此時會發生持續上溢 直到根節點 此時,還是跟之前一樣,將key為1的關鍵碼往上提,即把53往上提 至此就完成了上溢調整。 這也是b樹長高的唯一可能。4.show you my code~
注釋寫的很詳細了,可以自己對照圖加深理解。 template<typename T> void BTree<T>::solveOverFlow(BTNode<T>* v) {while (_order < static_cast<int>(v->_child.size())) {int s = _order / 2;//軸點(如果能到這一步,說明發生了上溢,此時,必然有key的size等于_order)int move_Number = _order - s - 1;//分裂后需要移動的關鍵碼的個數//創建一個新節點,并且由于構造函數,這個節點必然有一個空孩子//堆區的數據,由vector來刪除BTNode<T>* newNode = new BTNode<T>(_order);//將v的右側的child的vector的數值轉移到新的節點的孩子vector中//首先,因為這個新節點創建后,一定有一個空孩子,所以要將這個空孩子的值賦值成對應的值//而且v->_child的vector必然有兩個元素以上(可能均為nullptr)newNode->_child.at(0) = *(v->_child.end() - move_Number - 1);//再在后面進行插入,此時是在begin()的下一個位置進行插入newNode->_child.insert(++newNode->_child.begin(), v->_child.end() - move_Number, v->_child.end());//刪除v中對應child的vector的一部分v->_child.erase(v->_child.end() - move_Number - 1, v->_child.end());//將v的右側的關鍵碼vector的數值轉移到新的節點的關鍵碼vector中newNode->_key.insert(newNode->_key.begin(), v->_key.end() - move_Number, v->_key.end());//刪除v中對應key的vector的一部分v->_key.erase(v->_key.end() - move_Number, v->_key.end());//如果新節點的第一個孩子不為空,如果一個孩子為空,則后面孩子必然為空if (newNode->_child.at(0)) {for (auto& child : newNode->_child) {//則令它們的父節點統一child->_parent = newNode;//即指向父節點}}BTNode<T>* p = v->_parent;//v節點的當前父親if (p == nullptr) {//如果父親為空//則說明此時根節點溢出,則需創建一個新的節點作為根節點_root = p = new BTNode<T>(_order);p->_child.at(0) = v;//就將這個根節點的孩子設為vv->_parent = p;//并更新v的父親}//找到v在父節點對應的孩子的秩auto p_r_Iter = std::find_if(p->_key.rbegin(), p->_key.rend(), [=](T key_value) {return key_value <= v->_key.at(0);});int distance = std::distance(p_r_Iter, p->_key.rend()) + 1;//提前算好距離并且必須要加1auto key_Iter = p_r_Iter.base();p->_key.insert(key_Iter, v->_key.at(s));//在對應位置插入軸點關鍵碼v->_key.pop_back();//并且s,必然是v的key此時的最后一個,在v中刪除這個關鍵碼auto child_Iter = p->_child.begin();child_Iter += distance;p->_child.insert(child_Iter, newNode);//將這個新節點作為p的孩子newNode->_parent = p;//并且更新新節點的父親v = p;//將v設成v的父親,進入迭代循環} }5.復雜度分析~
??若將B-樹的階次m視作為常數,則關鍵碼的移動和復制操作所需的時間都可以忽略。至于solveOverflow()算法,其每一次循環均只需常數時間,迭代層數不超過B-樹高度。由此可知,對于存有N個關鍵碼的m階B-樹,每次插入操作都可在O(logmN)時間內完成。
??實際上,因插入操作而導致O(logmN)次分裂的情況極為罕見,單次插入操作平均引發的分裂次數,遠遠低于這一估計,故時間通常主要消耗于對目標關鍵碼的查找。
(五)刪除~
1.節點為葉節點刪除圖示~
在上圖中刪除節點412.節點不為葉節點刪除圖示~
在上圖中刪除節點53,首先將53與其直接后繼64進行交換數值 然后當做葉節點刪除53既可3.代碼~
template<typename T> bool BTree<T>::remove(const T& data) {BTNode<T>* v = search(data);if (v == nullptr)//如果沒找到,就返回return false;//在v中確定data的逆向迭代器的位置//必然存在,不可能找不到auto key_r_Iter = std::find_if(v->_key.rbegin(), v->_key.rend(), [data](T key_value) {return key_value <= data;});//同search算法,不需要加1//distance必然不可能為0int distance = std::distance(key_r_Iter, v->_key.rend());if (v->_child.at(0)) {//如果v有孩子,就去找其直接后繼BTNode<T>* u = v->_child.at(distance);//找到對應孩子節點的位置while (u->_child.at(0))//向左一直找,直到u沒有孩子為止,說明u此時為葉節點u = u->_child.at(0);//此時的u即為原來v的直接后繼,因此同BST,此時將v此時的值設為u的值既可v->_key.at(distance - 1) = u->_key.at(0);v = u;//讓v去接替u的值,然后刪掉其中的key和child。distance = 0;}if (distance == 0) {v->_key.erase(v->_key.begin());v->_child.erase(++(v->_child.begin()));//刪第二個,也可以刪第一個}else {auto key_Iter = key_r_Iter.base();v->_key.erase(--key_Iter);//此時刪除必須要退一格進行刪除auto child_Iter = v->_child.begin();child_Iter += distance;v->_child.erase(child_Iter);}_size--;//規模更新solveUnderFlow(v);//如果必要,需做旋轉或合并return true; }(六)下溢與合并~
??通過B樹的性質,不難發現,在m階B-樹中,剛發生下溢的節點V必恰好包含?m/2?(取上界)- 2個關鍵碼和?m/2?- 1個孩子。以下將根據其左、右兄弟所含關鍵碼的數目,分五種情況做相應的處理。
??還是以3階b樹來考慮,此時m=3,即m/2的上界為2.
1.V的左兄弟L存在,左兄弟至少包含?m/2?個關鍵碼(動圖gif)~
??即左兄弟至少有2個關鍵碼。而v刪除后此時只有0個關鍵碼,此時,就將父節點的對應關鍵碼給v,并從v的左兄弟中借最右邊的那個關鍵碼給父親。同時也要對孩子進行一些刪除和變換。
如刪除下圖中關鍵碼32.V的右兄弟R存在,右兄弟至少包含?m/2?個關鍵碼(動圖gif)~
??即右兄弟至少有2個關鍵碼。而v刪除后此時只有0個關鍵碼,此時,就將父節點的對應關鍵碼給v,并從v的右兄弟中借最左邊的那個關鍵碼給父親。同時也要對孩子進行一些刪除和變換。
如刪除下圖中關鍵碼13.左兄弟為空,并且右兄弟沒有足夠孩子(動圖gif)~
??顯然,由于一個節點至少有兩個孩子,其父親至少有兩個孩子,因此,這個節點v至少存在一個兄弟,并且這個兄弟一定恰好包含?m/2?- 1個關鍵碼,必然不可能為空。
??對于3階b樹而言,其兄弟有且僅有1個關鍵碼。不足以借它,所以可以向其父親,借一個關鍵碼,將兩個節點合并成一個節點。同時對孩子進行調整。
如刪除下圖中關鍵碼1并且,借了一個關鍵碼之后,這個新節點,必然不可能越界。
4.右兄弟為空,并且左兄弟沒有足夠孩子(動圖gif)~
同(3)分析
如刪除下圖中關鍵碼55.經過(3)(4)后,父親發生下溢~
??但是從父親借一個關鍵碼,可能造成父親的下溢,因此,只需對父親再進行一次下溢的操作,即可修復父親的下溢,當然,可能會引發連鎖下溢,但最壞也不過下溢到根節點。
如刪除下圖中關鍵碼1??如果下溢到根節點,并且原來的根節點只有一個關鍵碼,借了之后就沒有了。那么此時就不妨刪除這個根節點,以其孩子作為根節點,從而取代之。此時,b樹的高度也必然下降一層。
這也是b樹變矮的唯一可能。6.show you my code~
注釋寫的很詳細了,可以自己對照圖加深理解。 template<typename T> void BTree<T>::solveUnderFlow(BTNode<T>* v) {if ((_order + 1) / 2 <= static_cast<int>(v->_child.size()))//遞歸基,當前節點未下溢return;BTNode<T>* p = v->_parent;//當前節點的父親if (p == nullptr) {//遞歸基,如果父親為空節點,即說明此時到達了根節點。if (!v->_key.size() && v->_child.at(0)) {//如果v此時的key為空,但卻有唯一的非空孩子_root = v->_child.at(0);//就將v的孩子作為根節點_root->_parent = nullptr;v->_child.at(0) = nullptr;//將v的這個孩子置為空delete v;//釋放vv = nullptr;}//整樹的高度降低一層return;//無論v此時是否為空,均達到遞歸基,若v不為空,則v就是根節點。}size_t rank = 0;while (p->_child.at(rank) != v)//找到v在其父親中所屬的孩子的位置rank++;/*1.向左兄弟借關鍵碼*/if (0 < rank) {//如果v不是p的第一個孩子BTNode<T>* ls = p->_child.at(rank - 1);//則左兄弟必然存在if ((_order + 1) / 2 < static_cast<int>(ls->_child.size())) {//如果左兄弟有充足的孩子v->_key.insert(v->_key.begin(), p->_key.at(rank - 1));//將父親插入v的最左邊//將左兄弟的最右邊的key設為父親的這個key的值p->_key.at(rank - 1) = ls->_key.at(ls->_key.size() - 1);ls->_key.pop_back();//刪除左兄弟的最右邊的key//將左兄弟的最右邊的孩子作為v的第一個孩子v->_child.insert(v->_child.begin(),ls->_child.back());ls->_child.pop_back();//刪除左兄弟的最右邊的child//如果左兄弟的右孩子不為空,即v此時的左孩子不為空,就重設左孩子的父親。if (v->_child.front())v->_child.front()->_parent = v;return;//至此,就完成了當前層的下溢處理。同時,由于父親的key沒有減少,所以也完成了所有層的下溢處理。}}//如果執行到此,說明左兄弟沒有充足的孩子,或者根本沒進入if語句內,即rank=0,v沒有左兄弟/*2.向右兄弟借關鍵碼*/if (p->_child.size() - 1 > rank) {//如果v并非最后一個孩子,并且其左兄弟沒有充足的孩子時BTNode<T>* rs = p->_child.at(rank + 1);//則右兄弟必然存在if ((_order + 1) / 2 < static_cast<int>(rs->_child.size())) {//如果右兄弟有充足的孩子v->_key.push_back(p->_key.at(rank));//將父親插入v的最右邊p->_key.at(rank) = rs->_key.front();//將右兄弟的最左邊的key設為父親的這個key的值rs->_key.erase(rs->_key.begin());//刪除右兄弟的最左邊的keyv->_child.push_back(rs->_child.front());//將右兄弟的最左邊的孩子作為v的最后一個孩子rs->_child.erase(rs->_child.begin());//刪除右兄弟的最左邊的child//如果右兄弟的左孩子不為空,即v此時的右孩子不為空,就重設右孩子的父親。if (v->_child.back())v->_child.back()->_parent = v;return;//至此,就完成了當前層的下溢處理。同時,由于父親的key沒有減少,所以也完成了所有層的下溢處理。}}//如果執行到此,說明右兄弟沒有充足的孩子,或者根本沒進入if語句中,即v沒有右兄弟/*3.此時,要么左兄弟為空,并且右兄弟沒有足夠孩子;或者右兄弟為空,并且左兄弟沒有足夠孩子*//*左右兄弟不可能均為空*/if (0 < rank) {//與左兄弟和父親合并BTNode<T>* ls = p->_child.at(rank - 1);//左兄弟必然存在,一定不為空,但沒有足夠孩子ls->_key.push_back(p->_key.at(rank - 1));//在左兄弟的最右邊插入父親的keyp->_key.erase(p->_key.begin() + rank - 1);//并刪除父親對應的節點//同時刪除父親這個節點的孩子,此時v不再是p的這個孩子p->_child.erase(p->_child.begin() + rank);ls->_child.push_back(v->_child.front());//將v的左孩子作為其左兄弟的最右孩子v->_child.erase(v->_child.begin());if (ls->_child.back())//并且重新設定父子關系ls->_child.back()->_parent = ls;//將v此時所有的key都插到其左兄弟的尾部ls->_key.insert(ls->_key.end(), v->_key.begin(), v->_key.end());v->_key.clear();//刪除v的key的所有節點for (auto v_c_Iter : v->_child) {ls->_child.push_back(v_c_Iter);//將v此時所有的child都插到其左兄弟的尾部if (ls->_child.back())//如果不為空,就重新設定父子關系ls->_child.back()->_parent = ls;}v->_child.clear();//刪除v的child的所有節點delete(v);//釋放vv = nullptr;}else {//與右兄弟和父親合并BTNode<T>* rs = p->_child.at(rank +1);//右兄弟必然存在,一定不為空,但沒有足夠孩子rs->_key.insert(rs->_key.begin(), p->_key.at(rank));//在右兄弟的最左邊插入父親的keyp->_key.erase(p->_key.begin() + rank);//并刪除父親對應的節點//同時刪除父親這個節點的孩子,此時v不再是p的這個孩子p->_child.erase(p->_child.begin() + rank);rs->_child.insert(rs->_child.begin(), v->_child.back());//將v的右孩子作為右兄弟的最左孩子v->_child.pop_back();if (rs->_child.front())//并且重新設定父子關系rs->_child.front()->_parent = rs;//將v此時所有的key都插到其右兄弟的頭rs->_key.insert(rs->_key.begin(), v->_key.begin(), v->_key.end());v->_key.clear();//刪除v的key的所有節點//將v此時所有的child都插到其右兄弟的頭部rs->_child.insert(rs->_child.begin(), v->_child.begin(), v->_child.end());for (auto& rs_child : rs->_child) {if (rs_child) {//如果不為空,就重新設定父子關系//直到遍歷到沒有加v的child之前的原來的rs的child為止。if (rs_child->_parent == rs)break;rs_child->_parent = rs;}}v->_child.clear();//刪除v的child的所有節點delete v;//釋放vv = nullptr;}solveUnderFlow(p);//上升一層,如果有必要,繼續分裂,至多遞歸至logn層 }7.復雜度分析~
??與插入操作同理,在存有N個關鍵碼的m階B-樹中的每次關鍵碼刪除操作,都可以O(logmN)時間內完成。
??另外同樣地,因某一關鍵碼的刪除而導致O(logmN)次合并操作的情況也極為罕見,單次刪除操作過程中平均只需做常數次節點的合并。
??故刪除的復雜度為O(logmN)。
(七)前序遍歷測試~
調用show_BTree函數。傳一個root()作為參數既可。
template<typename T> void BTree<T>::show_BTree(BTNode<T>* BT)const {//用的時候,傳一個root()if (BT == nullptr) {cout << "BTree 不存在" << endl;return;}bool has_child = false;cout << "[";for (auto it = BT->_key.begin(); it != BT->_key.end(); ++it) {if (it != BT->_key.begin())cout << " ";cout << *it;}cout << "]";for (size_t i = 0; i <= BT->_key.size(); ++i) {if (BT->_child.at(i) != nullptr) {if (i == 0) {cout << "<";}else {cout << ",";}show_BTree(BT->_child.at(i));has_child = true;}}if (has_child)cout << ">"; }四.結果測試~
1.插入測試~
#include<iostream> #include "BTree.h"using namespace std; using namespace my_btree;int main() {BTree b(3);for (int i = 0; i <7; i++) {b.insert(i);}b.show_BTree(b.root());return 0; }輸出結果為
[3]<[1]<[0],[2]>,[5]<[4],[6]>>
更具體點為
2.刪除測試~
#include<iostream> #include "BTree.h"using namespace std; using namespace my_btree;int main() {BTree b(3);for (int i = 0; i <7; i++) {b.insert(i);}b.show_BTree(b.root());cout << endl;for (int i = 0; i < 7; i++) {b.remove(i);b.show_BTree(b.root());cout << endl;}return 0; }輸出結果為
[3]<[1]<[0],[2]>,[5]<[4],[6]>> [3 5]<[1 2],[4],[6]> [3 5]<[2],[4],[6]> [5]<[3 4],[6]> [5]<[4],[6]> [5 6] [6] []五.結語~
??理解b樹,特別是(2,4)樹,是理解紅黑樹的基礎,紅黑樹的代碼見紅黑樹
??碼字不易,此篇高達1w5千字,有任何疑問可以在下方留言;代碼有任何紕漏之處,希望廣大讀者可以進行指正。
??如果你覺得對你有所幫助,可以點個贊。
總結
以上是生活随笔為你收集整理的真c++创建B树(非c with class)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 社工要掌握哪些计算机基本操作,【作为一名
- 下一篇: C++ 用cout输出数字正负号的方法