B-树 —— 平衡的多叉搜索树。
1.优势:
- 如果一个节点中保存的信息多一些,那么树的高度就会降低;多个孩子节点指针意味着降低了树的高度(提高查找效率)
- 数据节点多一些,相应的也会降低IO次数。我们应该知道的是Mysql这些数据库的索引的底层实现部分就是用B-树这种结构来实现的。
2.规则:
一棵m阶B树(balanced tree of order m)是一棵平衡的m路搜索树。它或者是空树,或者是满足下列性质的树:
3.三阶模拟实现
M=3(三叉树)
1.Find() :找待插入元素的位置;
用当前元素与待插入节点中的元素依次进行比较;
- ‘==’ : return;
- ‘<’ : 在其第i个孩子中找;
- ‘>’: 继续向后比较: 1.后边有元素,继续比 2.后边没有元素,与当前元素的i+1个孩子中找
2.Insert() :插入元素:
为了简单起见,假设M = 3. 即三叉树,每个节点中存储两个数据,两个数据可以将区间分割成三个部分,因此节点应该有三个孩子,为了后续实现简单期间,节点的结构如下:
注意:孩子永远比数据多一个。
while (1){
1.满足条件按照直接插入排序思想直接插入
2.不满足条件,分裂
.1找到当前节点元素的中间位置;
.2申请一个新的节点
.3中间节点元素右边的元素以及其对应的孩子搬移到新节点中;
.4将中间元素想其双亲节点中继续插入
}
.三叉树代码示例:
//节点
template<class T, size_t M = 3>
struct BTreeNode
{
BTreeNode()
: _size(0)
, _pParent(nullptr)
{
for (size_t i = 0; i < M + 1; ++i)
_child[i] = nullptr;
}
// 存放元素的空间
T _array[M];
BTreeNode<T, M>* _child[M + 1];
size_t _size; // 当前节点中有效元素的个数
BTreeNode<T, M>* _pParent;
};
template<class T, size_t M = 3>
class BTree
{
typedef BTreeNode<T, M> Node;
public:
BTree()
: _pRoot(nullptr)
{}
pair<Node*, int> Find(const T& data)
{
Node* pCur = _pRoot;
Node* pParent = nullptr;
while (pCur)
{
size_t index = 0;
while (index < pCur->_size)
{
if (pCur->_array[index] == data)
return make_pair(pCur, index);
else if (data < pCur->_array[index])
{
break;
}
else
{
index++;
}
}
pParent = pCur;
pCur = pCur->_child[index];
}
return make_pair(pParent, -1);
}
bool Insert(const T& data)
{
if (nullptr == _pRoot)
{
_pRoot = new Node;
_pRoot->_array[0] = data;
_pRoot->_size++;
return true;
}
// 1. 找待插入元素在B-树中的位置
pair<Node*, int> ret = Find(data);
if (-1 != ret.second)
return false;
// 2. 插入元素
Node* pCur = ret.first;
T key = data;
Node* pSub = nullptr;
while (true)
{
// 将data插入pCur
InsertKey(pCur, key, pSub);
// 当前pCur节点已经违反B树的性质
if (pCur->_size != M)
return true;
//必须对当前节点进行分裂
// 找当前节点元素的中间位置
int mid = (M >> 1);
Node* pNewNode = new Node;
size_t count = 0;
// 将当前节点中间位置以后的元素搬移到新节点中
int index = mid + 1;
for (; index < pCur->_size; ++index)
{
// 搬移元素
pNewNode->_array[count] = pCur->_array[index];
// 搬移元素对应的孩子
pNewNode->_child[count] = pCur->_child[index];
// 更新孩子的双亲
if (pCur->_child[index])
pCur->_child[index]->_pParent = pNewNode;
count++;
}
// 孩子要多搬移一个
pNewNode->_child[count] = pCur->_child[index];
if (pCur->_child[index])
pCur->_child[index]->_pParent = pNewNode;
// 更新当前节点中剩余元素的个数
pCur->_size = pCur->_size - count - 1;
pNewNode->_size = count;
// 将中间元素和分裂出的节点向双亲节点中继续进行插入
// 如果待分裂的节点刚好是根节点
if (nullptr == pCur->_pParent)
{
_pRoot = new Node;
_pRoot->_array[0] = pCur->_array[mid];
_pRoot->_size = 1;
_pRoot->_child[0] = pCur;
_pRoot->_child[1] = pNewNode;
pCur->_pParent = _pRoot;
pNewNode->_pParent = _pRoot;
return true;
}
else
{
key = pCur->_array[mid];
pSub = pNewNode;
pCur = pCur->_pParent;
}
}
}
void InOrder()
{
_InOrder(_pRoot);
}
private:
// 借助插入排序的思想
void InsertKey(Node* pCur, const T& key, Node* pSub)
{
int end = pCur->_size - 1;
while (end >= 0 && key < pCur->_array[end])
{
// 搬移元素
pCur->_array[end+1] = pCur->_array[end];
// 搬移当前元素对应右侧的孩子
pCur->_child[end+2] = pCur->_child[end+1];
end--;
}
pCur->_array[end+1] = key;
pCur->_child[end+2] = pSub;
// 如果psub不为空,说明pSub一定为分裂出的新节点
// 如果psub为空,说明是在第一插入元素
if (pSub)
pSub->_pParent = pCur;
pCur->_size++;
}
void _InOrder(Node* pRoot)
{
if (nullptr == pRoot)
return;
for (size_t i = 0; i < pRoot->_size; ++i)
{
_InOrder(pRoot->_child[i]);
cout << pRoot->_array[i] << " ";
}
_InOrder(pRoot->_child[pRoot->_size]);
}
private:
Node* _pRoot;
};