日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

B树学习总结

發布時間:2024/7/5 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 B树学习总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

B樹

多路搜索樹
當數據規模足夠大的時候, 大部分數據存儲到外存中, 常規的平衡二叉搜索樹將無法滿足需求理由如下:
常規二叉平衡搜索樹每當進行search(),insert(),remove()操作的時候就會進行節點的切換,從而導致大量的IO操作,就此提出了多路搜索,嘗試將原本的多個節點合在一起,用于減少IO操作; 適用于在磁盤與設備上直接進行動態查找;
如下圖二叉搜索及多路搜索拓撲結構:

每個節點中具有多個關鍵碼, 每個關鍵碼又對應著多個引用
它與二叉平衡搜索樹的區別在于,將多個節點進行合并變為關鍵碼,減少IO操作

多路搜平衡索樹
m階B-樹(m>2):

  • 在每個節點中具有不超過m-1個關鍵碼, 并且外部節點(而非葉節點,包含一下為空的節點)的深度均相同;
  • 每個內部節點存有不超過m-1個關鍵碼, 每個關鍵碼所對應的分支n不超過m個引用
  • 內部節點的分支數不能太少,除根節點以外, 所有內部節點都應滿足n+1>=m/2(m/2的值為向上取整), 在非空的B樹中,根節點滿足n>=1
  • 各節點所對應的分支數介于m/2到m之間

如:(2,4) (3,5) (4,7) (4,8)樹

圖中空白方格則是存儲指向下一層的引用(即孩子向量), 存儲數字的方格則是相應的關鍵碼向量

B樹接口

//需要用到向量vector template<typename T>class BTNode{public:BTNode<T>* parent;//父節點vector<T> key;//用于存放關鍵碼向量vector<BTNode<T>*> child;//存放孩子向量,用于保存下一層節點地址的指針BTNode(){parent=null; child.insert(0,null);}BTNode(T e,BTNode<T>* lc=null,BTNode<T>* rc=null){parent=null;key.insert(0,e);//B樹初始化,只存有一個_root,key中保存一個關鍵碼,倆孩子child.insert(0,lc);child.insert(1,rc);if(lc) lc-parent=this;if(rc) rc->parent=this;//孩子向量的大小總是比關鍵碼向量大小多一個}//~BTNode(){key.clear();child.clear();},析構函數待會處理 }; template<typename T>class BTREE{protected:int _size;//關鍵碼總數int _order;//B樹階次BTNode<T>* _root,_hot;//根節點,以及進行search訪問后所指向的節點位置void solveOverflow(BTNode<T>*);//節點發生上溢進行分裂void solveUnderflow(BTNode<T>*);//節點發生下溢進行合并public:BTREE(int order=3):_order(order),size(0);{_root=new BTNode<T>();}//構造函數,默認階數為3// ~BTREE(){if(_root)}int const order(){return _order;}int const size(){return _size;}BTNode<T>* & root(){return _root;}bool empty()const{return !_root;}//判斷樹根是否為空bool insert(const T& e);bool remove(const T& e);BTNode<T>* search(); };

search實現

//對比上圖,思考查找方式 //查找方式與常規的二叉平衡搜索樹一致,需要指出的一點是,在每個關鍵碼中進行查找的時候若關鍵碼較少的時候就可直接使用順序查找策略,而非二分查找 template<typename T>BTNode<T>* BTREE<T>::search(const T& e){BTNode<T>* v=_root;_hot=null;//從根節點查找while(v){for(int i=0;i<key.size();i++){//在當前的關鍵碼向量中順序查找是否有滿足條件的節點if(e==v.key[i])return v;else if(e<v.key[i]){//由于關鍵碼在向量中是順序存放,當所要查找的關鍵碼小于key[i],i所對應的孩子引用位置就是查找的關鍵碼下一層所在的節點位置_hot=v;//記錄查找失敗的節點位置v=v->child[i];}}}return null;//遍歷完所有節點還未找到 } //查找過程中總共需要訪問O(logn)個節點,盡管查找過程中沒有漸進上的意義,但是極大地減少了I/O操作次數,提高了訪問效率

關鍵碼插入

