简介
和AVL树、RB树一样,B树也是一种搜索树结构,能够存储数据也可以对其进行排序。它的特点是结点可以有多余两个的子结点。但是他的优势在于处理大块数据的读写操作,因为在计算机中的存储体系当中,有一种叫做局部性的东西。他分为时间局部性和空间局部性。
时间局部性:最近被访问的内存内容会很快再被访问;
空间局部性:靠近当前被访问内存的内存内容很快也会被访问。
当在处理大块数据操作的时候B树的特性就可以减少定位数据的过程,从而提高查找效率,所以B树普遍应用在数据库的底层实现上。
B树的定义:
一个M阶的B树要满足以下几点规则
1.根结点至少有两个孩子结点;
2.每个结点有M-1个Key,并且以升序排列;
3.位于key[i]和key[i+1] 之间的孩子结点的值介于key[i]和key[i+1]之间。
4.其他节点至少有M/2个子结点(M/2是向上取整)。
如下图是一个三阶的B树
B树的结构定义
在这里实现B树我使用了pair结构,在pair数组里存放了key和value。其他部分还有_size用来计算键值数量,_sub用来存放孩子,_parent用来存放父结点。
代码(这里给出的是3阶B树,M给定默认3)
template <class K, class V, size_t M = 3>
struct BTreeNode
{
pair<K,V> _kv[M];//存放键值
size_t _size;//键值计数
BTreeNode<K, V>* _sub[M + 1];
BTreeNode<K, V>* _parent;
BTreeNode()
:_parent(NULL)
, _size(0)
{
for (size_t i = 0; i <= M; ++i)
{
_sub[i] = NULL;
}
}
};
B树的插入
B树的插入主要分成了三个步骤
1.首先是找到插入位置,在这里我们实现了一个函数通过key值来查找要插入结点所应存在的位置,然后返回他的父结点。
pair<Node*, int> Find(const K& key)
{//查找结点,
Node* cur = _root;
Node* parent = NULL;
while (cur)
{
size_t index = 0;
while (index < cur->_size)
{
if (key > cur->_kv[index].first)
index++;
else if (key == cur->_kv[index].first)
return make_pair(cur, index);
else
break;
}
parent = cur;
cur = cur->_sub[index];
}
//返回其父结点
return make_pair(parent, -1);
}
2.找到位置后就插入结点。
void _Insert(Node* cur, const pair<K, V>& newKV, Node* sub)
{
int index = (cur->_size)-1;
while (index >= 0)
{//寻找插入位置
if (newKV.first >= cur->_kv[index].first)
break;
cur->_kv[index + 1] = cur->_kv[index];
cur->_sub[index + 2] = cur->_sub[index + 1];
index--;
}
//放入结点及对应孩子结点
cur->_kv[index + 1] = newKV;
cur->_sub[index + 2] = sub;
//孩子存在,给其父亲赋值
if (sub)
sub->_parent = cur;
cur->_size++;
}
3.插入节点之后,如果发现kv数组已经存满了,则需要对结点进行分裂操作,分裂操作的主要思想为先找到这个结点的key值为中间值的位置,然后从这个位置把结点分为两部分,左边结点为旧结点,右边部分为新开的结点,然后再把中间的位置连同新结点的储存位置插入到父结点之中,然后再重复这个过程,看父结点是否需要分裂。直到B树满足其规则。
如图插入key38:
代码
bool Insert(const pair<K,V>& kv)
{//插入
if (_root == NULL)
{
_root = new Node;
_root->_kv[0] = kv;
_root->_size++;
return true;
}
pair<Node*, int> ret = Find(kv.first);
if (ret.second != -1)
return false;
Node* cur = ret.first;
Node* temp = cur;
pair<K, V> newKV = kv;
Node* sub = NULL;
while (1)
{//向cur里插入newKV
_Insert(cur, newKV, sub);
//cur还没装满,返回
if (cur->_size < M)
return true;
//否则开辟新结点
Node* tmp = new Node;
//把旧结点分成两半,右边的一半存放在新结点内
size_t mid = M >> 1;
size_t i = mid + 1;
size_t j = 0;
while (i < cur->_size)
{//拷贝右侧结点到新结点
tmp->_kv[j] = cur->_kv[i];
cur->_kv[i] = pair<K, V>();
tmp->_sub[j] = cur->_sub[i];
if (cur->_sub[i])
cur->_sub[i]->_parent = tmp;
i++;
j++;
tmp->_size++;
cur->_size--;
}
tmp->_sub[j] = cur->_sub[i];
cur->_sub[i] = NULL;
if (cur->_sub[i])
cur->_sub[i]->_parent = tmp;
if (cur->_parent)
{//将中间元素插入到父结点
newKV = cur->_kv[mid];
sub = tmp;
cur->_size--;
cur = cur->_parent;
}
else
{//父结点为空,即根结点需要分裂
Node* newRoot = new Node;
newRoot->_kv[0] = cur->_kv[mid];
newRoot->_size = 1;
newRoot->_sub[0] = cur;
cur->_parent = newRoot;
newRoot->_sub[1] = tmp;
tmp->_parent = newRoot;
cur->_size--;
_root = newRoot;
return true;
}
}
}