【寒江雪】B树的一般知识与简单实现
来源:互联网 发布:阿里云幕布 高清下载 编辑:程序博客网 时间:2024/05/16 08:00
B树的定义
一棵B树T是具有如下性质的有根树
- 每个结点x具有如下性质
- x.n表示当前存储在结点x中的关键字个数
- x.n个关键字本身x.key1,x.key2,x.key3…,x.keyn,以非降序存放使得x.key1 ≤ x.key2≤x.key3 ≤…≤x.keyn
- x.leaf是一个布尔值,如果x是叶结点则为true,否则为fakse
- 每个内部结点x还包含x.n+1个指向其孩子的指针x.c1,x.c2,x.c3…x.cn+1。叶子结点没有孩子,所以它们的ci属性没有定义
- 关键字x.keyi对存储在各子树中的关键字的范围加以分割:如果ki为任意一个存储在以x.ci为根的子树中的关键字,那么
k1 ≤ x.key1 ≤ k2 ≤ x.key2 ≤ k3 ≤ … ≤ x.keyn ≤ kn+1
- 每个叶结点具有相同的深度,即树高h
- 每个结点所包含的关键字个数有个上界和下界。用一个被称为B树的最小度数的固定整数t ( t ≥ 2) 来表示这些界:
- 除了根结点以外的每个结点必须至少有t-1个关键字。因此,除了根节点意外的每个内部结点至少有 t 个孩子。如果树非空,根结点至少有一个关键字。
- 每个结点至多可包含2t-1个关键字。因此,一个内部结点至多可有2t个孩子。当一个结点恰好有2t-1个关键字时,称该结点是满的。
【多说一句】t=2的B树是最简单的。每个内部结点有2个,3个或4个孩子,即一棵2-3-4树。然而在实际中,t越大,B树的高度越小。
B树中插入一个结点
在B树中,不能简单地创建一个新的叶结点,然后将其插入,因为这样得到的树将不再是合法的B树。相反,我们是将新的关键字插入一个已经存在的叶结点上。由于不能将关键字插入一个满的叶结点,故引入一个操作,将一个满的结点y按其中间关键字分裂为两个各含t-1个关键字的结点。中间关键字被提升到父亲结点中.
分裂伪代码
B-TREE-SPLIT-CHILD(x,i) z=ALLOCATE-NODE() y=x.ci z.leat=y.leat z.n=t-1 for j = 1 to t-1 z.keyj=y.keyj+t if not y.leaf for j = 1 to t z.ci=y.cj+t y.n=t - 1 for j = x.n + 1 downto i + 1 x.cj+1=x.cj x.ci+1=z for j = x.n downto i x.keyj+1=x.keyj x.keyi=y.keyt x.n=x.n+1
以沿树向下方式向B树中插入关键字
在一棵高度为h的B树中,以沿树单程向下方式插入一个关键字k的操作需要O(h)次磁盘存取。所需要的CPU时间为O(th)=O(tlogtn)。过程B-TREE-INSERT利用B-TREE-SPLIT-CHILD来保证递归始终不会将至一个满结点上。
B-TREE-INSERT(T,k) r=t.root if r.n==2t-1 s=ALLOCATE-NODE() T.root=s s.leaf=FALSE s.n=0 s.c1=r B-TREE-SPLIT(s,1) B-TREE-INSERT-NONFULL(s,k) else B-TREE-INSERT-NONFULL(r,k)B-TREE-INSERT-NONFULL(x,k) i=x.n if x.leaf while i ≥ t and k < x.keyi x.keyi+1=x.keyi i=i-1 x.keyi+1=k x.n=x.n+1 else while i ≥ 1 and k < x.keyi i=i-1 i=i+1 if x.ci == 2t-1 B-TREE-SPLIT-CHILD(x,i) if k > x.keyi i=i+1 B-TREE-INSERT-NONFULL(x.ci,k)
从B树中删除关键字
我们来简要地介绍删除操作是如何工作的
- 如果关键字k在叶子结点中,并且x是叶结点,则从x中删除k。
- 如果关键字k在内部结点中,并且x是内部结点,则做如下操作
- 如果结点x中前于k的子节点y至少包含t个关键字,则找出k在以y为根的子树中的前驱k’,递归地删除k’,并在x中用k’代替k。
- 对称的,如果结点x中后于k的子结点y至少包含t个关键字,则找出k在以y为根的子树中的后继k’,递归地删除k’,并在x中用k’代替k
- 否则,如果y和z都只含有t-1个关键字,则将k和z的全部合并进y,这样x就失去了k和指向z的指针,并且y现在包含2t-1个关键字。然后释放z并递归地从y中删除k
- 如果关键字k不在内部结点x中,则必须确定包含k的子树的根x.ci。如果x.ci只有t-1个关键字,必须执行以下操作来保证递归不会降至一个关键字少于t的结点。然后,通过对x的某个合适的子结点进行递归而结束。
- 如果x.ci只含有t-1个关键字,但是它的一个相邻的兄弟至少含有t个关键字,则将x中的某一个关键字降至x.ci中,将x.ci的相邻左兄弟或右兄弟的一个关键字升至x,将该兄弟中相应的孩子指针移到x.ci中,这样就使得x.ci增加了一个额外的关键字。
- 如果x.ci以及x.ci的所有相邻兄弟都只包含t-1个关键字,则将x.ci与一个兄弟合并,即将x的一个关键字移至新合并的新节点,使之成为该结点的中间关键字。
代码实现
树上的节点定义
//B树上的节点struct Node { int* keys; int key_size; Node** childs; int child_size; bool leaf;//标记是否是叶子节点};typedef Node BTREE_NODE;typedef Node* PBTREE_NODE;
树的定义
const int t;//B树的度数,除了根之外的节点至少有t-1个关键字和t个孩子,至多有2t-1个关键字和2t个孩子PBTREE_NODE root;
初始化构造函数
BTree(int t_=2):t(t_){//度数至少为2 root = new BTREE_NODE(); root->leaf = true; root->keys = new int[2*t]; root->key_size = 0; root->childs = new PBTREE_NODE[2*t+1]; root->child_size = 0; }
插入一个元素k
void Insert(int k) { PBTREE_NODE r = root; if (r->key_size == 2 * t-1) { PBTREE_NODE s = new BTREE_NODE(); s->childs = new PBTREE_NODE[2 * t + 1]; s->keys = new int[2 * t]; root = s; s->leaf = false; s->key_size = 0; s->childs[1] = r; s->child_size = 1; Split_Child(s, 1); Insert_NONFull(s, k); } else { Insert_NONFull(r, k); } }
分裂x的第i个孩子结点
void Split_Child(PBTREE_NODE x, int i) { PBTREE_NODE z = new BTREE_NODE();//新生成的节点z PBTREE_NODE y = x->childs[i];//x的第i个孩子 z->childs = new PBTREE_NODE[t * 2 + 1];//初始化指针数组 z->child_size = 0; z->keys = new int[2 * t];//初始化关键字数组 z->key_size = 0; z->leaf = y->leaf;//节点z是否是叶子可以根据y是否为叶子来判断 z->key_size=t-1; for (int j = 1; j <= t - 1; j++) {//将y中的后t-1个关键字移到z中 z->keys[j] = y->keys[j + t]; } if (!z->leaf) {//如果z不是叶子节点,就把y中的后t个孩子指针复制到z中 for (int j = 1; j <= t; j++) { z->childs[j] = y->childs[j + t]; } z->child_size = t; } y->key_size=t-1; //将x的孩子后移,准备插入z的指针 for (int j = x->key_size + 1; j >= i + 1;j--) { x->childs[j + 1] = x->childs[j]; } x->childs[i+1] = z; for (int j = x->key_size; j >= i; j--) { x->keys[j + 1] = x->keys[j]; } x->keys[i] = y->keys[t]; x->key_size++; }
将k插入到x中或x的某个非满的子树上。(这段代码防止递归落到满子树中)
void Insert_NONFull(PBTREE_NODE x, int k) { int i = x->key_size; if (x->leaf) { while (i >= 1 && x->keys[i] > k) { x->keys[i + 1] = x->keys[i]; i--; } x->keys[i + 1] = k; x->key_size++; } else { while (i >= 1 && x->keys[i] > k) { i--; } i++; if (x->childs[i]->key_size == 2 * t - 1) { Split_Child(x, i); if (k > x->keys[i]) i++; } Insert_NONFull(x->childs[i], k); } }
在当前结点中查找k并返回k所在位置,若不存在则返回-1
int Find(PBTREE_NODE now, int k) { int l = 1; int r = now->key_size; int mid; while (l<r) { mid = (l + r) >> 1; if (now->keys[mid] >= k) { r = mid; } else { l = mid+1; } } if (now->keys[l] == k) return l; else return -1; }
查找某前驱结点,返回结点的指针
PBTREE_NODE FindForwardNode(PBTREE_NODE _root) { if (_root->leaf) { return root; } else { return FindForwardNode(_root->childs[root->child_size]); } }
查找后继结点,返回结点指针
PBTREE_NODE FindBackwardNode(PBTREE_NODE root) { if (root->leaf) { return root; } else { return FindBackwardNode(root->childs[1]); } }
从树中删除元素k
void Delete(int k) { Delete_Inside(root, k); }
内部调用的删除函数。递归删除元素k。
void Delete_Inside(PBTREE_NODE now, int k) { int i = Find(now, k); if (i != -1) { //在当前结点中找到了k if (now->leaf) { //如果当前结点是叶子结点 //则从当前结点中删除k for (int j = i; j < now->key_size; j++) { now->keys[j] = now->keys[j + 1]; } now->key_size--; }//以上是对叶子结点处理的代码块 else {//k在内部节点 if (now->childs[i]->key_size >= t) {//前于k的结点中至少包含t的关键字 PBTREE_NODE y=FindForwardNode(now->childs[i]); int k_ = y->keys[y->key_size]; now->keys[i] = k_; Delete_Inside(now->childs[i], k_); } else if (now->childs[i + 1]->key_size >= t) {//后于k的结点中至少包含t个关键字 PBTREE_NODE z = FindBackwardNode(now->childs[i + 1]); int k_ = z->keys[1]; now->keys[i] = k_; Delete_Inside(now->childs[i + 1], k_); } }//以上是对内部结点处理的代码块 //以上是在结点中有k的情况的处理 } else {//在当前结点中找不到k int j = now->key_size; while (j >= 1 && now->keys[j] > k) { j--; } j++; //now.cj即是包含k的子树 if (now->childs[j]->key_size >= t) {//如果包含k的子树中有至少t个关键字,则递归删除k Delete_Inside(now->childs[j], k); } else {//否则,为了避免递归降至只包含t-1个关键字的结点,执行以下步骤进行补充 //先对极端情况进行处理 if (j == 1) {//k位于最左边子树中 if (now->childs[j + 1]->key_size >= t) {//右兄弟至少拥有t个关键字 PBTREE_NODE y = now->childs[j]; PBTREE_NODE z = now->childs[j + 1]; y->keys[t] = now->keys[j];//将当前结点中的关键字下降到y中 now->keys[j] = z->keys[1];//将z中的关键字提升到当前结点 y->key_size++; for (int p = 1; p < z->key_size; p++) {//收缩z的关键字 z->keys[p] = z->keys[p + 1]; } z->key_size--; if (!y->leaf) {//如果不是叶子结点,还要拷贝孩子结点 y->childs[y->child_size + 1] = z->childs[1]; y->child_size++; for (int p = 1; p < z->child_size; p++) { z->childs[p] = z->childs[p + 1]; } z->child_size--; } Delete_Inside(y, k); } else {//所有子树都只包含t-1个关键字 PBTREE_NODE y = now->childs[j]; PBTREE_NODE z = now->childs[j + 1]; y->keys[t] = now->keys[j]; for (int p = 1; p <= t - 1; p++) { y->keys[p + t] = z->keys[p]; } y->key_size = 2 * t - 1; if (!y->leaf) { for (int p = 1; p <= t; p++) { y->childs[p + t] = z->childs[p]; } y->child_size = 2 * t; } for (int p = j; p < now->key_size; p++) { now->keys[p] = now->keys[p + 1]; } for (int p = j + 1; p < now->child_size; p++) { now->childs[p] = now->childs[p + 1]; } now->key_size--; now->child_size--; delete[] z->childs; delete[] z->keys; delete z; Delete_Inside(y, k); if (now == root&&now->key_size == 0) { root = y; delete[] now->childs; delete[] now->keys; delete now; } } } else if (j > now->key_size) {//k位于最右边子树中 if (now->childs[j - 1]->key_size >= t) {//左兄弟有至少t个结点 PBTREE_NODE z = now->childs[j - 1]; PBTREE_NODE y = now->childs[j]; //在y中为关键字腾出位置 for (int p = y->key_size + 1; p > 1; p--) { y->keys[p] = y->keys[p - 1]; } y->keys[1] = now->keys[j - 1]; now->keys[j - 1] = z->keys[z->key_size]; y->key_size++; z->key_size--; if (!y->leaf) {//在y中为孩子腾出位置 for (int p = y->child_size + 1; p > 1; p--) { y->childs[p] = y->childs[p - 1]; } y->childs[1] = z->childs[z->child_size]; y->child_size++; z->child_size--; } Delete_Inside(y, k); } else {//所有子树只包含t-1个关键字 //将z合并到y中 j--; PBTREE_NODE y = now->childs[j]; PBTREE_NODE z = now->childs[j + 1]; y->keys[t] = now->keys[j]; for (int p = 1; p <= t - 1; p++) { y->keys[p + t] = z->keys[p]; } y->key_size = 2 * t - 1; if (!y->leaf) { for (int p = 1; p <= t; p++) { y->childs[p + t] = z->childs[p]; } y->child_size = 2 * t; } for (int p = j; p < now->key_size; p++) { now->keys[p] = now->keys[p + 1]; } for (int p = j + 1; p < now->child_size; p++) { now->childs[p] = now->childs[p + 1]; } now->key_size--; now->child_size--; delete[] z->childs; delete[] z->keys; delete z; Delete_Inside(y, k); if (now == root&&now->key_size == 0) { root = y; delete[] now->childs; delete[] now->keys; delete now; } } } else {//包含k的子树在区间中间 PBTREE_NODE y = now->childs[j]; PBTREE_NODE left = now->childs[j - 1]; PBTREE_NODE right = now->childs[j + 1]; if (right->key_size >= t) { //右兄弟有至少t个关键字 y->keys[t] = now->keys[j]; now->keys[j] = right->keys[1]; y->key_size++; for (int p = 1; p < right->key_size; p++) { right->keys[p] = right->keys[p + 1]; } right->key_size--; if (!y->leaf) { y->childs[t + 1] = right->childs[1]; for (int p = 1; p < right->child_size; p++) { right->childs[p] = right->childs[p + 1]; } right->child_size--; y->child_size++; } Delete_Inside(y, k); } else if (left->key_size >= t) { //左兄弟中至少含有t个关键字 //腾出位置准备存放关键字 for (int p = t; p > 1; p--) { y->keys[p] = y->keys[p - 1]; } y->keys[1] = now->keys[j - 1]; y->key_size++; now->keys[j - 1] = left->keys[left->key_size]; left->key_size--; if (!y->leaf) { for (int p = t + 1; p > 1; p--) { y->childs[p] = y->childs[p - 1]; } y->childs[1] = left->childs[left->child_size]; left->child_size--; } Delete_Inside(y, k); } else { //左右兄弟都只包含t-1个关键字 y->keys[t] = now->keys[j]; for (int p = j; p < now->key_size; p++) { now->keys[p] = now->keys[p + 1]; } now->key_size--; for (int p = 1; p <= t - 1; p++) { y->keys[p + t] = right->keys[p]; } y->key_size = 2 * t - 1; if (!y->leaf) { for (int p = 1; p <= t; p++) { y->childs[p + t] = right->childs[p]; } y->child_size = 2 * t; } delete[] right->childs; delete[] right->keys; delete right; Delete_Inside(y, k); if (now == root&&now->key_size == 0) { root = y; delete[] now->childs; delete[] now->keys; delete now; } } } } } }
(这段代码有很多冗余的部分,也是最难最复杂的部分,由于我头昏眼花,在递归中脑子打结,导致代码长这样了。如果大家有更好的写法可以发邮件给我,希望能得到大家的指点。)
Copyright ©By 寒江雪
QQ:211392413
Email:211392413@qq.com
Date:2016-11-21
1 0
- 【寒江雪】B树的一般知识与简单实现
- 一般简单服务器选型知识
- 住宅的一般知识
- 入市的一般知识
- 显卡的一般知识
- 住宅的一般知识
- 树 - B树的简单实现
- B+树插入C++的简单实现
- 哲学的一般知识---生活智慧与时代精神
- B-树的学习笔记与C实现及 简单的RPC编程实践——HelloWorld的实现
- B-树与B+树的实现及应用
- B树的原理与实现(C++)
- B树的实现与源代码一
- 数据结构学习—“一般树”的基本概念和知识
- 简单逻辑实现一个计算器,没有用到树的知识
- 用二叉树、栈的知识实现一个简单计算器
- 平衡二叉树与红黑树的简单知识
- 索引的种类与B*树索引 简单归纳总结
- excel列下拉自增1的实现方法
- 解决XP下iis5.1访问 IIS 元数据库失败
- markdown编辑器——颜色、大小、字体
- ElasticSearch教程--第十一章:修改您的数据--更新文档
- 第八章笔记
- 【寒江雪】B树的一般知识与简单实现
- 多项式相加
- ServletContext对象(4)、web项目中路径使用、ServletConfig,ServletContext方法总结
- 自学iOS开发系列----OC(数组)
- mysql主从复制
- 使用Velocity导出Word文档
- BZOJ 4318: OSU!【期望
- Android网络协议相关
- js 中的 typeof 和 instanceof 的区别