哈喽啊!好久不见,甚是想念!失踪人口要回归了,时隔一个多月小吉我终于要更新blog了🎉。在停更的一个多月中,小吉也有在好好学习提升自己,立志给大家呈现好文章。
现在让我们进入正题吧,今天我们这篇blog主要讲用c++实现B树,上一篇blog讲的是二叉搜索树,中间还有avl树和红黑树小吉还没有把博客写出来。希望大家多多关注小吉,在完成这篇blog后,小吉将会出avl树实现的博客(浅浅期待一下吧)。
老规矩,在正式实现B树之前,我们先来了解了解什么是B树。
B树的概念
B树的特征
在讲B树的特征之前,我们先来了解一下度和阶的概念
在m阶的B树中,即孩子数最大值为m
(小吉在这里提醒一下:一定要好好理解B树的特征,后面在实现的过程有什么不能理解的地方,一定要回头好好读一下特征,大部分问题都能在看过特征后得到解答(小吉的小经验✨))
B树节点类
节点类代码呈现👇:
class Node
{
public:
Node(int t, bool leaf = true) :_t(t), _leaf(left), _children(2 * t, nullptr),_keys(2*t-1),KeyNumber(0){}//初始化列表
public:
vector<int> _keys;//键值
vector<Node*> _children;//孩子
int KeyNumber;//有效键值数目
bool _leaf;//是否为叶子节点
int _t;//最小度数(最小孩子数)
};
B树节点类成员方法
主要实现3个方法
代码呈现👇:
Node* Node::get(int key)
{
int i = 0;
while (i < KeyNumber)
{
if (_keys[i] == key)
{
return this;
}
if (_keys[i] > key)
{
break;
}
i++;
}
//比key值大且为叶子节点
if (this->_leaf)
{
return nullptr;
}
//非叶子节点(采用递归)
return _children[i]->get(key);
}
void Node::insertKey(int index,int key)
{
_keys.insert(_keys.begin() + index, key);
KeyNumber++;
}
void Node::insertChild(int index, Node* child)
{
_children.insert(_children.begin() + index, child);
}
B树类
class BTree
{
public:
BTree(int t) :_t(t), _root(new Node(t)), MAX_KEYNUMS(2 * t - 1), MIN_KEYNUMS(t - 1) { }//初始化列表
public:
Node* _root;
int _t;//树中最小度数
const int MIN_KEYNUMS;
const int MAX_KEYNUMS;
};
B树类成员方法
代码实现👇:
//是否存在
bool contains(int _key)
{
return _root->get(_key) != nullptr;
}
B树put插入节点初实现
代码架构👇:
void BTree::doput(Node* node, int key,Node* parent,int index)
{
int i = 0;
while (i < node->KeyNumber)
{
if (node->_keys[i]==key)
{
node->_keys[i] = key;//更新
return;
}
if (node->_keys[i] > key)
{
break;//找到了插入位置
}
i++;
}
if (node->_leaf)
{
node->insertKey(i, key);
}
else
{
doput(node->_children[i], key,node,i);//递归
}
}
void BTree::put(int key)
{
doput(_root, key,nullptr,0);
}
B树split分裂
无论哪种情况,插入完成后都可能超过节点keys数目限制,应执行节点分裂
(注:较小的数字代表索引)
split分裂思路🎇
分为三种情况,待分裂节点为叶子节点,非叶子节点和根节点。1️⃣我们先来实现最简单的一种情况,待分裂节点为叶子节点。
代码展示👇:
void BTree::split(Node* left, Node* parent, int index)
{
//1.创建right节点,把left中t之后的key和child移动过去
Node* right = new Node(_t);
right->_leaf = left->_leaf;
right->_keys.assign(left->_keys.begin() + _t, left->_keys.end());
right->KeyNumber = _t - 1;
left->KeyNumber = _t - 1;
//2.中间的key(t-1处)插入到父节点
int mid = left->_keys[_t - 1];
parent->insertKey(index, mid);
//3.right节点作为父节点的孩子
parent->insertChild(index + 1, right);
}
实现叶子节点的分裂就按照上面的思路就可以了,相对比较简单,我们现在可以小小测试一下代码,这里提供一个小的测试案例
void splitTest()//split分裂方法的测试案例
{
BTree tree(2);
Node* root = tree._root;
root->_leaf = false;
root->_keys[0] = 2;
root->KeyNumber = 1;
root->_children[0] = new Node(2);
root->_children[0]->_keys = { 1 };
root->_children[0]->KeyNumber = 1;
root->_children[1] = new Node(2);
root->_children[1]->_keys = { 3,4,5 };
root->_children[1]->KeyNumber = 3;
tree.split(root->_children[0], root, 0);
}
分裂结果可以参照小吉上面给的图
2️⃣待分裂节点为非叶子节点,这种情况就比叶子节点多一个步骤就是处理孩子
代码实现👇:
void BTree::split(Node* left, Node* parent, int index)
{
//1.创建right节点,把left中t之后的key和child移动过去
Node* right = new Node(_t);
right->_leaf = left->_leaf;
right->_keys.assign(left->_keys.begin() + _t, left->_keys.end());
//分裂节点是非叶子节点的情况
if (!left->_leaf)
{
right->_children.assign(left->_children.begin() + _t, left->_children.end());
}
right->KeyNumber = _t - 1;
left->KeyNumber = _t - 1;
//2.中间的key(t-1处)插入到父节点
int mid = left->_keys[_t - 1];
parent->insertKey(index, mid);
//3.right节点作为父节点的孩子
parent->insertChild(index + 1, right);
}
3️⃣待分裂节点为根节点,这里小吉先画个图来帮小可爱们想象一下
代码实现👇:
if (parent == nullptr)//分裂的是根节点
{
Node* newRoot = new Node(_t);
newRoot->_leaf = false;
newRoot->insertChild(0, left);
this->_root = newRoot;
parent = newRoot;
}
三种情况已经全部分析完了🎉🎉🎉,现在小吉把三种情况的代码进行汇总,封装在split方法中
void BTree::split(Node* left, Node* parent, int index)
{
if (parent == nullptr)//分裂的是根节点
{
Node* newRoot = new Node(_t);
newRoot->_leaf = false;
newRoot->insertChild(0, left);
this->_root = newRoot;
parent = newRoot;
}
//1.创建right节点,把left中t之后的key和child移动过去
Node* right = new Node(_t);
right->_leaf = left->_leaf;
right->_keys.assign(left->_keys.begin() + _t, left->_keys.end());
//分裂节点是非叶子节点的情况
if (!left->_leaf)
{
right->_children.assign(left->_children.begin() + _t, left->_children.end());
}
right->KeyNumber = _t - 1;
left->KeyNumber = _t - 1;
//2.中间的key(t-1处)插入到父节点
int mid = left->_keys[_t - 1];
parent->insertKey(index, mid);
//3.right节点作为父节点的孩子
parent->insertChild(index + 1, right);
}
节点的分裂到这里已经全部讲完了🥳🥳🥳,下面就是要把split方法和put方法结合起来
B树put插入节点最终实现
void BTree::put(int key)
{
doput(_root, key,nullptr,0);
}
void BTree::doput(Node* node, int key,Node* parent,int index)
{
int i = 0;
while (i < node->KeyNumber)
{
if (node->_keys[i]==key)
{
node->_keys[i] = key;//更新
return;
}
if (node->_keys[i] > key)
{
break;//找到了插入位置
}
i++;
}
if (node->_leaf)
{
node->insertKey(i, key);
}
else
{
doput(node->_children[i], key,node,i);//递归
}
if (node->KeyNumber == MAX_KEYNUMS)//进行分裂
{
split(node, parent, index);
}
}
这篇blog到这里已经全部结束了,浅浅预告一下小吉的下一篇blog讲的是B树的删除操作(又是一个大工程😶🌫️),还望大家多多支持小吉❤️
这篇博客有点长也有点难,希望小可爱们给自己多一点耐心,静下心来慢慢学,还有一定要敲代码,只有敲了代码才能发现问题。
创作不易,还望大家多多支持🌹🌹🌹(小吉写了整整3个小时🤣)