(一)B-树的定义:
在大规模数据查找中,大量的数据储存在外存磁盘,查找是要从磁盘读取数据,磁盘I/O读写的基本操作单位是块(block)。磁盘上的数据有一个三维地址唯一标识:柱面号,盘面号,块号
磁盘的读写要一下三个步骤(1).根据柱面号使磁头移动到所要的柱面上,称为定位(2).根据盘面号来确定盘面上的磁道(3)盘面确定后盘片开始旋转,将指定块号的磁道端记下;
数据的查找由三个时间端组成:(1)查找时间(2)等待时间(3)传输时间;
I/O时间主要花在查找时间上,当使用二叉树查找时,若要找值为8的结点,则要访问四次结点,即为结点所在的高度,增加了读写,由于二叉树的结点分布在不同的数据块上,这样就会增加程序执行时间
若减小树的高度,则只要访问三次结点即可;
B树也是一种平衡多路查找树,其中树中结点的最大的孩子数目称为树的阶m
- 树中每个结点最多有m个子树
- 根结点最少有两个子树
- 除了根结点以外的非叶子结点最少有m/2个子树
- 所有叶子结点都出现在同一层
- 其中有n+1个子树的非叶子结点的信息如下图:
含有n个关键字的m阶B-树的高度h<= log[m/2] { (n+1)/2 }+1
代码实现:
template<class Key>
class BNode{
public:
int keynum; //结点中关键字的数量
BNode<Key>*parent; //指向父结点的指针
Key key[m+1]; //存储关键字的数组,其中m为结点中B-树的阶,0号单元未使用
BNode*ptr[m+1]; //存储指针的数组,0号未使用
};
(二)B-树的查找
B-树的查找是要先确定待查找记录所在的结点,然后在在结点中查找记录;
对B树的查找包含两个操作:
1:在B-树中查找结点,在结点中寻找关键字,此操作在磁盘上进行
2:在磁盘上找到指针p所指的结点,将结点信息读入内存,利用顺序查找或二分查找进行查询;
要查找的关键字的结点在B-树中的最大层数是决定B-树查找效率的首要因素
最坏的情况是要经过log[m/2] { (n+1)/2 }+1;
template<class Key>
class Result{
public:
BNode<Key>*r; //指向找到的结点
int i; //记录所查到的关键字在结点的位置
bool flag; //是否找到关键字的标志
Result(BNode<Key>*rr,int ii,int flagg){ //构造函数
r=rr;
i=i;;
flag=flagg;
}
};
template<class Key>
Result<Key>BTree<Key>::Search(const Key x){
//在m阶B-树中查找关键字,返回1结果result,查找成功返回flag=1;
//指针r指向的结点中第i个关键字就是所查的关键字x,不成功返回flag=0;
Result<key>ret(NULL,0,false);
BTree<Key>*p=root; //p指向待查结点
BTree<Key>*q=NULL; //q指向p的双亲结点
int i=0;
while(p&&!ret.flag){
for(i=1;i<=p->keynum;i++){ //找到待查记录的结点
if(p->key[i/>=x) //在p->key[1,2,...keynum]中查找
break; //i使得:p->key[i-1]<=x<p->key[i]
}
if(i<=p->keynum&&p->key[i]==x){ //在结点中查找所查关键字x
ret.falg=true; //查找成功
ret.r=p;
ret.i=i;
}
else if(i=1) { //转向第一个子树
p;
p=p->ptr[1];
}
else{ //转向第i-1棵子树
q=p;
p=p->ptr[i-1];
}
}
return ret;
}
(三)B-树的插入:
可分为三种情形:
-
1.将关键字插入叶子结点后,叶子结点中的关键字个数不超过m-1,则直接插入即可;
-
2.将关键字插入叶子结点的关键字个数超过m-1,此时,要分裂叶子结点,创建一个新的叶子结点,将关键字个数超过m-1的叶子结点中一半关键字移动到新叶子结点中,并将新叶子结点合并到B-树;再把中间的关键字提升到双亲结点中,同时在双亲结点中设置一个指向新叶子结点的指针;
-
3.将关键字插入后,B-树的根结点的关键字个数超过m-1,则必须分裂根结点,并将根结点中的关键字提升到新建的根结点中;
第一次分裂:
第二次分裂:
(四)B-树的删除:
B-树的删除很大程度是插入过程的逆操作;首先要找到删除结点的位置,若结点为底层叶子结点,删除后关键字数目不小于m/2;则直接删除即可;否则要进行合并操作;
-
(1)若被删除的关键字所在的结点中关键字的数目不小于m/2,则只要从该结点删除即可:
-
(2)若被删结点的关键字数目为m/2-1,但与该结点相邻的其中一个兄弟结点中关键字数目大于m/2-1;则要将右兄弟中最小结点,或左兄弟中最小结点移至双亲结点,双亲结点中间关键字下移至兄弟结点;
-
(3)若被删结点其左右兄弟结点中关键字数目均为m/2-1;在删去关键字后,将该结点中剩余关键字和指针以及双亲结点划分该结点和左兄弟结点的关键字一起合并到其左兄弟结点中,(或右兄弟结点)