template<typename T>bool BTREE<T>::insert(const T& e){BTNode<T>* v=search(e); if(v) return false;//確認目標節點不存在int r=_hot->key.find(e);//find()在此處簡寫,需要重寫find操作,查找失敗返回失敗的位置(這個位置是e剛好小于該關鍵碼的位置)//在查找失敗的_hot位置處的向量內定位具體e的位置,然后進行插入操作_hot->key.insert(r,e);_hot->child.insert(r+1,null);//孩子向量總是比key向量大1,所以在r+1的位置插入一個空子樹指針_size++;solveOverflow(_hot);return true;//插入成功 }

由于B樹在節點插入,或者節點刪除時會出現當前key向量, child向量不滿足B樹的定義要求, 當關鍵碼向量出現超出當前階數所規定的最大值時,則需要進行分裂處理, 反之小于最小值則需要進行合并處理

上溢
當剛發生上溢的時候, key中恰好有m個關鍵碼, 以m/2為分界,前后兩部分為等長的的子節點, 令m/2升入上一層, 歸入父節點中合適的位置, 將子節點歸入m/2的左右孩子, 如此分裂后, 就可滿足m階B-樹關于節點分支數的條件
例: 以37為界進行分裂上溢操作

如果將上溢的節點添加到父節點一層又導致父節點一層發生上溢操作稱之為上溢的向上傳遞; 但是這種傳遞并不是沒有盡頭, 最遠到達樹根位置, 上溢操作修復之后全樹的高度將上升一層(但這種概率特別低), 整個過程中所做的分裂次數必然不會超過全樹的高度,即O(logn)
根據上溢過程的處理方式, 可實現如下代碼:

template<typename T>void BTREE<T>::solverOverflow(BTNode* v){if(_order >= v->child.size()) return;//遞歸基,當前節點未發生上溢int s=_order/2;//以此為軸點向上做上溢BTNode* u=new BTNode<T>();for(int i=0;i < _order-s-1; i++){u->child.insert(i, v->child.remove(s+1));//將前半段子節點的child節點指針與原來v->child進行分裂操作,一邊復制到新節點u中,一邊對原來的節點的前半段子節點進行刪除操作u->key.insert(i,v->key.remove(s+1));//關鍵碼的操作與child一致//將前部分key,child逐個移動到u中}u->child[_order-s-1]=v->child.remove(s+1);//將m/2處的孩子進行移動到u中,它不能跟著m/2上溢到父節點中if(u->child[0])//如果上述操作執行了,將會把u中的child的父節點進行統一使其不再指向vfor(int j=0 ;j< _order-s; j++)u->child[j]->parent=u;BTNode<T>* p=v->parent;if(!p){//v不具有父節點,那么則要將v中m/2處的關鍵碼通過p上升至_root的位置_root=p=new BTNode<T>();p->child[0]=v;v->parent=p;}int r=1+p->key.find(v->key[0]);//查找key[0]在p->key的位置p->key.insert(r,v->key.remove(s)));//將v中m/2上升p->child.insert(r+1,u);//將u中的child向量插入到p中u->parent=p;//u中關鍵碼與p相連solveOverflow(p);//有必要的話,遞歸進行分裂直到不再上溢為止,最多O(logn)層 }

上述過程中必須要清楚v, v->child, v->parent, u, u->child, u-parent, p, p->child 之間的含義以及聯系:

  • v是要發生上溢的節點, v->child中存儲著每個關鍵碼的左右孩子,v->parent則是整個v節點的父親
  • u中存儲著v前半段的關鍵碼以及child, 由于u是重新申請的空間需要與p重連
  • p中存儲v->parent的信息以及v中上溢m/2關鍵碼的信息,需要與v-parent, v, u, 重連

參考如下圖:

關鍵碼刪除

template<typename T>bool BTREE<T>::remove(const T& e){BTNode<T>* v=search(e); if(!v) return false;//關鍵碼未找到int r=v->key.find(e);//查找關鍵碼在節點v中的位置if(v->child[0]){//判斷是否為葉子節點,若為葉子節點則不需要將r處的v->child轉移到u中BTNode<T>* u=v->child[r+1];while(u->child[0]) u=u->child[0];v->key[r]=u->key[0];v=u;r=0;//將v->child[r+1]移動至u中,完成重連,然后一路向下,找到v的直接后繼,將u->key[0]把v->key[r]覆蓋,使v與v->child[r+1]互換位置,v->child[r+1]代替v成為父節點 }v.key.remove(r);v->child.remove(r+1); _size--;//刪除vsolveUnderflow(v);//有必要進行合并return true; }

下溢
通過上述操作,剛好發生下溢的節點v必須恰好包含m/2-2關鍵碼和m/2-1個分支, 可根據左右兄弟所包含關鍵碼數目,分3中情況處理
1.v的左兄弟存在,且包含至少m/2個關鍵碼, 右兄弟含有m/2-2個關鍵碼:v的右兄弟向父節點p借一個關鍵碼,使之關鍵碼數為m/2-1, 然后父節點向v的左兄弟借一個關鍵碼用于維持原有狀態, 至此局部滿足B-樹的條件,整個過程可以看做是關鍵碼之間的旋轉
2. 該情況與1中的情況相反, 右兄弟存在,且包含至少m/2個關鍵碼, 左兄弟含有m/2-2個關鍵碼, 解決方法與1一致
3. v的左右兄弟存在或者不存在, 或者包含的關鍵碼均不足m/2個, 解決方法可采用向父節點借一個關鍵碼, 然后將v的左右兄弟以及借來的關鍵碼合在一起, 此時關鍵碼總數: (m/2-1)+1+(m/2-2)=m-2<=m-1 注:m/2做向上取整處理
上述方法完成對下溢的修復,但是有可能還會出現下溢的傳遞(與上溢傳遞情況一致); 當傳遞到樹根的位置時,整棵樹的高度將下降一層, 整個過程中至多進行O(logn)次合并操作
通過上述, 有如下代碼

//整個過程中p始終代表v->parent,v代表發生下溢的向量,ls代表p在r處的左孩子,rs代表p在r處的右孩子 template<typename T>void BTREE<T>::solveUnderflow(BTNode<T>* v){if((_order+1)/2 <= v->child.size()) return;//遞歸基,當前節點未發生下溢BTNode<T>* p=v->parent;if(!p){//遞歸基,到達根節點,v代表根節點,此時的根節點已經完成了下溢的合并操作,v的內部關鍵碼key向量已經為空if(!v->key.size() && v->child[0]){//若v中只含有child向量,key向量為空,即可以刪除v,將v與_root進行重連_root=v->child[0]; _root->parent=null;v->child[0]=null]; v.clear();}return;}int r=0; while(p->child[r]!=v) r++;//確定v是p的第幾個孩子,用于后續的關鍵碼之間的三角旋轉//情況1: 向左兄弟借關鍵碼if(0<r){//v不是p的第一個孩子,說明左兄弟必然存在BTNode<T>* ls=p->child[r-1];if((_order+1)/2 < ls->child.size()){//左兄弟快發生上溢,則向左兄弟借出一個節點v->key.insert(0,p->key[r-1]);//先將p的關鍵碼給v,再將v左兄弟的關鍵碼給p//p借出后無需進行remove,直接通過v左兄弟的關鍵碼將其覆蓋p->key[r-1]=ls->key.remove(ls->size()-1);//由于關鍵碼的轉移,所以要將轉移的關鍵碼所對應的child進行轉移v->child.insert(0,ls->child.remove(ls->child.size()-1));if(v->child[0])//判斷轉移后的孩子是否為空,不為空,則進行重連v->child[0]->parent=v;return;//完成當前層的下溢操作}}//情況2: 向右兄弟借關鍵碼if(p->child.size()-1>r)//v不是p的最后一個孩子,說明右兄弟必然存在BTNode<T>* rs=p->child[r+1];if((_order+1)/2 < rs->child.size()){v.key.insert(v->key.size(),p.key[r])p->key[r]=rs->key.remove(0);v->child.insert(v->child.size(),rs->child.remove(0));if(v->child[v->key.size()-1])v->child[v->key.size()-1]->parent=v;return;}//情況3:向p中借出r位置的節點,使r的左右孩子進行合并操作if(0<r){//左兄弟存在,與左兄弟進行合并BTNode<T>* ls=p->child[r-1];//左兄弟存在,將p中r處的關鍵碼轉移至ls中ls->key.insert(ls->key.size(),p->key.remove(r-1));if(ls->child[ls->child.size()-1])//將v最左側的孩子轉移到ls中,做ls最右側的孩子while(!v->key.empty()){//合并過程,將v中的信息全部轉移至ls中ls->key.insert(ls->key.size(),v->key.remove(0));//一邊對ls進行插入,一邊對v進行刪除ls->child.insert(ls->child.size(),v->child.remove(0));if(ls->child[ls->child.size()-1])//判斷轉移過來的孩子是否為空,不為空則進行child->parent與ls的重連ls->child[ls->child.size()-1]->parent=ls;}v.clear();}else{//右兄弟存在,與右兄弟合并BTNode<T>* rs=p->child[r+1];rs->key.insert(0,p->key.remove(r+1));//將p中r+1處的關鍵碼轉移至右兄弟中p->child.remove(r);//刪除p中r+1處的childrs->child.insert(0,v->child.remove(v->child.size()-1));//孩子的轉移if(rs->child[0])//此處做法與右兄弟中的操作一致rs->child[0]->parent=rs;while(!v->empty()){//將v中關鍵碼,孩子向量一一轉至到rs中rs->key.insert(0,v->key.remove(v->key.size()-1));rs->child.insert(0,v->child.remove(v->child.size()-1));if(rs->child[0])//rs的孩子存在,進行重連rs->child[0]->parent=rs;}v.clear();}solveUnderflow(p);//上升一層,如果有必要進行繼續分裂return; }

上述過程如下圖:

刪除操作的復雜度依然可以保證在O(logn)內,單次操作平均只需做常數次節點的合并

總結

以上是生活随笔為你收集整理的B树学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。