B树

来源:互联网 发布:js如何获取鼠标位置 编辑:程序博客网 时间:2024/05/16 19:01

B树:严格来讲,B树并非BST,但从逻辑上讲,仍然等效于BST。

引入B树的目的:弥补不同存储设备访问速度的巨大差异,实现高效的I/O。普遍应用在数据库和文件系统中。B树结构非常适宜于在相对更小的内存中,实现对大规模数据的高效操作。

特点:搜索每下降一层,都以“大节点”为单位从外村读取一组(而不是单个)关键码。更为重要的是,这组关键码在逻辑上和物理上都彼此相邻,故可以批量方式从外村中一次性读取,且所需时间与读取单个关键码几乎一样。

注意:1)B树新建节点发生于插入操作的时候,当要插入的节点已经满了,需要分裂出一个新的节点,此时增加了一个新节点。而删除大节点发生在删除一个节点,且当前大节点中元素个数小于要求,且左右子树也小于要求,此时合并当前节点和其左子树,或右子树,用其父节点中的一个节点来连接;另一种情况是从根节点借了一个节点,此时根节点已经没有节点了,删除根节点。

2)B树高度永远保持平衡,即对于大节点来说,B树的高度永远是平衡的,任一节点的左子树和右子树的高度平衡,这是因为B树的高度的增加完全是由于根节点的分裂而导致的,而高度的减少,也是由于根节点的删除导致的,操作都在根节点,因此永远是平衡的,但是不用作普通平衡搜索树的原因是保持其平衡的代价太高了,合并和分裂一个节点的复杂度高。

3)阶数表示B数节点可以拥有的最大的孩子数,m阶B树表示B树中的“大节点”除了根节点外,其它“大节点”必须至少有[m/2]([]表示向上取整)个子节点,最多有m个子节点。根最少有2个子节点,最多有m个子节点。

4)每个节点的关键字个数有个上界和下界。用一个被称为B树的最小度数的固定整数t来表示这个界。如果用度来度量B树,则度为t的B树,根节点最少为1,最多为2t-1,其它节点的度最少为t-1,最多为2t-1个关键字。孩子数根节点最少为2,最多为2t,其它节点最少为t,最多为2t。

下面的代码以3)定义,且m=5。

