前言
B树和红黑树一样,也是一种平衡树。但是前者主要用在数据库系统或者文件系统中,有助于降低磁盘I/O次数;此外,B树可以有很多的子女,几十或者上千个不等,“分支因子”较大。
B树定义
B树是递归定义的,每个节点,都可以看做是一棵以该节点为根的子B树。
B树高度
B树的操作
本篇博客实现的B树提供以下操作:
1、判空操作empty:返回一个bool值,表明树是否为空;
2、插入操作insert:接受一个值,插入B树,无返回值;
3、删除操作erase:接受一个值,在B树搜寻删除此值,返回一个bool值,表明是否删除成功;
4、查找操作search:接受一个值,在B树中查找是否存在此值,返回一个bool值;
5、输出操作sequentialPrint:按顺序输出B树中的所有关键字;
6、清空操作clear:释放B树节点占用的资源,使其回到最初状态。
B树节点类型
首先给出实现的B树的节点类型,C++实现
template < typename T, int degree = 3, typename Compare = less<T> >
struct node
{//B树节点类型,degree为度,默认为3,Compare比较器类型,默认小于
static const int min_num = degree - 1;//每个节点的最小关键字数
static const int max_num = 2 * degree - 1;//节点最大关键字数
static Compare compare;//比较器
int num = 0;//该节点关键字数目
bool leaf = true;//是否为叶子节点
T key[max_num];//关键字数组
node *child[max_num + 1];//孩子节点数组
static void setCompare(const Compare &c){ compare = c; }//设置比较器,由B树构造时设置
node()
{//默认构造函数
for (int i = 0; i != max_num; ++i)
{
key[i] = T();
child[i] = nullptr;
}
child[max_num] = nullptr;
}
int search(const T&)const;
void insert(const T&);
bool erase(int);
};
设计理由:
1、节点内的关键字均是有序的,那么为了加快查找速度,节点内采用二分查找,返回第一个不小于k的关键字索引;
2、由于采用二分查找,那么关键字的存储最好使用连续的内存空间,因而选择数组;
3、比较器类型是为了我们可以设置自定义数据类型的比较规则;由B树构造时触发设置。
B树实现代码,C++实现,注释详细,因此就不再画蛇添足叙述算法了
//B树
#include<iostream>
#include<vector>
using namespace std;
template < typename T, int degree = 3, typename Compare = less<T> >
struct node
{//B树节点类型,degree为度,默认为3
static const int min_num = degree - 1;//每个节点的最小关键字数
static const int max_num = 2 * degree - 1;//节点最大关键字数
static Compare compare;
int num = 0;//该节点关键字数目
bool leaf = true;//是否为叶子节点
T key[max_num];//关键字数组
node *child[max_num + 1];//孩子节点数组
static void setCompare(const Compare &c){ compare = c; }
node()
{//默认构造函数
for (int i = 0; i != max_num; ++i)
{
key[i] = T();
child[i] = nullptr;
}
child[max_num] = nullptr;
}
int search(const T&)const;
void insert(const T&);
bool erase(int);
};
//定义静态变量
template < typename T, int degree, typename Compare> const int node<T, degree, Compare>::max_num;
template < typename T, int degree, typename Compare> const int node<T, degree, Compare>::min_num;
template < typename T, int degree, typename Compare> Compare node<T, degree, Compare>::compare;
template < typename T, int degree = 3, typename Compare = less<T> >
int node<T, degree,Compare>::search(const T &k)const
{//节点内关键字查找
int low = 0, high = num - 1;
while (low < high)
{//二分查找
int mid = (low + high) / 2;
if (!compare(k,key[mid]) && !compare(key[mid],k)) return mid;
else if (compare(k,key[mid])) high = mid - 1;
else low = mid + 1;
}
if (compare(key[low], k) && low < num - 1) ++low;
return low;//返回第一个不小于k的关键字的索引
}
template < typename T, int degree = 3, typename Compare = less<T> >
void node<T, degree, Compare>::insert(const T &k)
{//节点内插入
int i = num - 1;
while (i >= 0 && compare(k,key[i]))
{//找寻插入位置
key[i + 1] = key[i];
--i;
}
key[++i] = k;//插入
++num;
}
template <typename T,int degree = 3,class Compare>
bool node<T, degree, Compare>::erase(int index)
{//节点内删除
for (int i = index + 1; i != num; ++i)
key[i - 1] = key[i];
--num;
return true;
}
template < typename T, int degree = 3, typename Compare = less<T> >
class Btree
{//B树
public:
typedef node<T, degree, Compare> node;
typedef Compare comp;
private:
node *root;
Compare compare;
void destroy(node*);//销毁树
void split(node*, int);//分割节点
void insert_aux(node*, const T&);//插入辅助
bool erase_aux(node*, const T&);//删除辅助
void merge(node*, int);//合并节点
T erasePredecessor(node*);//删除前驱
T eraseSuccessor(node*);//删除后继
void borrowFromRightSibling(node*, int);//向右兄弟借关键字
void borrowFromLeftSibling(node*, int);//向左兄弟借关键字
void print(node*)const;//按顺序打印
public:
Btree() :root(nullptr), compare(Compare()){ node::setCompare(compare); }
Btree(const Compare &c) :root(nullptr), compare(c){ node::setCompare(c); }
bool empty()const { return root == nullptr; }
void insert(const T&);
bool search(const T&)const;
void sequentialPrint()const { print(root); }
void clear()
{
destroy(root);
root = nullptr;
}
bool erase(const T &k) { return erase_aux(root, k); }
~Btree(){ destroy(root); }
};
template <typename T,int degree,class Compare>
bool Btree<T,degree, Compare>::search(const T &k)const
{//在B树中查找k
node *curr = root;
while (curr != nullptr)
{
int index = curr->search(k);//在当前节点查找
if (!compare(k, curr->key[index]) && !compare(curr->key[index], k)) return true;//若存在
else if (compare(k,curr->key[index]))//若k小于index处的关键,则在其左边查找
curr = curr->child[index];
else curr = curr->child[index + 1];//否则在右边查找
}
return false;
}
template <typename T, int degree, class Compare>
void Btree<T, degree, Compare>::split(node *curr, int index)
{//将curr所指节点的index处孩子节点分割成两个节点,两边各degree - 1个关键字,第degree个关键字上升到curr中
node *new_child = new node,*old_child = curr->child[index];
T k = old_child->key[degree - 1];
for (int first = 0; first != degree - 1; ++first)//将原节点的后一半关键字复制到新节点
new_child->key[first] = old_child->key[first + degree];
if (!old_child->leaf)//如果不是叶子
for (int first = 0; first != degree; ++first)//则还要复制一半的孩子节点指针
new_child->child[first] = old_child->child[first + degree];
new_child->leaf = old_child->leaf;
new_child->num = degree - 1;//新节点关键字数
old_child->num -= degree;//原节点关键字数减半
for (int last = curr->num - 1; last >= index; --last)//将curr中index(包括)之后的关键字全部后移一个位置
curr->key[last + 1] = curr->key[last];
for (int last = curr->num; last > index; --last)//将curr中index(不包括)之后的孩子指针全部后移一个位置
curr->child[last + 1] = curr->child[last];
curr->key[index] = k;//在curr中的index处填上升上来的关键字
curr->child[index + 1] = new_child;//index后的孩子指针指向新节点
++curr->num;
}
template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::insert_aux(node *curr, const T &k)
{//插入辅助函数
if (curr->leaf) curr->insert(k);//若当前节点是叶子,则直接插入
else
{//否则
int index = curr->search(k);//找到第一个不小于k的关键字索引
if (compare(curr->key[index], k)) ++index;//极端情况,该节点所有关键字均小于k
if ((curr->child[index])->num == node::max_num)
{//若该所引处的孩子节点关键字数已满
split(curr, index);//则将其从该处分割
if (compare(curr->key[index], k))//分割后上升上来的关键字若小于k
++index;//则将要到新生成的节点中继续插入
}
insert_aux(curr->child[index], k);//递归插入
}
}
template <typename T, int degree, class Compare>
void Btree<T, degree, Compare>::insert(const T &k)
{//插入关键字
if (root == nullptr)
{//如果在空树中插入第一个关键字
root = new node;
root->insert(k);
return;
}
else if (root->num == node::max_num)
{//否则如果根节点满
node *p = root;
root = new node;//树将会长高
root->child[0] = p;
root->leaf = false;
split(root, 0);//树根分裂
}
insert_aux(root, k);
}
template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::merge(node *curr, int index)
{//合并curr中index处的孩子节点和其右兄弟,此时两者均恰好只有degree - 1个关键字
node *left = curr->child[index], *right = curr->child[index + 1];
left->key[degree - 1] = curr->key[index];//curr中index处关键字先下降
for (int i = 0; i != right->num; ++i)//复制right所有关键字过来
left->key[degree + i] = right->key[i];
for (int i = index + 1; i != curr->num; ++i)//将curr中index之后的关键字前移1
curr->key[i - 1] = curr->key[i];
if (!left->leaf)//如果不是叶子
for (int i = 0; i <= right->num; ++i)//则还要移动right的孩子指针
left->child[degree + i] = right->child[i];
for (int i = index + 2; i <= curr->num; ++i)//移动curr中index+2(包括)指针前移1,index处不必被覆盖
curr->child[i - 1] = curr->child[i];
--curr->num; ++left->num;
left->num += right->num;
delete right;
}
template <typename T,int degree,class Compare>
T Btree<T, degree, Compare>::erasePredecessor(node *curr)
{//删除前驱,在情况2.a中被调用
if (curr->leaf)
{//若是叶子,则说明已经可以删除最后一个元素,即前驱了
T tmp = curr->key[curr->num - 1];
--curr->num;
return tmp;//返回前驱
}
//否则是内节点,继续递归向下?
else if (curr->child[curr->num]->num == node::min_num)
{//若最后一个孩子关键字数目已达最小值
if (curr->child[curr->num - 1]->num > node::min_num)//若左兄弟有多余关键字
borrowFromLeftSibling(curr, curr->num);//那么借一个
else merge(curr, curr->num - 1);//否则只有合并了
}
return erasePredecessor(curr->child[curr->num]);//继续向下递归
}
template <typename T,int degree,class Compare>
T Btree<T, degree, Compare>::eraseSuccessor(node *curr)
{//删除后继,在情况2.b中被调用
if (curr->leaf)
{//若是叶子节点,则直接删除最前面元素,即后继
T tmp = curr->key[0];
curr->erase(0);
return tmp;
}
//否则,是内节点,继续向下?
else if (curr->child[0]->num == node::min_num)
{//若第一个孩子关键字数目已达最小值
if (curr->child[1]->num > node::min_num)//若右兄弟有足够关键字
borrowFromRightSibling(curr, 0);//则借一个
else merge(curr, 0);//否则只有合并了
}
return eraseSuccessor(curr->child[0]);//继续向下递归
}
template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::borrowFromRightSibling(node *curr, int index)
{//curr中index孩子向其右兄弟借一个关键字
node *left = curr->child[index], *right = curr->child[index + 1];
left->key[left->num] = curr->key[index];//先将curr中index处的关键字添入该子树根,left所指
curr->key[index] = right->key[0];//再用右兄弟(right所指)第一个关键字覆盖curr中index槽位
for (int i = 1; i != right->num; ++i)//将右兄弟从1开始的所有关键字前移1
right->key[i - 1] = right->key[i];
if (!left->leaf)
{//若并非叶子,则还要设置相关孩子指针域
left->child[left->num + 1] = right->child[0];//右兄弟第一个孩子成为left最后一个孩子
for (int i = 1; i <= right->num; ++i)//前移右兄弟的孩子指针数组
right->child[i - 1] = right->child[i];
}
++left->num; --right->num;
}
template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::borrowFromLeftSibling(node *curr, int index)
{
--index;//移到左兄弟槽位
node *left = curr->child[index], *right = curr->child[index + 1];
right->insert(curr->key[index]);
curr->key[index] = left->key[left->num - 1];
if (!right->leaf)
{//非叶子,移动指针
for (int i = right->num; i >= 0; --i)
right->child[i + 1] = right->child[i];
right->child[0] = left->child[left->num];
}
--left->num;
}
template <typename T,int degree,class Compare>
bool Btree<T, degree, Compare>::erase_aux(node *curr, const T &k)
{//删除辅助函数
int index = curr->search(k);//找到第一个不小于k的关键字
if (curr->leaf && (!compare(curr->key[index], k) && !compare(k, curr->key[index])))
return curr->erase(index);//情况1,关键字在叶子
else if (curr->leaf) return false;//不在叶子节点,则删除失败,不存在该关键字
if (!curr->leaf && (!compare(curr->key[index], k) && !compare(k, curr->key[index])))
{//情况2,关键字在该内节点。则使用该关键字的前驱或者后继代替
if (curr->child[index]->num > node::min_num)
{//情况2.a,其左孩子有足够的关键字,即至少degree - 1个,则使用前驱代替。
//删除其前驱,并返回前驱关键字,以覆盖该关键字
curr->key[index] = erasePredecessor(curr->child[index]);
return true;
}
else if (curr->child[index + 1]->num > node::min_num)
{//情况2.b,否则其右孩子有足够关键字,则使用后继代替
curr->key[index] = eraseSuccessor(curr->child[index + 1]);//同上
return true;
}
else
{//否则,由于该关键字左右孩子的关键字数均处于最少,则不能采用前驱或者后继代替,那么合并左右孩子以及该关键字
merge(curr, index);//将curr节点的index处相关关键字和孩子合并
return erase_aux(curr->child[index], k);
}
}
else
{//情况3,关键字不在该节点,处于该关键字的左子树中
if (compare(curr->key[index], k)) ++index;//极端情况,当curr中所有关键字均比k小时出现
if (curr->child[index]->num == node::min_num)
{//若左子树关键字数已到达最小值
if (index < curr->num && curr->child[index + 1]->num > node::min_num)
//情况3.a,存在右兄弟,且有足够节点,即至少degree - 1个,则向其借一个
borrowFromRightSibling(curr, index);
else if (index > 0 && curr->child[index - 1]->num > node::min_num)
//情况3.b,存在左兄弟且关键字数足够,类似于3.a
borrowFromLeftSibling(curr, index);
else
{//3.c,左/右兄弟均只有degree - 1个关键字,那么合并节点
if (index == curr->num) --index;//呼应上述极端情况下的节点合并
merge(curr, index);
if (curr == root && curr->num == 0)
{//若当前curr是根,且仅存一个元素
root = curr->child[index];
delete curr;//那么树高降1
return erase_aux(root, k);
}
}
}
return erase_aux(curr->child[index], k);
}
}
template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::print(node *curr)const
{//打印整棵树
if (curr->leaf)
{//若是叶子节点,则打印全部关键字
cout << "[ ";
for (int i = 0; i != curr->num; ++i)
cout << curr->key[i] << ' ';
cout << ']' << endl;
}
else
{//否则
cout << '{' << endl;
for (int i = 0; i <= curr->num; ++i)
{//依次打印孩子和关键字
print(curr->child[i]);
if (i < curr->num)
cout << curr->key[i] << endl;
}
cout << '}' << endl;
}
}
template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::destroy(node *curr)
{//销毁B树
if (curr == nullptr) return;
if (curr->leaf) delete curr;//若是叶子,直接销毁
else
{//否则
for (int i = 0; i <= curr->num; ++i)//依次销毁所有孩子后
destroy(curr->child[i]);
delete curr;//再销毁该节点
}
}
int main()
{
/*Btree<char> bt;
vector<char> cvec = {'P','C','M','T','X','A','B','D','E',
'F','J','K','L','N','O','Q','R','S','U','V','Y','Z'};
for (size_t i = 0; i != cvec.size(); ++i)
bt.insert(cvec[i]);
cout << boolalpha << bt.search('A') << endl;
bt.erase('L');
bt.erase('M');
bt.erase('K');
bt.erase('A');
bt.erase('X');
bt.erase('Y');
bt.erase('J');
cout << boolalpha << bt.search('A') << endl;
bt.sequentialPrint();*/
Btree<int,10> bt;
for (int i = 0; i != 10000; ++i)
bt.insert(i);
for (int i = 0; i != 100; ++i)
bt.erase(3 * i);
bt.sequentialPrint();
getchar();
return 0;
}
习题 18.1-1
因为节点中的关键字数目至少为1,那么查找方向至少有两个,因而度至少要为2.
习题18.1-4
(2t)^(h+1) – 1
习题18.1-5
2-3-4树
习题 18.2-2
根满之后分裂,会出现一次冗余读。
习题 18.3-2
见erase和erase_aux代码,非常简洁。