目录
1. 常见的搜索结构
2. B树概念
3. B-树的插入分析
4. B-树的插入实现
4.1 B-树的节点设计
4.2 插入key的过程
4.4 B-树的简单验证
4.5 B-树的性能分析
4.6 B-树的删除
5. B+树和B*树
5.1 B+树
5.2 B*树
5.3 总结
6. B-树的应用
6.1 索引
6.2 MySQL索引简介
6.2.1 MyISAM
6.2.2 InnoDB
#include
#include
#include
using namespace std;
// 返回值:PNode代表找到的节点,int为该元素在该节点中的位置
pair Find(const K &key)
{
// 从根节点的位置开始查找
PNode pCur = _pRoot;
PNode pParent = NULL;
size_t i = 0;
// 节点存在
while (pCur)
{
i = 0;
// 在该节点的值域中查找
while (i < pCur->_size)
{
// 找到返回
if (key == pCur->_keys[i]) return pair(pCur, i);
else if (key < pCur->_keys[i]) // 该元素可能在i的左边的孩子节点中
break;
else i++;
// 继续向右查找
}
// 在pCur中没有找到,到pCur节点的第i个孩子中查找
pParent = pCur;
pCur = pCur->_pSub[i];
}
// 没有找到
return pair(pParent, -1);
}
int main()
{
system("pause");
return 0;
}
#include
#include
#include
using namespace std;
// M 叉树,即一个节点最多有M个孩子,M-1 个数据域
template
struct BTreeNode
{
K _keys[M]; //存放元素
BTreeNode *_pSub[M + 1];
BTreeNode *_pParent;
size_t _size;
BTreeNode() : _pParent(NULL), _size(0)
{
for (size_t i = 0; i <= M; i++)
{
_pSub[i] = nullptr;
}
}
};
int main()
{
system("pause");
return 0;
}
void _InsertKey(PNode pCur, const K& key, PNode pSub)
{
// 按照插入排序思想插入key
int end = pCur->_size-1;
while(end >= 0)
{
if(key < pCur->_keys[end])
{
// 将该位置元素以及其右侧孩子往右搬移一个位置
pCur->_keys[end+1] = pCur->_keys[end];
pCur->_pSub[end+2] = pCur->_pSub[end+1];
end--;
}
else
break;
}
// 插入key以及新分裂出的节点
pCur->_keys[end+1] = key;
pCur->_pSub[end+2] = pSub;
// 更新节点的双亲
if(pSub)
pSub->_pParent = pCur;
pCur->_size++;
}
4.3 B-树的插入实现
#include
#include
#include
using namespace std;
// M 叉树,即一个节点最多有M个孩子,M-1 个数据域
template
struct BTreeNode
{
K _keys[M]; //存放元素
BTreeNode *_pSub[M + 1];
BTreeNode *_pParent;
size_t _size; //节点元素的个数
BTreeNode() : _pParent(NULL), _size(0)
{
for (size_t i = 0; i <= M; i++)
{
_pSub[i] = nullptr;
}
}
bool Insert(const K &key)
{
// 如果树为空,直接插入
if (NULL == _pRoot)
{
_pRoot = new Node();
_pRoot->_keys[0] = key;
_pRoot->_size = 1;
return true;
}
// 找插入位置,如果该元素已经存在,则不插入
pair ret = Find(key);
if (-1 != ret.second)
return false;
K k = key;
PNode temp = NULL;
PNode pCur = ret.first;
while (true)
{
// 将key插入到pCur所指向的节点中
_InsertKey(pCur, k, temp);
// 检测该节点是否满足B-树的性质,如果满足则插入成功返回,否则,对pCur节点进行分裂
if (pCur->_size < M) return true;
// 申请新节点
temp = new Node;
// 找到pCur节点的中间位置
// 将中间位置右侧的元素以及孩子搬移到新节点中
int mid = (M >> 1);
for (size_t i = mid + 1; i < pCur->_size; ++i)
{
temp->_keys[temp->_size] = pCur->_keys[i];
temp->_pSub[temp->_size++] = pCur->_pSub[i];
// 跟新孩子节点的双亲
if (pCur->_pSub[i])
pCur->_pSub[i]
->_pParent = temp;
}
// 注意:孩子比关键字多搬移一个
temp->_pSub[temp->_size] = pCur->_pSub[pCur->_size];
if (pCur->_pSub[pCur->_size])
pCur->_pSub[pCur->_size]->_pParent = temp;
// 更新pCur节点的剩余数据个数
pCur->_size -= (temp->_size + 1);
// 如果分裂的节点为根节点,重新申请一个新的根节点,将中间位置数据以及分裂出的新节点
插入到新的根节点中,插入结束 if (pCur == _pRoot)
{
_pRoot = new Node;
_pRoot->_keys[0] = pCur->_keys[mid];
_pRoot->_pSub[0] = pCur;
4.4 B - 树的简单验证 对B树进行中序遍历,如果能得到一个有序的序列,说明插入正确。 4.5 B - 树的性能分析 对于一棵节点为N度为M的B - 树,查找和插入需要$log{M - 1} N$ ~$log{M / 2} N$次比较,这个很好证 明:对于度为M的B - 树,每一个节点的子节点个数为M / 2 ~(M - 1) 之间,因此树的高度应该在要 $log{M - 1} N$和$log{M / 2} N$之间,在定位到该节点后,再采用二分查找的方式可以很快的定位 到该元素。 B - 树的效率是很高的,对于N = 62 * 1000000000个节点,如果度M为1024,则 $log_{M / 2} N$ <=
4,即在620亿个元素中,如果这棵树的度为1024,则需要小于4次即可定位到该节点,然后利用 二分查找可以快速定位到该元素,大大减少了读取磁盘的次数。 4.6 B - 树的删除 学习B树的插入足够帮助我们理解B树的特性了,如果对删除有兴趣的同学们参考《算法导论》--伪代码和《数据结构 - 殷人昆》--C++ 实现代码。 5. B + 树和B * 树 5.1 B + 树 B + 树是B树的变形,是在B树基础上优化的多路平衡搜索树,B + 树的规则跟B树基本类似,但是又 在B树的基础上做了以下几点改进优化: _pRoot->_pSub[1] = temp;
_pRoot->_size = 1;
pCur->_pParent = temp->_pParent = _pRoot;
return true;
}
else
{
// 如果分裂的节点不是根节点,将中间位置数据以及新分裂出的节点继续向pCur的双亲
中进行插入
k = pCur->_keys[mid];
pCur = pCur->_pParent;
}
}
return true;
}
};
int main()
{
system("pause");
return 0;
}
#include
#include
#include
using namespace std;
// M 叉树,即一个节点最多有M个孩子,M-1 个数据域
template
struct BTreeNode
{
K _keys[M]; //存放元素
BTreeNode *_pSub[M + 1];
BTreeNode *_pParent;
size_t _size; //节点元素的个数
BTreeNode() : _pParent(NULL), _size(0)
{
for (size_t i = 0; i <= M; i++)
{
_pSub[i] = nullptr;
}
}
bool Insert(const K &key)
{
// 如果树为空,直接插入
if (NULL == _pRoot)
{
_pRoot = new Node();
_pRoot->_keys[0] = key;
_pRoot->_size = 1;
return true;
}
// 找插入位置,如果该元素已经存在,则不插入
pair ret = Find(key);
if (-1 != ret.second)
return false;
K k = key;
PNode temp = NULL;
PNode pCur = ret.first;
while (true)
{
// 将key插入到pCur所指向的节点中
_InsertKey(pCur, k, temp);
// 检测该节点是否满足B-树的性质,如果满足则插入成功返回,否则,对pCur节点进行分裂
if (pCur->_size < M) return true;
// 申请新节点
temp = new Node;
// 找到pCur节点的中间位置
// 将中间位置右侧的元素以及孩子搬移到新节点中
int mid = (M >> 1);
for (size_t i = mid + 1; i < pCur->_size; ++i)
{
temp->_keys[temp->_size] = pCur->_keys[i];
temp->_pSub[temp->_size++] = pCur->_pSub[i];
// 跟新孩子节点的双亲
if (pCur->_pSub[i])
pCur->_pSub[i]
->_pParent = temp;
}
// 注意:孩子比关键字多搬移一个
temp->_pSub[temp->_size] = pCur->_pSub[pCur->_size];
if (pCur->_pSub[pCur->_size])
pCur->_pSub[pCur->_size]->_pParent = temp;
// 更新pCur节点的剩余数据个数
pCur->_size -= (temp->_size + 1);
// 如果分裂的节点为根节点,重新申请一个新的根节点,将中间位置数据以及分裂出的新节点
插入到新的根节点中,插入结束 if (pCur == _pRoot)
{
_pRoot = new Node;
_pRoot->_keys[0] = pCur->_keys[mid];
_pRoot->_pSub[0] = pCur;
4.4 B - 树的简单验证 对B树进行中序遍历,如果能得到一个有序的序列,说明插入正确。 4.5 B - 树的性能分析 对于一棵节点为N度为M的B - 树,查找和插入需要$log{M - 1} N$ ~$log{M / 2} N$次比较,这个很好证 明:对于度为M的B - 树,每一个节点的子节点个数为M / 2 ~(M - 1) 之间,因此树的高度应该在要 $log{M - 1} N$和$log{M / 2} N$之间,在定位到该节点后,再采用二分查找的方式可以很快的定位 到该元素。 B - 树的效率是很高的,对于N = 62 * 1000000000个节点,如果度M为1024,则 $log_{M / 2} N$ <=
4,即在620亿个元素中,如果这棵树的度为1024,则需要小于4次即可定位到该节点,然后利用 二分查找可以快速定位到该元素,大大减少了读取磁盘的次数。 4.6 B - 树的删除 学习B树的插入足够帮助我们理解B树的特性了,如果对删除有兴趣的同学们参考《算法导论》--伪代码和《数据结构 - 殷人昆》--C++ 实现代码。 5. B + 树和B * 树 5.1 B + 树 B + 树是B树的变形,是在B树基础上优化的多路平衡搜索树,B + 树的规则跟B树基本类似,但是又 在B树的基础上做了以下几点改进优化: _pRoot->_pSub[1] = temp;
_pRoot->_size = 1;
pCur->_pParent = temp->_pParent = _pRoot;
return true;
}
else
{
// 如果分裂的节点不是根节点,将中间位置数据以及新分裂出的节点继续向pCur的双亲
中进行插入
k = pCur->_keys[mid];
pCur = pCur->_pParent;
}
}
return true;
}
};
void _InOrder(PNode pRoot)
{
if (NULL == pRoot)
return;
for (size_t i = 0; i < pRoot->_size; ++i)
{
_InOrder(pRoot->_pSub[i]);
cout << pRoot->_keys[i] << " ";
}
_InOrder(pRoot->_pSub[pRoot->_size]);
}
int main()
{
system("pause");
return 0;
}
B+树的特性
参考资料:
CodingLabs - MySQL索引背后的数据结构及算法原理