B 树又叫平衡多路查找树。一棵m阶的B树的特性如下:
- 树中每个结点最多含有m个孩子()
- 除根结点和叶子结点外,其它每个结点至少有个孩子
- 若根结点不是叶子结点(不是空树),则至少有2个孩子
- 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,实际上这些结点不存在,指向这些结点的指针都为NULL)
- 每个非终端结点(最底层的非叶子节点)中包含有n个关键字信息:。其中:
a) Ki (i=1...n)为关键字,且关键字按顺序升序排序K(i-1)< Ki
b) 为指向子树根的接点,且指针指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)
c) 关键字的个数n必须满足:
注:平衡m叉查找树是指每个关键字的左侧子树与右侧子树的高度差的绝对值不超过1的查找树,其结点结构与上面提到的B-树结点结构相同,由此可见,B-树是平衡m叉查找树,但限制更强,要求所有叶结点都在同一层。
下图演示的是一棵三阶B树:
B树的查找:
搜索B树与搜索二叉树很相似,只是在每个结点所做的不是二叉或者“两路”分支决定,二是根据该结点的子女数所做的多路分支决定。更准确的说,在每个内结点x处,要做n(x) + 1路的分支决定。
B树的插入:
插入操作是指插入一条记录,即(key, value)的键值对。如果B树中已存在需要插入的键值对,则用需要插入的value替换旧的value。若B树不存在这个key,则一定是在叶子结点中进行插入操作。
1)根据要插入的key的值,找到叶子结点并插入。
2)判断当前结点key的个数是否小于等于m-1,若满足则结束,否则进行第3步。
3)以结点中间的key为中心分裂成左右两部分,然后将这个中间的key插入到父结点中,这个key的左子树指向分裂后的左半部分,这个key的右子支指向分裂后的右半部分,然后将当前结点指向父结点,继续进行第3步。
向B树中插入关键字,同二叉查找树中插入一个关键字类似,要查找插入新关键字的叶子结点位置。因为不能把关键字插入到一个已满的终端结点中,故需要将一个已满的结点按其中间关键字分裂成两个结点,中间关键字被提升到该结点的父结点中。但是这种满结点的分裂动作会沿着树向上传播。为了解决这个问题,可以采取这样一种策略:当沿着树根往下查找新关键字所属位置时,就沿途分裂遇到的每个满结点。因此,每当要分裂一个满结点时,就能确保它的父结点不是满的。
B树的删除:
删除操作是指,根据key删除记录,如果B树中的记录中不存对应key的记录,则删除失败。
1)如果当前需要删除的key位于非叶子结点上,则用后继key(这里的后继key均指后继记录的意思)覆盖要删除的key,然后在后继key所在的子支中删除该后继key。此时后继key一定位于叶子结点上,这个过程和二叉搜索树删除结点的方式类似。删除这个记录后执行第2步
2)该结点key个数大于等于,结束删除操作,否则执行第3步。
3)如果兄弟结点key个数大于,则父结点中的key下移到该结点,兄弟结点中的一个key上移,删除操作结束。
否则,将父结点中的key下移与当前结点及它的兄弟结点中的key合并,形成一个新的结点。原父结点中的key的两个孩子指针就变成了一个孩子指针,指向这个新结点。然后当前结点的指针指向父结点,重复上第2步。
有些结点它可能即有左兄弟,又有右兄弟,那么我们任意选择一个兄弟结点进行操作即可。
C++实现
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
template<class T>
class CBTree
{
private:
static const int M = 3; //B树的最小度数
static const int KEY_MAX = 2 * M - 1; //节点包含关键字的最大个数
static const int KEY_MIN = M - 1; //非根节点包含关键字的最小个数
static const int CHILD_MAX = KEY_MAX + 1; //孩子节点的最大个数
static const int CHILD_MIN = KEY_MIN + 1; //孩子节点的最小个数
struct Node
{
bool isLeaf; //是否是叶子节点
int keyNum; //节点包含的关键字数量
T keyValue[KEY_MAX]; //关键字的值数组
Node* pChild[CHILD_MAX]; //子树指针数组
Node(bool b = true, int n = 0)
:isLeaf(b), keyNum(n) {}
};
public:
/* 创建一棵空的B树 */
CBTree() {
m_pRoot = NULL;
}
~CBTree() {
clear();
}
/* 向B数中插入新结点key */
bool insert(const T& key)
{
if (contain(key)) { // 检查该关键字是否已经存在
return false;
}
else {
if (m_pRoot == NULL) { // 检查是否为空树
m_pRoot = new Node();
}
if (m_pRoot->keyNum == KEY_MAX) { // 检查根节点是否已满
Node* pNode = new Node(); // 创建新的根节点
pNode->isLeaf = false;
pNode->pChild[0] = m_pRoot;
splitChild(pNode, 0, m_pRoot);
m_pRoot = pNode; //更新根节点指针
}
insertNonFull(m_pRoot, key);
return true;
}
}
bool remove(const T& key) //从B中删除结点key
{
if (!search(m_pRoot, key)) //不存在
{
return false;
}
if (m_pRoot->keyNum == 1)//特殊情况处理
{
if (m_pRoot->isLeaf)
{
clear();
return true;
}
else
{
Node* pChild1 = m_pRoot->pChild[0];
Node* pChild2 = m_pRoot->pChild[1];
if (pChild1->keyNum == KEY_MIN && pChild2->keyNum == KEY_MIN)
{
mergeChild(m_pRoot, 0);
deleteNode(m_pRoot);
m_pRoot = pChild1;
}
}
}
recursive_remove(m_pRoot, key);
return true;
}
void display()const //打印树的关键字
{
displayInConcavo(m_pRoot, KEY_MAX * 10);
}
bool contain(const T& key)const //检查该key是否存在于B树中
{
return search(m_pRoot, key);
}
void clear() //清空B树
{
recursive_clear(m_pRoot);
m_pRoot = NULL;
}
private:
//删除树
void recursive_clear(Node* pNode)
{
if (pNode != NULL)
{
if (!pNode->isLeaf)
{
for (int i = 0; i <= pNode->keyNum; ++i)
recursive_clear(pNode->pChild[i]);
}
deleteNode(pNode);
}
}
//删除节点
void deleteNode(Node*& pNode)
{
if (pNode != NULL)
{
delete pNode;
pNode = NULL;
}
}
//查找关键字
bool search(Node* pNode, const T& key)const
{
if (pNode == NULL) //检测节点指针是否为空,或该节点是否为叶子节点
{
return false;
}
else
{
int i;
for (i = 0; i<pNode->keyNum && key>* (pNode->keyValue + i); ++i)//找到使key<=pNode->keyValue[i]成立的最小下标i
{
}
if (i < pNode->keyNum && key == pNode->keyValue[i])
{
return true;
}
else
{
if (pNode->isLeaf) //检查该节点是否为叶子节点
{
return false;
}
else
{
return search(pNode->pChild[i], key);
}
}
}
}
//分裂子节点
void splitChild(Node* pParent, int nChildIndex, Node* pChild)
{
/* 将pChild分裂成pLeftNode和pChild两个节点 */
Node* pRightNode = new Node();
pRightNode->isLeaf = pChild->isLeaf;
pRightNode->keyNum = KEY_MIN;
/* 拷贝关键字的值 */
int i;
for (i = 0; i < KEY_MIN; ++i) {
pRightNode->keyValue[i] = pChild->keyValue[i + CHILD_MIN];
}
/* 如果不是叶子节点,拷贝孩子节点指针 */
if (!pChild->isLeaf) {
for (i = 0; i < CHILD_MIN; ++i) {
pRightNode->pChild[i] = pChild->pChild[i + CHILD_MIN];
}
}
/* 更新左子树的关键字个数 */
pChild->keyNum = KEY_MIN;
/* 将父节点中的nChildIndex后的所有关键字的值和子树指针向后移一位 */
for (i = pParent->keyNum; i > nChildIndex; --i) {
pParent->pChild[i + 1] = pParent->pChild[i];
pParent->keyValue[i] = pParent->keyValue[i - 1];
}
++pParent->keyNum; //更新父节点的关键字个数
pParent->pChild[nChildIndex + 1] = pRightNode; //存储右子树指针
pParent->keyValue[nChildIndex] = pChild->keyValue[KEY_MIN];//把节点的中间值提到父节点
}
/* 在非满节点中插入关键字 */
void insertNonFull(Node* pNode, const T& key)
{
int i = pNode->keyNum;
if (pNode->isLeaf) {
while (i > 0 && key < pNode->keyValue[i - 1]) {
pNode->keyValue[i] = pNode->keyValue[i - 1];
--i;
}
pNode->keyValue[i] = key;
++pNode->keyNum;
}
else {
while (i > 0 && key < pNode->keyValue[i - 1]) --i;
Node* pChild = pNode->pChild[i];
if (pChild->keyNum == KEY_MAX) {
splitChild(pNode, i, pChild);
if (key > pNode->keyValue[i])
pChild = pNode->pChild[i + 1];
}
insertNonFull(pChild, key);
}
}
/* 显示B树 */
void displayInConcavo(Node* pNode, int count)const
{
if (pNode != NULL)
{
int i, j;
for (i = 0; i < pNode->keyNum; ++i)
{
if (!pNode->isLeaf)
{
displayInConcavo(pNode->pChild[i], count - 2);
}
for (j = count; j >= 0; --j)
{
cout << "-";
}
cout << pNode->keyValue[i] << endl;
}
if (!pNode->isLeaf)
{
displayInConcavo(pNode->pChild[i], count - 2);
}
}
}
/* 合并两个子节点 */
void mergeChild(Node* pParent, int index)
{
Node* pChild1 = pParent->pChild[index];
Node* pChild2 = pParent->pChild[index + 1];
pChild1->keyNum = KEY_MAX;
pChild1->keyValue[KEY_MIN] = pParent->keyValue[index];//将父节点index的值下移
int i;
for (i = 0; i < KEY_MIN; ++i) {
pChild1->keyValue[i + KEY_MIN + 1] = pChild2->keyValue[i];
}
if (!pChild1->isLeaf) {
for (i = 0; i < CHILD_MIN; ++i) {
pChild1->pChild[i + CHILD_MIN] = pChild2->pChild[i];
}
}
/* 父节点删除index的key,index后的往前移一位 */
--pParent->keyNum;
for (i = index; i < pParent->keyNum; ++i) {
pParent->keyValue[i] = pParent->keyValue[i + 1];
pParent->pChild[i + 1] = pParent->pChild[i + 2];
}
deleteNode(pChild2);
}
//递归的删除关键字
void recursive_remove(Node* pNode, const T& key)
{
int i = 0;
while (i<pNode->keyNum && key>pNode->keyValue[i])
++i;
/* 关键字key在节点pNode中 */
if (i < pNode->keyNum && key == pNode->keyValue[i])
{
/* pNode是个叶节点 */
if (pNode->isLeaf) {
/* 从pNode中删除k */
--pNode->keyNum;
for (; i < pNode->keyNum; ++i) {
pNode->keyValue[i] = pNode->keyValue[i + 1];
}
return;
}
else { //pNode是个内节点
/* 节点pNode中前于key的子节点 */
Node* pChildPrev = pNode->pChild[i];
/* 节点pNode中后于key的子节点 */
Node* pChildNext = pNode->pChild[i + 1];
/* 节点pChildPrev中至少包含CHILD_MIN个关键字 */
if (pChildPrev->keyNum >= CHILD_MIN) {
/* 获取key的前驱关键字 */
T prevKey = getPredecessor(pChildPrev);
recursive_remove(pChildPrev, prevKey);
/* 替换成key的前驱关键字 */
pNode->keyValue[i] = prevKey;
return;
}
else if (pChildNext->keyNum >= CHILD_MIN) { //节点pChildNext中至少包含CHILD_MIN个关键字
/* 获取key的后继关键字 */
T nextKey = getSuccessor(pChildNext);
recursive_remove(pChildNext, nextKey);
/* 替换成key的后继关键字 */
pNode->keyValue[i] = nextKey;
return;
}
else {
/* 节点pChildPrev和pChildNext中都只包含CHILD_MIN-1个关键字 */
mergeChild(pNode, i);
recursive_remove(pChildPrev, key);
}
}
}
else { //关键字key不在节点pNode中
/* 包含key的子树根节点 */
Node* pChildNode = pNode->pChild[i];
/* 只有t-1个关键字 */
if (pChildNode->keyNum == KEY_MIN) {
/* 左兄弟节点 */
Node* pLeft = i > 0 ? pNode->pChild[i - 1] : NULL;
/* 右兄弟节点 */
Node* pRight = i < pNode->keyNum ? pNode->pChild[i + 1] : NULL;
/* 左兄弟节点至少有CHILD_MIN个关键字 */
int j;
if (pLeft && pLeft->keyNum >= CHILD_MIN) {
/* 父节点中i-1的关键字下移至pChildNode中 */
for (j = pChildNode->keyNum; j > 0; --j) {
pChildNode->keyValue[j] = pChildNode->keyValue[j - 1];
}
pChildNode->keyValue[0] = pNode->keyValue[i - 1];
if (!pLeft->isLeaf) {
/* pLeft节点中合适的子女指针移植到pChildNode中 */
for (j = pChildNode->keyNum + 1; j > 0; --j) {
pChildNode->pChild[j] = pChildNode->pChild[j - 1];
}
pChildNode->pChild[0] = pLeft->pChild[pLeft->keyNum];
}
++pChildNode->keyNum;
/* pLeft节点中的最大关键字上升到pNode中 */
pNode->keyValue[i] = pLeft->keyValue[pLeft->keyNum - 1];
--pLeft->keyNum;
}
else if (pRight && pRight->keyNum >= CHILD_MIN) { //右兄弟节点至少有CHILD_MIN个关键字
/* 父节点中i的关键字下移至pChildNode中 */
pChildNode->keyValue[pChildNode->keyNum] = pNode->keyValue[i];
++pChildNode->keyNum;
/* pRight节点中的最小关键字上升到pNode中 */
pNode->keyValue[i] = pRight->keyValue[0];
--pRight->keyNum;
for (j = 0; j < pRight->keyNum; ++j) {
pRight->keyValue[j] = pRight->keyValue[j + 1];
}
if (!pRight->isLeaf) {
/* pRight节点中合适的子女指针移植到pChildNode中 */
pChildNode->pChild[pChildNode->keyNum] = pRight->pChild[0];
for (j = 0; j <= pRight->keyNum; ++j) {
pRight->pChild[j] = pRight->pChild[j + 1];
}
}
}
else if (pLeft) { //与左兄弟合并
mergeChild(pNode, i - 1);
pChildNode = pLeft;
}
else if (pRight) { //与右兄弟合并
mergeChild(pNode, i);
}
}
recursive_remove(pChildNode, key);
}
}
/* 找到前驱关键字 */
T getPredecessor(Node* pNode)
{
while (!pNode->isLeaf) {
pNode = pNode->pChild[pNode->keyNum];
}
return pNode->keyValue[pNode->keyNum - 1];
}
/* 找到后继关键字 */
T getSuccessor(Node* pNode)
{
while (!pNode->isLeaf) {
pNode = pNode->pChild[0];
}
return pNode->keyValue[0];
}
private:
Node* m_pRoot; //B树的根节点
};
int main(int argc, char* argv[])
{
CBTree<int> m_btree;
m_btree.insert(10);
m_btree.insert(14);
m_btree.insert(19);
m_btree.insert(33);
m_btree.insert(22);
m_btree.insert(2);
m_btree.insert(56);
m_btree.insert(78);
m_btree.insert(45);
m_btree.insert(16);
m_btree.insert(37);
m_btree.insert(65);
m_btree.insert(61);
m_btree.display();
return 0;
}
B-树的应用
为了将大型数据库文件存储在硬盘上,以减少访问硬盘次数为目的,在此提出了一种平衡多路查找树——B-树结构。由其性能分析可知它的检索效率是相当高的 为了提高B-树性能还有很多种B-树的变型,力图对B-树进行改进,比如B+树。
其它可供参考的博文: