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])//统一将孩子的父节点指向u
for(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;//即没有要删除的关键字e
int 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;
}