一、B树的概念
B树,概括来说是一个节点可以拥有多于2个子节点的平衡多叉树。
特点:
1> 根节点至少有两个子节点
2>每个非跟节点节点有(M/2)-1至M-1个key
3>每个非根节点有[M/2 ,M]个孩子
4>key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
5> 所有的叶子节点都在同一层
二、B树的应用
由于增删查改的效率十分高(时间复杂度log M-1为底N的对数的样子),B树普遍应用于数据库和文件系统。一般会将M定义的非常大,这样B树的高度就很低,每一个节点中可以使用二分查找,所以B树效率高。缺点就是比较耗内存。
三、实现一个B树
1.节点内容
根据B树的特点,一个节点应该包含一个大小为M-1的key数组,但是考虑到B树在插入的时候需要先插入节点然后在进行分裂,所以key数组给M大的空间。其次,应该包含一个节点指针类型的数组,存储指向孩子的指针,大小应为M+1。还有,一个指向父节点的指针,一个表示当前结点key值实际数量的size。
2.节点插入
1>空树时,直接调用节点的构造函数,new一个root。
2>非空时,先查找树,若已经存在要插入的关键码,则插入失败。若不存在,让Find()函数返回插入位置的指针。那么,Find不仅要返回key值是否在树内(bool),还要返回要插入节点的位置(Node*)。所以,我们使用库里存在的一个类型pair,它可以带回两个返回值。
3>接下来,把key值插入Find返回的位置。
4>检测插入后key值的个数是否超过M-1,若没有,则插入成功,若超过,则需要分裂。根据B树的特点,可以知道,插入的位置只能是叶子节点。
分裂是怎么一回事呢?看下图
分裂是插入的难点,理解了分裂,插入就没什么问题了。
3.代码实现
#include<iostream>
using namespace std;
template<class K , int M>
struct BTreeNode
{
typedef BTreeNode<K, M> Node;
K _keys[M]; //多给出一个位置是为了方便分裂。
Node* _sub[M + 1];
Node* _parent;
size_t _size; //记录实际关键字的个数
BTreeNode()
:_parent(NULL)
, _size(0)
{
for (size_t i = 0; i < M; i++)
{
_keys[i] = K();
_sub[i] = 0;
}
_sub[M] = 0;
}
};
template<class K,int M>
class BTree
{
public:
typedef BTreeNode<K, M> Node;
BTree()
:_root(NULL)
{}
void InOrder()
{
_InOrder(_root);
}
~BTree()
{
_Destory(_root);
}
bool Insert(const K& key)
{
if (NULL == _root)
{
_root = new Node();
_root->_keys[0] = key;
_root->_size++;
return true;
}
pair<Node*, size_t> tmp = _Find(key);
if (tmp.second != -1) //已经存在关键值为key,则不能插入
{
return false;
}
Node* cur = tmp.first;
Node* sub = NULL;
K newkey = key;
while (1)
{
_Insertkey(cur,newkey,sub); //先将key放进要插的节点
//判断结点的关键字数目是否符合标准。
if (cur->_size < M) //该节点上插入后关键字数目正常
return true;
//数目超过规定值需要进行分裂
while (cur->_size >= M)
{
size_t mid = cur->_size / 2;
//1.分裂出新的结点
Node* NewNode = new Node;
for (size_t i = mid+1; i < cur->_size; i++)//mid之后的key值给新结点
{
int j = 0;
NewNode->_keys[j] = cur->_keys[i];
NewNode->_size++;
//cur->_size--; //注意此处先不要哦改动cur的size,否则会影响下一个循环
cur->_keys[i] = K(); //赋给新结点后cur对应的key应置成初始值
j++;
}
int j = 0;
for (size_t i = mid+1 ; i < cur->_size+1; i++)
{
NewNode->_sub[j] = cur->_sub[i];
if (NewNode->_sub[j])
NewNode->_sub[j]->_parent = NewNode;
j++;
cur->_sub[i] = NULL;
}
if (cur == _root) //创建出新的根节点
{
Node* tmp = new Node();
tmp->_keys[0] = cur->_keys[mid];
cur->_keys[mid] = K();
cur->_size=mid;
tmp->_size++;
tmp->_sub[0] = cur;
cur->_parent = tmp;
tmp->_sub[1] = NewNode;
NewNode->_parent = tmp;
_root = tmp;
return true;
}
newkey = cur->_keys[mid];
cur->_keys[mid] = K();
cur->_size = mid;
sub = NewNode;
}
cur = cur->_parent;
}
}
protected:
void _Destory(Node* root)
{
if (NULL == root)
return;
size_t i = 0;
for (; i < root->_size; i++)
{
_Destory(root->_sub[i]);
delete root->_sub[i];
}
_Destory(root->_sub[i]);
delete root->_sub[i];
}
void _InOrder(Node* root)
{
if (NULL == root)
return;
size_t i = 0;
for (; i < root->_size; i++)
{
_InOrder(root->_sub[i]);
cout << root->_keys[i]<<" ";
}
_InOrder(root->_sub[i]);
}
pair<Node*,size_t> _Find(const K& key)
{
Node* cur = _root;
Node* parent = NULL;
while (cur)
{
size_t i = 0;
while (i< cur->_size) //找当前结点的key
{
if (cur->_keys[i] < key)
i++;
else if (cur->_keys[i]>key)
//cur = cur->_sub[i];
break;
else //找到了和传入key相等的关键字
return pair<Node* ,size_t>(cur,i);
}
parent = cur;
cur = cur->_sub[i];
}
return pair<Node* , size_t>(parent, -1); //没找到
}
void _Insertkey(Node* cur,const K& key,Node*sub)
{
int i = cur->_size-1;
while (i >=0)
{
if (cur->_keys[i] > key)
{
//移动关键字的位置
cur->_keys[i + 1] = cur->_keys[i];
//移动子树的位置
cur->_sub[i + 2] = cur->_sub[i + 1];
i--;
}
else
break;
}
//i记录着要插入位置的前一个
cur->_keys[i + 1] = key;
cur->_sub[i + 2] = sub;
if (sub)
sub->_parent = cur;
cur->_size++;
}
private:
Node* _root;
};
void TestBTree()
{
BTree<int, 3> bt;
int a[] = { 53, 75, 139, 49, 145, 36, 101 };
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
bt.Insert(a[i]);
}
bt.InOrder();
}