1. 头文件
需要先引入C++ 数据结构学习 ---- 二叉树_孤城寻欢的博客-CSDN博客的BNode头文件
1.1 Btree
template <typename T> class BTree { //B-树模板类
protected:
int _size; //存放的关键码总数
int _m; //B-树的阶次,至少为3——创建时指定,一般不能修改
BTNodePosi<T> _root; //根节点
BTNodePosi<T> _hot; //BTree::search()最后访问的非空(除非树空)的节点位置
void solveOverflow(BTNodePosi<T>); //因插入而上溢之后的分裂处理
void solveUnderflow(BTNodePosi<T>); //因删除而下溢之后的合并处理
public:
BTree(int m = 3) : _m(m), _size(0) //构造函数:默认为最低的3阶
{
_root = new BTNode<T>();
}
~BTree() { if (_root) release(_root); } //析构函数:释放所有节点
int const order() { return _m; } //阶次
int const size() { return _size; } //规模
BTNodePosi<T>& root() { return _root; } //树根
bool empty() const { return !_root; } //判空
BTNodePosi<T> search(const T& e); //查找
bool insert(const T& e); //插入
bool remove(const T& e); //删除
}; //BTree
1.2 Dice
#include <ctime>
#include <Stdlib.h>
static int dice(int range) { return rand() % range; } //取[0, range)中的随机整数
static int dice(int lo, int hi) { return lo + rand() % (hi - lo); } //取[lo, hi)中的随机整数
static float dice(float range) { return rand() % (1000 * (int)range) / (float)1000.; }
static double dice(double range) { return rand() % (1000 * (int)range) / (double)1000.; }
static char dice() { return (char)(32 + rand() % 96); }
2. 相关函数
2.1 插入函数
//将关键码e插入B树中
template <typename T> bool BTree<T>::insert(const T& e) {
BTNodePosi<T> v = search(e); if (v) return false; //确认目标节点不存在
Rank r = _hot->key.search(e); //此处的search来自Vector接口,在节点_hot的有序关键码向量中查找合适的插入位置
_hot->key.insert(r + 1, e); //将新关键码插至对应的位置
_hot->child.insert(r + 2, NULL); //创建一个空子树指针
/*存在另一种方法,插入空子树*/
//此时的节点的叶节点,所有的子树也是空的,所以直接在child向量末尾加一个空子树与_hot->child.insert(r + 2, NULL)是等价的
_size++; //更新全树规模
solveOverflow(_hot); //如有必要,需做分裂
return true; //插入成功
}
2.2 删除函数
//从BTree树中删除关键码e
template <typename T> bool BTree<T>::remove(const T& e) {
BTNodePosi<T> v = search(e); if (!v) return false; //确认目标关键码存在
Rank r = v->key.search(e); //确定目标关键码在节点v中的秩(由上,肯定合法)
if (v->child[0]) { //若v非叶子,则e的后继必属于某叶节点
BTNodePosi<T> u = v->child[r + 1]; //在右子树中一直向左,即可
while (u->child[0]) u = u->child[0]; //找出e的后继
v->key[r] = u->key[0]; v = u; r = 0; //并与之交换位置
} //至此,v必然位于最底层,且其中第r个关键码就是待删除者
v->key.remove(r); v->child.remove(r + 1); _size--; //删除e,以及其下两个外部节点之一
solveUnderflow(v); //如有必要,需做旋转或合并
return true;
}
2.3 搜索函数
//在B-树中查找关键码e
template <typename T> BTNodePosi<T> BTree<T>::search(const T& e) {
BTNodePosi<T> v = _root; _hot = NULL; //从根节点出发
while (v) { //逐层查找
Rank r = v->key.search(e); //在当前节点中,找到不大于e的最大关键码
if ((0 <= r) && (e == v->key[r])) return v; //成功:在当前节点中命中目标关键码
_hot = v; v = v->child[r + 1]; //否则,转入对应子树(_hot指向其父)——需做I/O,最费时间
} //这里在向量内是二分查找,但对通常的_m可直接顺序查找
return NULL; //失败:最终抵达外部节点
}
2.4 上溢函数
//关键码插入后若节点上溢,则做节点分裂处理
template <typename T> void BTree<T>::solveOverflow(BTNodePosi<T> v) {
while (_m <= v->key.size()) { //除非当前节点并未上溢
Rank s = _m / 2; //轴点(此时应有_m = key.size() = child.size() - 1)
BTNodePosi<T> u = new BTNode<T>(); //注意:新节点已有一个空孩子
for (Rank j = 0; j < _m - s - 1; j++) { //v右侧_m-s-1个孩子及关键码分裂为右侧节点u
u->child.insert(j, v->child.remove(s + 1)); //逐个移动效率低
u->key.insert(j, v->key.remove(s + 1)); //此策略可改进
}
u->child[_m - s - 1] = v->child.remove(s + 1); //移动v最靠右的孩子
if (u->child[0]) //若u的孩子们非空,则
for (Rank j = 0; j < _m - s; j++) //令它们的父节点统一
u->child[j]->parent = u; //指向u
BTNodePosi<T> p = v->parent; //v当前的父节点p
if (!p) { _root = p = new BTNode<T>(); p->child[0] = v; v->parent = p; } //若p空则创建之
Rank r = 1 + p->key.search(v->key[0]); //p中指向v的指针的秩
p->key.insert(r, v->key.remove(s)); //轴点关键码上升
p->child.insert(r + 1, u); u->parent = p; //新节点u与父节点p互联
v = p; //上升一层,如有必要则继续分裂——至多O(logn)层
} //while
}
2.5 下溢函数
//关键码删除后若节点下溢,则做节点旋转或合并处理
template <typename T> void BTree<T>::solveUnderflow(BTNodePosi<T> v) {
while ((_m + 1) / 2 > v->child.size()) { //除非当前节点并未下溢
BTNodePosi<T> p = v->parent;
if (!p) { //已到根节点(两个孩子即可)
if (!v->key.size() && v->child[0]) {
//但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
/*DSA*/printf("collapse\n");
_root = v->child[0]; _root->parent = NULL; //这个节点可被跳过
v->child[0] = NULL; release(v); //并因不再有用而被销毁
} //整树高度降低一层
return;
}
Rank r = 0; while (p->child[r] != v) r++;
//确定v是p的第r个孩子——此时v可能不含关键码,故不能通过关键码查找
//另外,在实现了孩子指针的判等器之后,也可直接调用Vector::find()定位
/*DSA*/printf("\nrank = %d", r);
// 情况1:向左兄弟借关键码
if (0 < r) { //若v不是p的第一个孩子,则
BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
if ((_m + 1) / 2 < ls->child.size()) { //若该兄弟足够“胖”,则
/*DSA*/printf(" ... case 1\n");
v->key.insert(0, p->key[r - 1]); //p借出一个关键码给v(作为最小关键码)
p->key[r - 1] = ls->key.remove(ls->key.size() - 1); //ls的最大关键码转入p
v->child.insert(0, ls->child.remove(ls->child.size() - 1));
//同时ls的最右侧孩子过继给v
if (v->child[0]) v->child[0]->parent = v; //作为v的最左侧孩子
return; //至此,通过右旋已完成当前层(以及所有层)的下溢处理
}
} //至此,左兄弟要么为空,要么太“瘦”
// 情况2:向右兄弟借关键码
if (p->child.size() - 1 > r) { //若v不是p的最后一个孩子,则
BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
if ((_m + 1) / 2 < rs->child.size()) { //若该兄弟足够“胖”,则
/*DSA*/printf(" ... case 2\n");
v->key.insert(v->key.size(), p->key[r]); //p借出一个关键码给v(作为最大关键码)
p->key[r] = rs->key.remove(0); //rs的最小关键码转入p
v->child.insert(v->child.size(), rs->child.remove(0));
//同时rs的最左侧孩子过继给v
if (v->child[v->child.size() - 1]) //作为v的最右侧孩子
v->child[v->child.size() - 1]->parent = v;
return; //至此,通过左旋已完成当前层(以及所有层)的下溢处理
}
} //至此,右兄弟要么为空,要么太“瘦”
// 情况3:左、右兄弟要么为空(但不可能同时),要么都太“瘦”——合并
if (0 < r) { //与左兄弟合并
/*DSA*/printf(" ... case 3L\n");
BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
ls->key.insert(ls->key.size(), p->key.remove(r - 1)); p->child.remove(r);
//p的第r - 1个关键码转入ls,v不再是p的第r个孩子
ls->child.insert(ls->child.size(), v->child.remove(0));
if (ls->child[ls->child.size() - 1]) //v的最左侧孩子过继给ls做最右侧孩子
ls->child[ls->child.size() - 1]->parent = ls;
while (!v->key.empty()) { //v剩余的关键码和孩子,依次转入ls
ls->key.insert(ls->key.size(), v->key.remove(0));
ls->child.insert(ls->child.size(), v->child.remove(0));
if (ls->child[ls->child.size() - 1]) ls->child[ls->child.size() - 1]->parent = ls;
}
release(v); //释放v
}
else { //与右兄弟合并
/*DSA*/printf(" ... case 3R\n");
BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
rs->key.insert(0, p->key.remove(r)); p->child.remove(r);
//p的第r个关键码转入rs,v不再是p的第r个孩子
rs->child.insert(0, v->child.remove(v->child.size() - 1));
if (rs->child[0]) rs->child[0]->parent = rs; //v的最右侧孩子过继给rs做最左侧孩子
while (!v->key.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->child[0]->parent = rs;
}
release(v); //释放v
}
v = p; //上升一层,如有必要则继续旋转或合并——至多O(logn)层
} //while
}
2.6 释放函数
//释放节点值
template <typename T>void release(T& e) {
e = NULL;
}
//释放节点空间
template <typename T>void release(BTNodePosi<T>& e) {
delete e;
}
3. 完整代码
#include "BTNode.h"
template <typename T> class BTree { //B-树模板类
protected:
int _size; //存放的关键码总数
int _m; //B-树的阶次,至少为3——创建时指定,一般不能修改
BTNodePosi<T> _root; //根节点
BTNodePosi<T> _hot; //BTree::search()最后访问的非空(除非树空)的节点位置
void solveOverflow(BTNodePosi<T>); //因插入而上溢之后的分裂处理
void solveUnderflow(BTNodePosi<T>); //因删除而下溢之后的合并处理
public:
BTree(int m = 3) : _m(m), _size(0) //构造函数:默认为最低的3阶
{
_root = new BTNode<T>();
}
~BTree() { if (_root) release(_root); } //析构函数:释放所有节点
int const order() { return _m; } //阶次
int const size() { return _size; } //规模
BTNodePosi<T>& root() { return _root; } //树根
bool empty() const { return !_root; } //判空
BTNodePosi<T> search(const T& e); //查找
bool insert(const T& e); //插入
bool remove(const T& e); //删除
}; //BTree
//将关键码e插入B树中
template <typename T> bool BTree<T>::insert(const T& e) {
BTNodePosi<T> v = search(e); if (v) return false; //确认目标节点不存在
Rank r = _hot->key.search(e); //此处的search来自Vector接口,在节点_hot的有序关键码向量中查找合适的插入位置
_hot->key.insert(r + 1, e); //将新关键码插至对应的位置
_hot->child.insert(r + 2, NULL); //创建一个空子树指针
/*存在另一种方法,插入空子树*/
//此时的节点的叶节点,所有的子树也是空的,所以直接在child向量末尾加一个空子树与_hot->child.insert(r + 2, NULL)是等价的
_size++; //更新全树规模
solveOverflow(_hot); //如有必要,需做分裂
return true; //插入成功
}
//从BTree树中删除关键码e
template <typename T> bool BTree<T>::remove(const T& e) {
BTNodePosi<T> v = search(e); if (!v) return false; //确认目标关键码存在
Rank r = v->key.search(e); //确定目标关键码在节点v中的秩(由上,肯定合法)
if (v->child[0]) { //若v非叶子,则e的后继必属于某叶节点
BTNodePosi<T> u = v->child[r + 1]; //在右子树中一直向左,即可
while (u->child[0]) u = u->child[0]; //找出e的后继
v->key[r] = u->key[0]; v = u; r = 0; //并与之交换位置
} //至此,v必然位于最底层,且其中第r个关键码就是待删除者
v->key.remove(r); v->child.remove(r + 1); _size--; //删除e,以及其下两个外部节点之一
solveUnderflow(v); //如有必要,需做旋转或合并
return true;
}
//在B-树中查找关键码e
template <typename T> BTNodePosi<T> BTree<T>::search(const T& e) {
BTNodePosi<T> v = _root; _hot = NULL; //从根节点出发
while (v) { //逐层查找
Rank r = v->key.search(e); //在当前节点中,找到不大于e的最大关键码
if ((0 <= r) && (e == v->key[r])) return v; //成功:在当前节点中命中目标关键码
_hot = v; v = v->child[r + 1]; //否则,转入对应子树(_hot指向其父)——需做I/O,最费时间
} //这里在向量内是二分查找,但对通常的_m可直接顺序查找
return NULL; //失败:最终抵达外部节点
}
//关键码插入后若节点上溢,则做节点分裂处理
template <typename T> void BTree<T>::solveOverflow(BTNodePosi<T> v) {
while (_m <= v->key.size()) { //除非当前节点并未上溢
Rank s = _m / 2; //轴点(此时应有_m = key.size() = child.size() - 1)
BTNodePosi<T> u = new BTNode<T>(); //注意:新节点已有一个空孩子
for (Rank j = 0; j < _m - s - 1; j++) { //v右侧_m-s-1个孩子及关键码分裂为右侧节点u
u->child.insert(j, v->child.remove(s + 1)); //逐个移动效率低
u->key.insert(j, v->key.remove(s + 1)); //此策略可改进
}
u->child[_m - s - 1] = v->child.remove(s + 1); //移动v最靠右的孩子
if (u->child[0]) //若u的孩子们非空,则
for (Rank j = 0; j < _m - s; j++) //令它们的父节点统一
u->child[j]->parent = u; //指向u
BTNodePosi<T> p = v->parent; //v当前的父节点p
if (!p) { _root = p = new BTNode<T>(); p->child[0] = v; v->parent = p; } //若p空则创建之
Rank r = 1 + p->key.search(v->key[0]); //p中指向v的指针的秩
p->key.insert(r, v->key.remove(s)); //轴点关键码上升
p->child.insert(r + 1, u); u->parent = p; //新节点u与父节点p互联
v = p; //上升一层,如有必要则继续分裂——至多O(logn)层
} //while
}
//关键码删除后若节点下溢,则做节点旋转或合并处理
template <typename T> void BTree<T>::solveUnderflow(BTNodePosi<T> v) {
while ((_m + 1) / 2 > v->child.size()) { //除非当前节点并未下溢
BTNodePosi<T> p = v->parent;
if (!p) { //已到根节点(两个孩子即可)
if (!v->key.size() && v->child[0]) {
//但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
/*DSA*/printf("collapse\n");
_root = v->child[0]; _root->parent = NULL; //这个节点可被跳过
v->child[0] = NULL; release(v); //并因不再有用而被销毁
} //整树高度降低一层
return;
}
Rank r = 0; while (p->child[r] != v) r++;
//确定v是p的第r个孩子——此时v可能不含关键码,故不能通过关键码查找
//另外,在实现了孩子指针的判等器之后,也可直接调用Vector::find()定位
/*DSA*/printf("\nrank = %d", r);
// 情况1:向左兄弟借关键码
if (0 < r) { //若v不是p的第一个孩子,则
BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
if ((_m + 1) / 2 < ls->child.size()) { //若该兄弟足够“胖”,则
/*DSA*/printf(" ... case 1\n");
v->key.insert(0, p->key[r - 1]); //p借出一个关键码给v(作为最小关键码)
p->key[r - 1] = ls->key.remove(ls->key.size() - 1); //ls的最大关键码转入p
v->child.insert(0, ls->child.remove(ls->child.size() - 1));
//同时ls的最右侧孩子过继给v
if (v->child[0]) v->child[0]->parent = v; //作为v的最左侧孩子
return; //至此,通过右旋已完成当前层(以及所有层)的下溢处理
}
} //至此,左兄弟要么为空,要么太“瘦”
// 情况2:向右兄弟借关键码
if (p->child.size() - 1 > r) { //若v不是p的最后一个孩子,则
BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
if ((_m + 1) / 2 < rs->child.size()) { //若该兄弟足够“胖”,则
/*DSA*/printf(" ... case 2\n");
v->key.insert(v->key.size(), p->key[r]); //p借出一个关键码给v(作为最大关键码)
p->key[r] = rs->key.remove(0); //rs的最小关键码转入p
v->child.insert(v->child.size(), rs->child.remove(0));
//同时rs的最左侧孩子过继给v
if (v->child[v->child.size() - 1]) //作为v的最右侧孩子
v->child[v->child.size() - 1]->parent = v;
return; //至此,通过左旋已完成当前层(以及所有层)的下溢处理
}
} //至此,右兄弟要么为空,要么太“瘦”
// 情况3:左、右兄弟要么为空(但不可能同时),要么都太“瘦”——合并
if (0 < r) { //与左兄弟合并
/*DSA*/printf(" ... case 3L\n");
BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
ls->key.insert(ls->key.size(), p->key.remove(r - 1)); p->child.remove(r);
//p的第r - 1个关键码转入ls,v不再是p的第r个孩子
ls->child.insert(ls->child.size(), v->child.remove(0));
if (ls->child[ls->child.size() - 1]) //v的最左侧孩子过继给ls做最右侧孩子
ls->child[ls->child.size() - 1]->parent = ls;
while (!v->key.empty()) { //v剩余的关键码和孩子,依次转入ls
ls->key.insert(ls->key.size(), v->key.remove(0));
ls->child.insert(ls->child.size(), v->child.remove(0));
if (ls->child[ls->child.size() - 1]) ls->child[ls->child.size() - 1]->parent = ls;
}
release(v); //释放v
}
else { //与右兄弟合并
/*DSA*/printf(" ... case 3R\n");
BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
rs->key.insert(0, p->key.remove(r)); p->child.remove(r);
//p的第r个关键码转入rs,v不再是p的第r个孩子
rs->child.insert(0, v->child.remove(v->child.size() - 1));
if (rs->child[0]) rs->child[0]->parent = rs; //v的最右侧孩子过继给rs做最左侧孩子
while (!v->key.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->child[0]->parent = rs;
}
release(v); //释放v
}
v = p; //上升一层,如有必要则继续旋转或合并——至多O(logn)层
} //while
}
//释放节点值
template <typename T>void release(T& e) {
e = NULL;
}
//释放节点空间
template <typename T>void release(BTNodePosi<T>& e) {
delete e;
}
#include <iostream>
#include"Dice.h"
using namespace std;
template <typename T> void testBTree(int m, int n) {
BTree<T> bt(m);
while (bt.size() < n) {
T e = dice( (T)n * 3 ); //[0, 3n)范围内的e
switch (dice(3)) {
case 0: { //查找,成功率 <= 33.3%
cout << "搜索"<<e<<"...";//printf("Searching for "); print(e); printf(" ... ");
BTNodePosi<T> p = bt.search(e);
if(p) cout << "存在!" << endl;
else cout << "不存在!" << endl;
break;
}
case 1: { //删除,成功率 <= 33.3%
cout << "删除" << e << "...";// printf("Removing "); print(e); printf(" ... ");
if ( bt.remove(e) ) cout << "删除成功!"<<e << endl;
else cout << "不存在" << endl;
break;
}
default: {//插入,成功率 == 100%
cout << "插入" << e << "...";//printf("Inserting "); print(e); printf(" ... ");
int oldSize = bt.size();
if( bt.insert(e) ) cout << "插入成功!" << e << endl;
else cout << "插入失败!" << endl;
break;
}
}
}
/*
while (bt.size() > 0) {
T e = dice((T)n * 3); //[0, 3n)范围内的e
cout << "删除" << e << "...";
// printf("Removing "); print(e); printf(" ... ");
if (bt.remove(e)) cout << "删除成功!" << endl;
else cout << "不存在!" << endl;
// bt.remove(e) ?
// printf("Done\n"), print(bt) :
// printf("not exists\n");
}
*/
}
int main() {
srand((unsigned int)time(NULL));
testBTree<int>(2,4);
system("pause");
}
4. 运行结果及截图
以上相关代码参考邓俊辉老师的《c++语言版数据结构》!