本文介绍的数据结构英文是B-tree,中文写作B-树,其中 – 并不是减号,而是连接符,读作B树。
B-树是一种平衡搜索树,但它的每个结点包含的元素可以多于2个,因此并不是严格意义上的二叉树。
B-树的结点类似如下:
这可以看做二叉搜索树通过多层合并得到的
相比与二叉树,B树显得更矮,更胖。它的每个结点包含多个数据,这特别适合于对外存的访问。由于硬盘等设备访问速度和内存相比非常慢,而从硬盘读取1个数据和读取10个数据用时几乎一样,这非常适合使用B-树这种结构。每个结点数据很多,就可以从磁盘依次取出大量数据,矮的特点可以减少磁盘的IO次数。
定义
B-树的所有叶子结点都位于同一深度,同时对每个结点的分支数也有限制。一个m阶B-树满足
1. 每个结点的分支数最多m个,因此每个结点元素个数最多m-1个。
2. 除根节点外,每个结点的分支树最少 ⌈m/2⌉ 个(向上取整)。根节点例外,根节点可以只有2个分支。
这样,m阶B-树也可以叫做 (⌈m/2⌉ , m) -树 。
查找
B树的查找和二分查找类似,只不多这里是多分的。详见代码。
插入
插入元素总是插入到叶节点,如果插入后节点数多余m-1,则要修复上溢。
发生上溢时,有m个结点,则分裂为左右两部分和中间轴点,左半部分有 m/2 个结点,中间1个结点,有半部分有⌈m/2⌉-1 个结点。 中间节点上升一层。父节点可能因此继续上溢。
删除
通过和中序后继交换,实现删除元素总是位于叶子。删除后如果分支数小于要求,则称下溢。
发生下溢时,可以先看左右兄弟是否可借出一个孩子
如果左右兄弟都不能借出,也就是都具有最少的结点,则可以进行合并。
C++实现
BTree.h
#include "Vector.h"
#include "Queue.h"
#define BTNodePosi(T) BTNode<T>*
template <typename T>
class BTNode
{
public:
BTNodePosi(T) parent;
Vector<T> key;
Vector<BTNodePosi(T)> child;
BTNode() : parent(NULL) { child.push_back(NULL); }
BTNode(T e, BTNodePosi(T) lchild = NULL, BTNodePosi(T) rchild = NULL);
};
template <typename T>
BTNode<T>::BTNode(T e, BTNodePosi(T) lchild, BTNodePosi(T) rchild)
{
parent = NULL;
key.insert(e);
child.insert(0, lchild);
child.insert(1, rchild);
if (lchild) lchild->parent = this;
if (rchild) rchild->parent = this;
}
template <typename T>
class BTree
{
protected:
int _size;
int _order;
BTNodePosi(T) _root; //根节点
BTNodePosi(T) _hot;
void solveOverflow(BTNodePosi(T));
void solveUnderflow(BTNodePosi(T));
public:
BTNodePosi(T) search(const T & e);
bool insert(const T & e);
bool remove(const T & e);
void display();
BTree(int order) : _size(0), _order(order), _root(NULL), _hot(NULL) { }
};
template <typename T>
BTNodePosi(T) BTree<T>::search(const T & e)
{
BTNodePosi(T) p = _root;
_hot = NULL;
//反复循环直到到达外部结点 或者找到时直接return
while (p)
{
//向量的search接口返回不大于e的最后一个元素
int n = p->key.search(e);
//已经找到
if (p->key[n] == e)
return p;
//否则 下降一层
_hot = p;
p = p->child[n+1];
}
return NULL;
}
template <typename T>
bool BTree<T>::insert(const T & e)
{
//不允许重复元素
if (search(e)) return false;
//search执行后 _hot指向的是最终结点的父节点(search失败时指向外部结点的父节点 也就是叶子节点)
BTNodePosi(T) p = _hot;
if (!p)
{ //树为空的情况
p = new BTNode<T>;
_root = p;
p->key.push_back(e);
p->child.push_back(NULL);
++_size;
return true;
}
//树不为空时,将e插入到叶节点的适当位置
int n = p->key.search(e);
p->key.insert(n+1, e);
p->child.push_back(NULL); //叶节点的孩子是外部结点 全部为NULL 因此直接插入到最后面
++_size;
BTNodePosi(T) par;
//插入结点后 从该结点到其祖先结点 依次检测是否上溢并修复
while (p && p->child.size() > _order)
{
par = p->parent;
solveOverflow(p);
p = par;
}
return true;
}
template <typename T>
bool BTree<T>::remove(const T & e)
{
//找到e的位置
BTNodePosi(T) p = search(e);
if (!p) return false;
int n = p->key.search(e);
//若e所在结点不是叶子结点 用它的中序意义后继替代它 之后只需删除位于叶子节点的那个后继
if (p->child[0])
{
BTNodePosi(T) q = p->child[n+1];
while (q->child[0])
{
q = q->child[0];
}
p->key.put(n, q->key[0]);
n = 0;
p = q;
}
//现在待删除节点都位于叶子结点 开始删除
p->key.remove(n);
p->child.remove(n);
--_size;
//删除元素后解决下溢问题
if (p != _root && p->child.size() < (_order+1)/2)
{
solveUnderflow(p);
}
return true;
}
//解决上溢问题 即pn的分支数超过了B树的阶_order
template <typename T>
void BTree<T>::solveOverflow(BTNodePosi(T) pn)
{
BTNodePosi(T) p = pn->parent;
int n = (_order-1)/2; //n是最少结点数
//下面把pn结点分裂为lc和rc两个结点和中间一个只有一个元素的结点
//lc [0,n) 中间结点 [n] rc [n+1,size]
int i;
//左半部分
BTNodePosi(T) lc = new BTNode<T>;
for (i = 0; i < n; ++i)
{
lc->key.push_back(pn->key[i]);
if (i == 0)
lc->child.put(0, pn->child[0]);
else
lc->child.push_back(pn->child[i]);
}
lc->child.push_back(pn->child[i]);
for (i = 0; i < lc->child.size(); ++i)
if (lc->child[i]) lc->child[i]->parent = lc;
//右半部分
BTNodePosi(T) rc = new BTNode<T>;
for (i = n+1; i < pn->key.size(); ++i)
{
rc->key.push_back(pn->key[i]);
if (i == n+1)
rc->child.put(0, pn->child[i]);
else
rc->child.push_back(pn->child[i]);
}
rc->child.push_back(pn->child[i]);
for (i = 0; i < rc->child.size(); ++i)
if (rc->child[i]) rc->child[i]->parent = rc;
if (!p)
{ //上溢结点是树根节点的情况
p = new BTNode<T>;
_root = p;
}
//把中间节点插入到pn的父节点 其左右指针指向lc和rc两部分
lc->parent = p;
rc->parent = p;
int n1 = p->key.search(pn->key[n]);
p->key.insert(n1+1, pn->key[n]);
p->child.put(n1+1, lc);
p->child.insert(n1+2, rc);
delete pn;
}
template <typename T>
void BTree<T>::solveUnderflow(BTNodePosi(T) q)
{
//没有下溢则退出
if (q->child.size() >= (_order+1)/2) return;
if (q == _root)
{
if (q->key.empty())
{
//树根节点删除了最后一个元素变为空树的情况
_root = q->child[0];
delete q;
}
return; //树根节点最少可以是1个结点 没有下溢的问题
}
BTNodePosi(T) p = q->parent;
int n;
for (n = 0; n < p->child.size(); ++n)
{
if (p->child[n] == q)
break;
}
BTNodePosi(T) lc;
BTNodePosi(T) rc;
//q的左兄弟可以借出结点
if (n > 0 && p->child[n-1]->child.size() > (_order+1)/2)
{
lc = p->child[n-1];
q->key.insert(0, p->key[n-1]);
q->child.insert(0, lc->child.last());
if (q->child[0]) q->child[0]->parent = q;
p->key.put(n-1, lc->key.last());
lc->key.remove(lc->key.size() - 1);
lc->child.remove(lc->child.size() - 1);
return;
}
//q的右兄弟可以借出结点
if (n < p->child.size()-1 && p->child[n+1]->child.size() > (_order+1)/2)
{
rc = p->child[n+1];
q->key.push_back(p->key[n]);
q->child.push_back(rc->child[0]);
if (rc->child[0]) rc->child[0] = q;
p->key.put(n, rc->key[0]);
rc->key.remove(0);
rc->child.remove(0);
return;
}
//q的左右兄弟都不能借出 则合并
if (n > 0)
{
//有左兄弟时,与左兄弟合并
lc = p->child[n-1];
lc->key.push_back(p->key[n-1]);
int i;
for (i = 0; i < q->key.size(); ++i)
{
lc->key.push_back(q->key[i]);
lc->child.push_back(q->child[i]);
}
lc->child.push_back(q->child[i]);
p->key.remove(n-1);
p->child.remove(n);
delete q;
}
else
{
//否则和右兄弟合并
rc = p->child[n+1];
q->key.push_back(p->key[n]);
int i;
for (i = 0; i < rc->key.size(); ++i)
{
q->key.push_back(rc->key[i]);
q->child.push_back(rc->child[i]);
}
q->child.push_back(rc->child[i]);
p->key.remove(n);
p->child.remove(n+1);
delete rc;
}
//合并后,父节点的分支数减少,继续解决父节点的下溢问题
solveUnderflow(p);
}
template <typename T>
class MyPrint
{
public:
void operator()(T e)
{
cout << e << " ";
}
};
template <typename T>
void BTree<T>::display()
{
if (!_root) return;
MyPrint<T> visit;
Queue<BTNodePosi(T)> q;
BTNodePosi(T) x = _root;
BTNode<T> endl1;
q.enqueue(x);
q.enqueue(&endl1);
int i;
while (!q.empty())
{
x = q.dequeue();
if (!x) continue;
if (x == &endl1)
{
if (q.size() > 0)
q.enqueue(&endl1);
printf("\n");
continue;
}
printf("#");
x->key.travser(visit);
printf("#");
for (i = 0; i < x->child.size(); ++i)
{
q.enqueue(x->child[i]);
}
printf("----");
}
}
BTree.cpp
#include <iostream>
using namespace std;
#include "BTree.h"
int main()
{
BTree<int> bt(4);
bt.insert(21);
bt.insert(48);
bt.insert(72);
bt.insert(12);
bt.insert(22);
bt.insert(50);
bt.insert(34);
bt.insert(42);
bt.insert(60);
bt.insert(67);
bt.insert(89);
bt.insert(13);
bt.display();
cout << "delete 50\n\n";
bt.remove(50);
bt.display();
cout << "delete 72\n\n";
bt.remove(72);
bt.display();
cout << "delete 89\n\n";
bt.remove(89);
bt.display();
cout << "delete 67\n\n";
bt.remove(67);
bt.display();
cout << "delete 48\n\n";
bt.remove(48);
bt.display();
cout << "delete 60\n\n";
bt.remove(60);
bt.display();
cout << "delete 42\n\n";
bt.remove(42);
bt.display();
cout << "delete 22\n\n";
bt.remove(22);
bt.display();
cout << "delete 34\n\n";
bt.remove(34);
bt.display();
cout << "delete 21\n\n";
bt.remove(21);
bt.display();
// int i;
// for (i = 0; i < 30; ++i)
// bt.insert(i);
printf("233");
return 0;
}





支付宝打赏
微信打赏