前言:
为了能够更好的理解什么是B树,这里先对所有的动态查找树做下总结:
动态二叉树总共分为:二叉查找树(Binary Search Tree),平衡二叉查找树(Balanced Binary Search Tree),红黑树(Red-Black Tree ),B-tree/B+tree/B*tree。前三者是典型的二叉查找树结构,其查找的时间复杂度O(log2N)与树的深度相关,那么降低树的高度自然就可以提高查找效率。那么如何解决降低树的高度的问题?
现在假设这么个场景咱们有面对这样一个实际问题:就是大规模数据存储中,实现索引查询这样一个实际背景下,树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),这样导致二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下,那么如何减少树的深度(当然是不能减少查询的数据量),一个基本的想法就是:采用多叉树结构(由于树节点元素数量是有限的,自然该节点的子树数量也就是有限的)。
对于多叉树主要分为三种:B-tree/B+tree/B*tree
一、Btree:
一棵M阶(M>2)的B树,是一棵平衡的M路平衡搜索树,可以是空树或者满足一下性质:
1. 根节点至少有两个孩子
2. 每个非根节点有[ ,M]个孩子
3. 每个非根节点有[ -1,M-1]个关键字,并且以升序排列
4. key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
5. 所有的叶子节点都在同一层
二、B+树
这里只分别讲一下他们的区别于联系
他们的结构如下面三个图所示:B树还算是正常的多叉树,B+树和B*树是在B树的基础上发展的。
B树与B+树的区别:
1.所有的叶子节点包含了全部关键子信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树的叶子节点并没有包括全部需要查找的信息)
2.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)
B+树与B*树的区别:
B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。
B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。
所以,B*树分配新结点的概率比B+树要低,空间使用率更高;
B树的插入和查找
#ifndef __BTREE_H__
#define __BTREE_H__
#include <iostream>
using namespace std;
template<class K,int M = 3>
struct BTreeNode
{
K _key[M]; //多放一个空间方便分裂
BTreeNode<K, M>* _subs[M + 1];
BTreeNode<K, M>* _parent;
size_t _size;
BTreeNode()
:_size(0)
{
_parent = NULL;
for (int i = 0; i <= M; i++)
{
_subs[i] = NULL;
}
}
};
template<class K,class V>
struct Pair
{
K _first;
V _second;
Pair(const K& key, const V& value)
:_first(key)
, _second(value)
{}
};
template<class K,int M = 3>
class BTree
{
typedef BTreeNode<K, M> Node;
public:
BTree()
:_root(NULL)
{}
Pair<K, int> Find(const K& key)
{
Node* cur = _root;
Node* parent = NULL;
int index = 0;
while (cur)
{
while (index < cur->_size)
{
if (cur->_key[index] < key)
++index;
else if (cur->_key[index] == key)
{
return Pair<K, M>(cur, index);
}
else
break;
}
parent = cur;
cur = cur->_subs[index];//在当前节点往下找
index = 0;
}
return Pair<K, M>(parent, -1);//如果没找到就返回父亲节点的位置,然后在父亲节点的位置插入
}
bool Insert(const K& key)
{
//没有节点,直接修改root
if (_root == NULL)
{
_root = new Node();
_root->_key[0] = key;
_root->_size++;
return true;
}
Pair<K, M> pair = Find(key);
if (pair._second != -1)
{
return -1;//已经存在
}
Node* cur = pair._first;
Node* parent = cur->_parent;
K insertKey = key;
while (1)
{
_InsertKey(cur,insertKey);//直接插入
//小于M,无需分裂
if (cur->_size < M)
{
return true;
}
//分裂调整
int mid = (cur->_size - 1) / 2;//减1是为了防止M为偶数
Node* right = new Node();
int index = 0;
//拷贝_key[]
for (int i = mid + 1; i < cur->_size; i++)
{
right->_key[index++] = cur->_key[i];
++right->_size;
}
index = 0;
//拷贝subs[]
for (int i = mid + 1; i < cur->_size; ++i)
{
right->_subs[index++] = cur->_subs[i];
if (cur->_subs[i])
{
cur->_subs[i]->_parent = right;
}
}
insertKey = cur->_key[mid];
cur->_size = (cur->_size - 1) / 2;
if (parent == NULL)//cur为根节点
{
Node* tmp = new Node();
tmp->_key[0] = insertKey;
++tmp->_size;
tmp->_subs[0] = cur;
tmp->_subs[1] = right;
cur->_parent = right->_parent = tmp;
_root = tmp;
break;
}
else
{
right->_parent = parent;
index = M - 1;
while (parent->_subs[index] != cur)
{
parent->_subs[index + 1] = parent->_subs[index];
--index;
}
parent->_subs[index + 1] = right;
cur = parent;
parent = cur->_parent;
}
}
return true;
}
void _InsertKey(Node* cur, cosnt K& key)
{
//插入排序
it index = cur->_size - 1;
while (index >= 0 && key < cur->_key[index])
{
cur->_key[index + 1] = cur->_key[index];
--index;
}
cur->_key[index + 1] = key;
++cur->_size;
}
protected:
Node* _root;
};
#endif //__BTREE_H__