#include<iostream>#include<vector>#include<algorithm>using namespace std;//B树中大节点的定义struct BTNode{vector<int> key;BTNode *parent;vector<BTNode *> child;BTNode(){parent=nullptr;}BTNode(int e,BTNode * lc=nullptr,BTNode * rc=nullptr){parent=nullptr;key.insert(key.begin(),e);child.insert(child.begin(),lc);child.insert(child.begin()+1,rc);if(lc)//只有当左孩子不是空,才能给它赋值父节点lc->parent=this;if(rc)//只有当右孩子不是空,才能给它赋值父节点rc->parent=this;}~BTNode(){cout<<"调用析构函数"<<endl;}};class BTree{private:int _size;int _order;//存放B树的阶数,即B树中节点最多能有几个分支,至少为3,当为2时是二叉树BTNode * _root;BTNode *_hot;//search接口最后访问的非空(除非树空)的节点位置void solveOverFlow(BTNode *);void solveUnderFlow(BTNode *);int find(BTNode *v,int e);void release(BTNode *root);public:BTree(int order=3):_size(0),_order(order){_root=new BTNode();}~BTree(){release(_root);}int order()const {return _order;}int size()const {return _size;}BTNode *root()const {return _root;}bool empty()const {return !_root;}BTNode * search(int e);bool insert(int e);bool remove(int e);};int BTree::find(BTNode *v,int e){int k=-1;for(int i=0;i<v->key.size();i++){if(v->key[i]==e)return i;if(v->key[i]<e)k=i;}return k;}BTNode * BTree::search(int e){BTNode *v=_root;_hot=_root;//从根节点开始while(v){int r=find(v,e);//先在本节点key中寻找if(r<0 && v->key.size()==0)break;if(r>=0 && (v->key[r]==e))return v;_hot=v;v=v->child[r+1];//如果没有找到则深入到下一层孩子节点中}return nullptr;}bool BTree::insert(int e){BTNode *v=search(e);if(v)return false;int r=1+find(_hot,e);_hot->key.insert(_hot->key.begin()+r,e);//对于每一个B树来说,只会调用一次,即在第一次插入节点的时候。其它时候增加的BTNode都是在solveOverFlow函数中if(r==0 && _hot->key.size()==1)//只有在第一个节点的时候插入时,才需要同时插入两个NULL的孩子节点_hot->child.insert(_hot->child.begin()+r,nullptr);//其它时候即使要插入的e应该在key[0]由于孩子为NULL,  //因此插入到第0个和插入到第一个是一样的。_hot->child.insert(_hot->child.begin()+r+1,nullptr);//此时插入的节点的兄弟的孩子也都是空的,即插入到叶节点_size++;solveOverFlow(_hot);return true;}void BTree::solveOverFlow(BTNode *v){if(_order>=v->child.size())return ;//此时不需要进行上溢处理int s=_order>>1;BTNode *u=new BTNode();for(int i=0;i<_order-s-1;i++){//如下是把u指向了v的右侧的节点u->child.insert(u->child.begin()+i,*(v->child.begin()+s+1));//这里从s+1开始,是要保证左孩子比右孩子节点不少,v->child.erase(v->child.begin()+s+1);//使左孩子能够提出一个p到父节点u->key.insert(u->key.begin()+i,*(v->key.begin()+s+1));v->key.erase(v->key.begin()+s+1);}u->child.push_back(v->child.back());//最后一个孩子单独放,因为key比child少一个节点v->child.pop_back();if(u->child[0])//统一将孩子的父节点指向ufor(int i=0;i<u->child.size();i++)u->child[i]->parent=u;BTNode *p=v->parent;if(!p)//当前这个大节点是根节点{_root=p=new BTNode();//注意这里是唯一一处增加节点的地方!!!p->child.push_back(v);//插入孩子节点,值在下面插入v->parent=_root;}int r=1+find(p,v->key[0]);//p中指向v的指针p->key.insert(p->key.begin()+r,v->key.back());//轴点的关键码上升v->key.pop_back();//将关键码从左子树中删除,此时左子树已经是纯粹的左子树了p->child.insert(p->child.begin()+r+1,u);u->parent=p;solveOverFlow(p);}void BTree::release(BTNode *root){//递归地进行析构if(root==nullptr)return ;for(int i=0;i<root->child.size();i++)release(root->child[i]);delete root;}bool BTree::remove(int e){BTNode *v=search(e);if(!v)return false;//即没有要删除的关键字eint r=find(v,e);if(v->child[0]){BTNode *u=v->child[r+1];//在右子树中找到和e中序遍历相邻的元素while(u->child[0])u=u->child[0];v->key[r]=u->key[0];//交换关键码v=u;r=0;}v->key.erase(v->key.begin());//删除键值v->child.erase(v->child.begin());//删除孩子,这个孩子是空的,因为是叶子节点_size--;solveUnderFlow(v);return true;}void BTree::solveUnderFlow(BTNode *v){if(((_order+1)>>1)<=v->child.size())return ;//当前节点并未下溢,BTNode *p=v->parent;if(!p){//当前的v是根节点,但是此时的根节点由于被下面节点拉先来一个关键字而成空的了, //但是其孩子节点0还不是空的if(!v->key.size() && v->child[0]){_root=v->child[0];_root->parent=nullptr;delete v;}return ;}int r=0;while(p->child[r]!=v)//确定v是p的第r个孩子,此时p有可能不含关键码,因此不能用关键码来查找r++;if(r>0)//有左兄弟{BTNode *ls=p->child[r-1];if(((_order+1)>>1)<ls->key.size())//左兄弟足够胖,能够借出一个关键码{v->key.insert(v->key.begin(),p->key[r]);v->child.insert(v->child.begin(),ls->child.back());p->key[r]=ls->key.back();ls->key.pop_back();ls->child.pop_back();if(v->child[0])v->child[0]->parent=v;return ;}}if(r<p->child.size()-1)//即有右兄弟{BTNode *rs=p->child[r+1];if(((_order+1)>>1)<rs->key.size()){v->key.push_back(p->key[r]);v->child.push_back(rs->child.front());p->key[r]=rs->key.back();rs->key.erase(rs->key.begin());rs->child.erase(rs->child.begin());if(v->child.back())v->child.back()->parent=v;return ;}}if(r>0)//和左兄弟合并{BTNode *ls=p->child[r-1];ls->key.push_back(p->key[r-1]);p->key.erase(p->key.begin()+r-1);p->child.erase(p->child.begin()+r);//把右边孩子给删除ls->child.push_back(v->child.front());//把第一个孩子过继给左兄弟v->child.erase(v->child.begin());if(ls->child.back())ls->child.back()->parent=ls;while(!v->key.empty()){ls->key.push_back(v->key.front());v->key.erase(v->key.begin());ls->child.push_back(v->child.front());v->child.erase(v->child.begin());if(ls->child.back())ls->child.back()->parent=ls;}delete v;//这里只是把v这个节点删除,而其孩子都过继给了其兄弟,因此不用调用release函数}else//与其右兄弟合并{BTNode *rs=p->child[r+1];rs->key.insert(rs->key.begin(),p->key[r]);p->key.erase(p->key.begin()+r);p->child.erase(p->child.begin()+r);//把左边子树给删除rs->child.insert(rs->child.begin(),v->child.back());v->child.pop_back();if(rs->child.front())rs->child.front()->parent=rs;while(!v->key.empty()){rs->key.insert(rs->key.begin(),v->key.back());v->key.pop_back();rs->child.insert(rs->child.begin(),v->child.back());v->child.pop_back();if(rs->child.front())rs->child.front()->parent=rs;}delete v;}solveUnderFlow(p);return ;}int main(){    BTree bt(5);bt.insert(1);bt.insert(3);bt.insert(2);bt.insert(4);bt.insert(5);bt.insert(6);bt.insert(7);for(int i=8;i<18;i++)bt.insert(i);for(int i=1;i<18;i++)bt.remove(i);system("pause");return 0;}


0 0
原创粉丝点击