B树的原理及实现
B树的作用
B树是一种存放在磁盘中的数据结构,是为了减少磁盘读写的次数,同时也不影响查找效率而设计的。B+树在B树的基础上可以实现链式遍历。B树每个结点具有key值数组和索引数组,将数组大小定位页的倍数可以使计算机方便读取每一个结点。索引比key值多一个,相当于每一个key值都在两个索引中间,左边的索引小于key值,右边的索引大于key值,每一次都可以查找到需要读取的结点的索引地址,所以可以有效的减少磁盘读取次数。
在数据库中大量的使用B+树作为索引存储的数据结构。
B树的原理
B树有一个固定的阶数(M),该阶数一般为页的倍数,方便磁盘每次读取。
B树具有以下性质:
1.每个结点至多拥有M颗子树
2.根结点至少用于两颗子树
3.除了根结点以外,其余每个分支结点至少拥有M/2颗子树
4.所有的叶结点都在同一层上
5.有k颗子树的分支结点则存在k-1个关键字,关键字按照递增顺序进行排序
6.关键字数量满足ceil(M / 2) - 1 <= n <= M - 1
当一个结点数量超过M-1时,需要进行分裂,将结点分开。
结点的最小值公式:
if M mod 2 == 0
leftNum = (M - 1) / 2
else
leftNum = M / 2
分裂:
- 创建右边结点(right),将左结点右边(结点的最小值 + 2开始)的信息拷贝到右结点里。
- 如果不是叶子结点,拷贝对应的子结点信息。
- 如果是叶子结点,将右节点也设置为叶子结点。
- 如果当前的结点是根结点,创建新的根结点,将左右结点的父结点指向新的根结点,并添加根结点的索引,将中间值上移至根结点中。
- 如果不是根结点,需要判断左边结点在根结点中所处的位置,将右边结点的索引插入到左边结点后面,并将中间key值插入至左边结点的后面。
除了根结点外的结点,如果数量小于最小数量(结点的最小值),则需要进行调整,可以向左右兄弟借值。如果借不到则需要进行合并。
合并:
- 将左右结点合并,需要将父结点的值下沉到左边结点,删除父结点中对应的key值和value,删除右边结点的索引信息。
- 将右结点的信息向左结点后面添加,如果不是叶结点需要将索引也拷贝到左结点中。
接下来就谈谈怎么插入。
插入:
- 当B树是空树,则直接插入至头结点。
- 如果不是空树,则需要依照key值向下寻找,直到找到叶结点为止,插入到叶结点中。向下寻找就是通过key值判断。从根结点开始,可以从前开始遍历,如果小于key值则进入到key值左边的索引向下继续,
如果大于key值则向后继续遍历,直到最后一个key值。也可以从后开始遍历,如果大于就进入到key值右边的索引向下继续,如果小于key值则向前继续遍历,直到第一个key值为止。 - 如果当前结点是叶结点,直接将数值插入到相应的位置,使用插入排序的思想就行(待插入key从后向前比较,如果待插入key小则将数据向后移动,直到待插入key大于或等于停止)。
- 循环向上判断,从插入的结点开始,如果key值数量超过M - 1,则将结点分裂。又因为分裂需要向父结点插值,所以分裂后继续判断父结点,依次类推,循环向上。
插入完后就是删除了。
删除:
- 首先查找key是否存在,不存在就删除失败。
- 如果删除的结点不是叶子结点,找到比当前结点小的最大叶结点,key左边索引树的最右子树。
这个结点是实际删除的结点,找到最后一个key,将其key和value覆盖逻辑上需要删除的结点,将其结点key数减一。 - 如果删除的结点就是叶子结点,将该key值后面的数据向前覆盖。
- 判断实际删除的结点,key数是否小于结点的最小值,如果小于结点最大小值,则进行修复。
- 修复完wDelete后继续向上修复,将wDelete指向父结点。
- 如果 根结点为空或者(wDelete是根结点 并且 wDeleet的key数大于0)就结束修复。
删除修复:
- 判断wDelete是否为根结点,
- 是根节点又是叶子结点的直接删除根结点(因为进入删除修复的根结点key数一定为0)。
- 是根节点不是叶结点,将根结点指向第一个索引(这种情况一定是根结点只有两个子树进行了合并,只剩下一个子树了)。
- 剩下的情况wDelete不是根结点,一定有父结点。
- 查看自己是父结点的第几个孩子(n - 1)。
- 如果有左兄弟并且左兄弟的key数大于结点的最小值,则向左兄弟借值,将父结点第一个小于本节点最小值的key(n)下沉,插入到本结点最左,将左兄弟结点的最大key上移。
- 如果有右兄弟并且右兄弟的key数大于结点的最小值,则向右兄弟借值,将父结点的第一个大于本节点的最大值的key(n)下沉,插入到本结点最后,将右兄弟第一个key上移。
- 如果左右兄弟的key数都刚好等于结点的最小值。
- 有左兄弟就和左兄弟合并
- 有右兄弟就和右兄弟合并
二叉树和红黑树的实现
我是按照对原理的理解,和观看相关博客、伪代码,又结合了泛型编程的方式,自己实现的二叉树和红黑树。算法流程和算法导论中的不完全一致,没有算法导论简洁。仅供参考,具体代码如下:
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
using namespace std;
/* $begin bTree */
/*
一颗M阶B树T,满足以下条件
1.每个结点至多拥有M颗子树
2.根结点至少用于两颗子树
3.除了根结点以外,其余每个分支结点至少拥有M/2颗子树
4.所有的叶结点都在同一层上
5.有k颗子树的分支结点则存在k-1个关键字,关键字按照递增顺序进行排序
6.关键字数量满足ceil(M / 2) - 1 <= n <= M - 1
*/
// B树的关系特性
template <typename __TYPE>
struct BCharacter
{
__TYPE* parent;
__TYPE** children;
};
// B树的结点
template <typename __KEY, typename __VALUE>
struct BNode
{
BCharacter<BNode<__KEY, __VALUE>> bCharacter;
__KEY* key;
__VALUE* value;
int num;
bool leaf;
};
// B树类
template <typename __KEY, typename __VALUE>
class BTree
{
public:
typedef BNode<__KEY, __VALUE> BNodeT;
typedef BTree<__KEY, __VALUE> BTreeT;
// 初始化为空树
BTree(int m) noexcept
: root(nullptr), M(m){}
// 销毁树
~BTree() noexcept
{
destroyTreeAction(root);
}
// 树是空
bool
isEmpty() noexcept
{
if(root == nullptr)
return true;
else
return false;
}
// 插入B树结点
bool
insertNode(__KEY p_key, __VALUE p_value) noexcept
{
// 当B树为空时,创建头结点
if(isEmpty())
{
root = createNode();
root->leaf = true;
}
BNodeT* cur = root;
int i;
while(cur->leaf == false)
{// 当前结点不为叶结点,依照key向下寻找,直到找到叶结点
for(i = cur->num - 1; p_key <= cur->key[i] && i >= 0;i--);
cur = cur->bCharacter.children[i + 1];
}
// 当前结点为叶结点,将数值插入到相应的位置
for(i = cur->num - 1; p_key < cur->key[i] && i >= 0;i--)
{
cur->key[i + 1] = cur->key[i];
cur->value[i + 1] = cur->value[i];
}
cur->key[i + 1] = p_key;
cur->value[i + 1] = p_value;
cur->num++;
while(cur->num == M)
{// 超出了M - 1,将叶子结点分裂,因为分裂向父结点插值,
// 所以继续判断父结点是否超出M - 1
bTreeSplit(cur);
cur = cur->bCharacter.parent;
}
return false;
}
// 查找B树结点的value
bool findNode(__KEY p_key, __VALUE& p_value) noexcept
{
int i = 0;
BNodeT* res = getNode(p_key, i);
if(res == nullptr)return false;
p_value = res->value[i];
return true;
}
// 修改B树结点
bool updateNode(__KEY p_key, __VALUE p_value) noexcept
{
int i = 0;
BNodeT* res = getNode(p_key, i);
if(res == nullptr)return false;
res->value[i] = p_value;
return true;
}
// 删除B树结点
bool deleteNode(__KEY p_key) noexcept
{
int index = 0;
BNodeT* res = getNode(p_key, index);
if(res == nullptr)return false;
BNodeT* wDelete = res;
if(wDelete->leaf == false)
{// 如果待删除结点不是叶结点,找到比当前结点小的最大叶结点
wDelete = wDelete->bCharacter.children[index];
while(wDelete->leaf == false)
{// 一直向最大子树寻找
wDelete = wDelete->bCharacter.children[wDelete->num];
}
// 待删除结点为叶结点
res->key[index] = wDelete->key[wDelete->num - 1];
res->value[index] = wDelete->value[wDelete->num - 1];
wDelete->num--;
}
else
{// 待删除结点是叶结点
wDelete->num--;
for(int i = index; i < wDelete->num; i++)
{
wDelete->key[i] = wDelete->key[i + 1];
wDelete->value[i] = wDelete->value[i + 1];
}
}
// 修复B树
while(wDelete->num < getMinNum())
{// 小于最小结点数,需要修复
deleteFixUp(wDelete);
if(wDelete != root)
wDelete = wDelete->bCharacter.parent;
if(root == nullptr ||
(wDelete == root && wDelete->num > 0))break;
}
return true;
}
// 销毁B树
bool destroyTree() noexcept
{return destroyTreeAction(root);};
// 遍历B树
bool dispBinTree() noexcept
{return dispBinTreeAction(root);};
// 展示B树结构
bool showBinTree() noexcept
{return showBinTreeAction(root, 1);};
private:
// 分裂结点
void bTreeSplit(BNodeT* cur)
{
BNodeT* left = cur, *right = createNode(), *parent;
int lNum = getMinNum() - 1,
rNum = lNum + 2;
int j;
for(int i = rNum, j = 0; i < M; i++, j++)
{
// 将左结点右边的信息拷贝至右结点
right->key[j] = left->key[i];
right->value[j] = left->value[i];
}
if(cur->leaf == false)
{// 不是叶子结点
for(int i = rNum, j = 0; i <= M; i++, j++)
{
// 拷贝对应的子结点信息
right->bCharacter.children[j] =
left->bCharacter.children[i];
left->bCharacter.children[i] = nullptr;
right->bCharacter.children[j]->bCharacter.parent = right;
}
}
else
{// 是叶子结点,将右结点置为叶子结点
right->leaf = true;
}
left->num = lNum + 1;
right->num = M - rNum;
if(cur == root)
{// 如果是根结点需要开辟两个新结点
BNodeT* r = createNode();
root = r;
r->bCharacter.parent = nullptr;
// 修复对应的结点关系
left->bCharacter.parent = r;
r->bCharacter.children[0] = left;
}
right->bCharacter.parent = left->bCharacter.parent;
parent = left->bCharacter.parent;
int i;
// 将数值和关系腾出位置,并找到相应位置
for(i = parent->num; left != parent->bCharacter.children[i] && i >= 0;i--)
{
parent->key[i] = parent->key[i - 1];
parent->value[i] = parent->value[i - 1];
parent->bCharacter.children[i + 1] =
parent->bCharacter.children[i];
}
parent->key[i] = left->key[lNum + 1];
parent->value[i] = left->value[lNum + 1];
parent->bCharacter.children[i + 1] = right;
parent->num++;
}
bool mergeNode(BNodeT* parent, int l, int r)
{
BNodeT* left = parent->bCharacter.children[l],
*right = parent->bCharacter.children[r];
int i, j;
// 将父结点的值下沉
left->value[left->num] = parent->value[l];
left->key[left->num++] = parent->key[l];
if(parent->num > 0)parent->num--;
for(i = l; i < parent->num; i++)
{
parent->key[i] = parent->key[i + 1];
parent->value[i] = parent->value[i + 1];
parent->bCharacter.children[i + 1] =
parent->bCharacter.children[i + 2];
}
parent->bCharacter.children[i + 1] =
parent->bCharacter.children[i + 2];
// 将右结点的值拷贝至左结点
for(i = left->num, j = 0; j < right->num; i++, j++)
{
left->key[i] = right->key[j];
left->value[i] = right->value[j];
left->num++;
if(left->leaf == false)
{// 左孩子不是叶结点
left->bCharacter.children[i] =
right->bCharacter.children[j];
left->bCharacter.children[i]->bCharacter.parent = left;
}
}
if(left->leaf == false)
{// 左孩子不是叶结点
left->bCharacter.children[i] =
right->bCharacter.children[j];
left->bCharacter.children[i]->bCharacter.parent = left;
}
destroyNode(right);
return true;
}
bool deleteFixUp(BNodeT*& wDelete)
{
if(wDelete == root)
{
if(root->leaf == true)
{
destroyNode(root);
root = nullptr;
return true;
}
root = root->bCharacter.children[0];
destroyNode(wDelete);
wDelete = root;
return true;
}
BNodeT* parent = wDelete->bCharacter.parent,
*lBrother, *rBrother;
int n = 0;
// 查看自己是父结点的第几个孩子
for(n = 0; wDelete != parent->bCharacter.children[n] &&
n <= parent->num; n++);
// 1.尝试借位
if(n != 0)
{// 有左兄弟
lBrother = parent->bCharacter.children[n - 1];
if(lBrother->num > getMinNum())
{// 有多的结点
int i;
// 将父结点值下沉
for(i = 0; i < wDelete->num; i++)
{
wDelete->key[i + 1] = wDelete->key[i];
wDelete->value[i + 1] = wDelete->value[i];
wDelete->bCharacter.children[i + 1] =
wDelete->bCharacter.children[i];
}
wDelete->bCharacter.children[i + 1] =
wDelete->bCharacter.children[i];
wDelete->key[0] = parent->key[n - 1];
wDelete->value[0] = parent->value[n - 1];
wDelete->num++;
// 将左兄弟最大值上移
lBrother->num--;
parent->key[n - 1] = lBrother->key[lBrother->num];
parent->value[n - 1] = lBrother->value[lBrother->num];
// 将左兄弟最有子结点放在右兄弟最左
wDelete->bCharacter.children[0] =
lBrother->bCharacter.children[lBrother->num];
return true;
}
}
if(n != parent->num)
{// 有右兄弟
int i;
rBrother = parent->bCharacter.children[n + 1];
if(rBrother->num > getMinNum())
{// 有多的结点
// 将父结点值下沉
wDelete->key[wDelete->num] = parent->key[n];
wDelete->value[wDelete->num] = parent->value[n];
wDelete->num++;
// 将右兄弟最小值上移
rBrother->num--;
parent->key[n] = rBrother->key[0];
parent->value[n] = rBrother->value[0];
wDelete->bCharacter.children[wDelete->num] =
rBrother->bCharacter.children[0];
// 将右兄弟第一个结点和子树移除
for(i = 0; i < rBrother->num; i++)
{
rBrother->key[i] = rBrother->key[i + 1];
rBrother->value[i] = rBrother->value[i + 1];
rBrother->bCharacter.children[i] =
rBrother->bCharacter.children[i + 1];
}
rBrother->bCharacter.children[i] =
rBrother->bCharacter.children[i + 1];
return true;
}
}
// 2.合并
if(n != 0)
{// 和左兄弟合并
wDelete = parent->bCharacter.children[n - 1];
mergeNode(parent, n - 1, n);
return true;
}
else
{// 和右兄弟合并
wDelete = parent->bCharacter.children[n];
mergeNode(parent, n, n + 1);
return true;
}
return true;
}
// 得到最小数量
int getMinNum()
{
if(M % 2 == 0)return (M - 1) >> 1;
return M >> 1;
}
// 得到B树结点和索引
BNodeT* getNode(__KEY p_key, int& index) noexcept
{
BNodeT* res = root;
int i;
while(res != nullptr)
{// 当前结点不为叶结点,依照key向下寻找,直到找到叶结点或找到key
for(i = res->num - 1; p_key <= res->key[i] && i >= 0;i--)
{
if(p_key == res->key[i])
{
index = i;
return res;
}
}
if(res->leaf == true)return nullptr;
res = res->bCharacter.children[i + 1];
}
return nullptr;
}
// 创建新结点
BNodeT*
createNode() noexcept
{
// 创建当前结点并初始化
BNodeT* cur = new BNodeT;
cur->value = new __VALUE[M];
cur->key = new __KEY[M];
cur->num = 0;
cur->leaf = false;
cur->bCharacter.parent = nullptr;
cur->bCharacter.children = new BNodeT*[M + 1];
for(int i = 0; i <= M; i++)
cur->bCharacter.children[i] = nullptr;
return cur;
}
// 销毁叶结点
bool
destroyNode(BNodeT*& cur) noexcept
{
if(cur == nullptr) return false;
BNodeT* wDelete = cur;
delete[] cur->value;
delete[] cur->key;
delete[] cur->bCharacter.children;
delete cur;
cur = nullptr;
return true;
}
// 后序遍历销毁二叉树
bool
destroyTreeAction(BNodeT* cur) noexcept
{
if(cur == nullptr)return false;
for(int i = 0; i < cur->num + 1; i++)
destroyTreeAction(cur->bCharacter.children[i]);
destroyNode(cur);
return true;
}
// 采用中序遍历输出顺序序列
bool
dispBinTreeAction(BNodeT* cur)
noexcept
{
if(cur == nullptr)return false;
dispBinTreeAction(cur->bCharacter.children[0]);
for(int i = 1; i < cur->num + 1; i++)
{
cout << endl << "key :\t" << cur->key[i - 1]
<< "\tvalue :\t" << cur->value[i - 1] << endl;
dispBinTreeAction(cur->bCharacter.children[i]);
}
return true;
}
// 采用先序遍历输出二叉树结构
bool
showBinTreeAction(BNodeT* cur,int deep)
noexcept
{
if(cur == nullptr)return false;
cout << endl << "deep : " << deep << endl;
cout << "key : \t";
for(int i = 0; i < cur->num; i++)
cout << cur->key[i] << "\t";
cout << endl;
cout << "value : \t";
for(int i = 0; i < cur->num; i++)
cout << cur->value[i] << "\t";
cout << endl;
for(int i = 0; i < cur->num + 1; i++)
showBinTreeAction(cur->bCharacter.children[i], deep + 1);
return true;
}
BNodeT* root;
int M;
};
/* $end bTree */
template <typename TreeT>
void test(TreeT& t)
{
while(1)
{
int op = 0;
int key;
cout << endl << "cin operator(1.insert 2.delete) : ";
cin >> op;
if(op == 1)
{
cout << "insert key: ";
cin >> key;
t.insertNode(key, key + 96);
t.dispBinTree();
t.showBinTree();
}
else if(op == 2)
{
cout << "delete key : ";
cin >> key;
t.deleteNode(key);
t.dispBinTree();
t.showBinTree();
}
}
}
int main(int argc, char **argv)
{
BTree<int, char> t(6);
// test(t);
/*
t.insertNode(5, 5);
t.insertNode(4, 4);
t.insertNode(2, 2);
t.insertNode(3, 3);
t.insertNode(7, 7);
t.insertNode(6, 6);
t.insertNode(8, 8);
t.dispBinTree();
t.showBinTree();
*/
test(t);
return 0;
}