转自:http://blog.chinaunix.net/uid-20196318-id-3030529.html

B树的定义

假设B树的度为t(t>=2),则B树满足如下要求:(参考算法导论)

(1)  每个非根节点至少包含t-1个关键字,t个指向子节点的指针;至多包含2t-1个关键字,2t个指向子女的指针(叶子节点的子女为空)。

(2)  节点的所有key按非降序存放,假设节点的关键字分别为K[1], K[2] … K[n], 指向子女的指针分别为P[1], P[2]…P[n+1],其中n为节点关键字的个数。则有:

P[1] <= K[1] <= P[2] <= K[2] …..<= K[n] <= P[n+1]   // 这里P[n]也指其指向的关键字

(3)  若根节点非空,则根节点至少包含两个子女;

(4)  所有的叶子节点都在同一层。

 

B树的搜索,searchroot, target

从root出发,对每个节点,找到大于或等于target关键字中最小的K[i],如果K[i]与target相等,则查找成功;否则在P[i]中递归搜索target,直到到达叶子节点,如仍未找到则说明关键字不在B树中,查找失败。

B树的插入,insert(root, target)

B树的插入需要沿着搜索的路径从root一直到叶节点,根据B树的规则,每个节点的关键字个数在[t-1, 2t-1]之间,故当target要加入到某个叶子时,如果该叶子节点已经有2t-1个关键字,则再加入target就违反了B树的定义,这时就需要对该叶子节点进行分裂,将叶子以中间节点为界,分成两个包含t-1个关键字的子节点,同时把中间节点提升到该叶子的父节点中,如果这样使得父节点的关键字个数超过2t-1,则要继续向上分裂,直到根节点,根节点的分裂会使得树加高一层。

上面的过程需要回溯,那么能否从根下降到叶节点后不回溯就能完成节点的插入呢?答案是肯定的,核心思想就是未雨绸缪,在下降的过程中,一旦遇到已满的节点(关键字个数为2t-1),就就对该节点进行分裂,这样就保证在叶子节点需要分裂时,其父节点一定是非满的,从而不需要再向上回溯。

B树的删除,delete(root, target)

在删除B树节点时,为了避免回溯,当遇到需要合并的节点时就立即执行合并,B树的删除算法如下:从root向叶子节点按照search规律遍历:

(1)  如果target在叶节点x中,则直接从x中删除target,情况(2)和(3)会保证当再叶子节点找到target时,肯定能借节点或合并成功而不会引起父节点的关键字个数少于t-1。

(2)  如果target在分支节点x中:

(a)  如果x的左分支节点y至少包含t个关键字,则找出y的最右的关键字prev,并替换target,并在y中递归删除prev。

(b)  如果x的右分支节点z至少包含t个关键字,则找出z的最左的关键字next,并替换target,并在z中递归删除next。

(c)  否则,如果y和z都只有t-1个关键字,则将targe与z合并到y中,使得y有2t-1个关键字,再从y中递归删除target。

(3)  如果关键字不在分支节点x中,则必然在x的某个分支节点p[i]中,如果p[i]节点只有t-1个关键字。

(a)  如果p[i-1]拥有至少t个关键字,则将x的某个关键字降至p[i]中,将p[i-1]的最大节点上升至x中。

(b)  如果p[i+1]拥有至少t个关键字,则将x个某个关键字降至p[i]中,将p[i+1]的最小关键字上升至x个。

(c)  如果p[i-1]与p[i+1]都拥有t-1个关键字,则将p[i]与其中一个兄弟合并,将x的一个关键字降至合并的节点中,成为中间关键字。

B树的实现

数据结构

  1. /**
  2. * @brief the degree of btree
  3. * key per node: [M-1, 2M-1]
  4. * child per node: [M, 2M]
  5. */
  6. #define M 2   // M为B树的度
  7. typedef struct btree_node {
  8. int k[2*M-1];
  9. struct btree_node *p[2*M];
  10. int num;
  11. bool is_leaf;
  12. } btree_node;

 

创建B树

  1. btree_node *btree_node_new()
  2. {
  3. btree_node *node = (btree_node *)malloc(sizeof(btree_node));
  4. if(NULL == node) {
  5. return NULL;
  6. }
  7. for(int i = 0; i < 2 * M -1; i++) {  // 初始化key
  8. node->k[i] = 0;
  9. }
  10. for(int i = 0; i < 2 * M; i++) {     // 初始化pointer
  11. node->p[i] = NULL;
  12. }
  13. node->num = 0;
  14. node->is_leaf = true;  // 默认为叶子
  15. }
  16. btree_node *btree_create()
  17. {
  18. btree_node *node = btree_node_new();
  19. if(NULL == node) {
  20. return NULL;
  21. }
  22. return node;
  23. }

 

插入节点

  1. // 当child满时,将其进行分裂,child = parent->p[pos]
  2. int btree_split_child(btree_node *parent, int pos, btree_node *child)
  3. {
  4. // 创建新的节点
  5. btree_node *new_child = btree_node_new();
  6. if(NULL == new_child) {
  7. return -1;
  8. }
      
     // 新节点的is_leaf与child相同,key的个数为M-1
  1. new_child->is_leaf = child->is_leaf;
  2. new_child->num = M - 1;
  3. // 将child后半部分的key拷贝给新节点
  4. for(int i = 0; i < M - 1; i++) {
  5. new_child->k[i] = child->k[i+M];
  6. }
      
     // 如果child不是叶子,还需要把指针拷过去,指针比节点多1
  1. if(false == new_child->is_leaf) {
  2. for(int i = 0; i < M; i++) {
  3. new_child->p[i] = child->p[i+M];
  4. }
  5. }
  6. child->num = M - 1;
      
     // child的中间节点需要插入parent的pos处,更新parent的key和pointer
  1. for(int i = parent->num; i > pos; i--) {
  2. parent->p[i+1] = parent->p[i];
  3. }
  4. parent->p[pos+1] = new_child;
  5. for(int i = parent->num - 1; i >= pos; i--) {
  6. parent->k[i+1] = parent->k[i];
  7. }
  8. parent->k[pos] = child->k[M-1];
  9. parent->num += 1;
  10. }
 
 // 执行该操作时,node->num < 2M-1 
  1. void btree_insert_nonfull(btree_node *node, int target)
  2. {
  3. if(1 == node->is_leaf) { // 如果在叶子中找到,直接删除
  4. int pos = node->num;
  5. while(pos >= 1 && target < node->k[pos-1]) {
  6. node->k[pos] = node->k[pos-1];
  7. pos--;
  8. }
  9. node->k[pos] = target;
  10. node->num += 1;
  11. } else {   // 沿着查找路径下降
  12. int pos = node->num;
  13. while(pos > 0 && target < node->k[pos-1]) {
  14. pos--;
  15. }
  16. if(2 * M -1 == node->p[pos]->num) {
  17. btree_split_child(node, pos, node->p[pos]); // 如果路径上有满节点则分裂
  18. if(target > node->k[pos]) {
  19. pos++;
  20. }
  21. }
  22. btree_insert_nonfull(node->p[pos], target);
  23. }
  24. }
 //插入入口
  1. btree_node* btree_insert(btree_node *root, int target)
  2. {
  3. if(NULL == root) {
  4. return NULL;
  5. }
  6. // 对根节点的特殊处理,如果根是满的,唯一使得树增高的情形
  7. // 先申请一个新的
  8. if(2 * M - 1 == root->num) {
  9. btree_node *node = btree_node_new();
  10. if(NULL == node) {
  11. return root;
  12. }
  13. node->is_leaf = 0;
  14. node->p[0] = root;
  15. btree_split_child(node, 0, root);
  16. btree_insert_nonfull(node, target);
  17. return node;
  18. } else {
  19. btree_insert_nonfull(root, target);
  20. return root;
  21. }
  22. }

 

删除节点

  1. // 将y,root->k[pos], z合并到y节点,并释放z节点,y,z各有M-1个节点
  2. void btree_merge_child(btree_node *root, int pos, btree_node *y, btree_node *z)
  3. {
  4. // 将z中节点拷贝到y的后半部分
  5. y->num = 2 * M - 1;
  6. for(int i = M; i < 2 * M - 1; i++) {
  7. y->k[i] = z->k[i-M];
  8. }
  9. y->k[M-1] = root->k[pos]; // k[pos]下降为y的中间节点
  10. // 如果z非叶子,需要拷贝pointer
  11. if(false == z->is_leaf) {
  12. for(int i = M; i < 2 * M; i++) {
  13. y->p[i] = z->p[i-M];
  14. }
  15. }
     
      // k[pos]下降到y中,更新key和pointer
  1. for(int j = pos + 1; j < root->num; j++) {
  2. root->k[j-1] = root->k[j];
  3. root->p[j] = root->p[j+1];
  4. }
  5. root->num -= 1;
  6. free(z);
  7. }
  
 // 删除入口
  1. btree_node *btree_delete(btree_node *root, int target)
  2. {
  3. // 特殊处理,当根只有两个子女,切两个子女的关键字个数都为M-1时,合并根与两个子女
  4. // 这是唯一能降低树高的情形
  5. if(1 == root->num) {
  6. btree_node *y = root->p[0];
  7. btree_node *z = root->p[1];
  8. if(NULL != y && NULL != z &&
  9. M - 1 == y->num && M - 1 == z->num) {
  10. btree_merge_child(root, 0, y, z);
  11. free(root);
  12. btree_delete_nonone(y, target);
  13. return y;
  14. } else {
  15. btree_delete_nonone(root, target);
  16. return root;
  17. }
  18. } else {
  19. btree_delete_nonone(root, target);
  20. return root;
  21. }
  22. }
  
 // root至少有个t个关键字,保证不会回溯
  1. void btree_delete_nonone(btree_node *root, int target)
  2. {
  3. if(true == root->is_leaf) {  // 如果在叶子节点,直接删除
  4. int i = 0;
  5. while(i < root->num && target > root->k[i]) i++;
  6. if(target == root->k[i]) {
  7. for(int j = i + 1; j < 2 * M - 1; j++) {
  8. root->k[j-1] = root->k[j];
  9. }
  10. root->num -= 1;
  11. } else {
  12. printf("target not found\n");
  13. }
  14. } else {  // 在分支中
  15. int i = 0;
  16. btree_node *y = NULL, *z = NULL;
  17. while(i < root->num && target > root->k[i]) i++;
  18. if(i < root->num && target == root->k[i]) { // 如果在分支节点找到target
  19. y = root->p[i];
  20. z = root->p[i+1];
  21. if(y->num > M - 1) {
  22. // 如果左分支关键字多于M-1,则找到左分支的最右节点prev,替换target
  23. // 并在左分支中递归删除prev,情况2(a)
  24. int pre = btree_search_predecessor(y);
  25. root->k[i] = pre;
  26. btree_delete_nonone(y, pre);
  27. } else if(z->num > M - 1) {
  28. // 如果右分支关键字多于M-1,则找到右分支的最左节点next,替换target
  29. // 并在右分支中递归删除next,情况2(b)
  30. int next = btree_search_successor(z);
  31. root->k[i] = next;
  32. btree_delete_nonone(z, next);
  33. } else {
  34. // 两个分支节点数都为M-1,则合并至y,并在y中递归删除target,情况2(c)
  35. btree_merge_child(root, i, y, z);
  36. btree_delete(y, target);
  37. }
  38. } else {   // 在分支没有找到,肯定在分支的子节点中
  39. y = root->p[i];
  40. if(i < root->num) {
  41. z = root->p[i+1];
  42. }
  43. btree_node *p = NULL;
  44. if(i > 0) {
  45. p = root->p[i-1];
  46. }
  47. if(y->num == M - 1) {
  48. if(i > 0 && p->num > M - 1) {
  49. // 左邻接节点关键字个数大于M-1
  50. btree_shift_to_right_child(root, i-1, p, y); //情况3(a)
  51. } else if(i < root->num && z->num > M - 1) {
  52. // 右邻接节点关键字个数大于M-1
  53. btree_shift_to_left_child(root, i, y, z); // 情况3(b)
  54. } else if(i > 0) {
  55. btree_merge_child(root, i-1, p, y);  // 情况3(c)
  56. y = p;
  57. } else {
  58. btree_merge_child(root, i, y, z); // 情况3(c)
  59. }
  60. btree_delete_nonone(y, target);
  61. } else {
  62. btree_delete_nonone(y, target);
  63. }
  64. }
  65. }
  66. }
 //寻找rightmost,以root为根的最大关键字
  1. int btree_search_predecessor(btree_node *root)
  2. {
  3. btree_node *y = root;
  4. while(false == y->is_leaf) {
  5. y = y->p[y->num];
  6. }
  7. return y->k[y->num-1];
  8. }
 // 寻找leftmost,以root为根的最小关键字
  1. int btree_search_successor(btree_node *root)
  2. {
  3. btree_node *z = root;
  4. while(false == z->is_leaf) {
  5. z = z->p[0];
  6. }
  7. return z->k[0];
  8. }
 // z向y借节点,将root->k[pos]下降至z,将y的最大关键字上升至root的pos处
  1. void btree_shift_to_right_child(btree_node *root, int pos,
  2. btree_node *y, btree_node *z)
  3. {
  4. z->num += 1;
  5. for(int i = z->num -1; i > 0; i--) {
  6. z->k[i] = z->k[i-1];
  7. }
  8. z->k[0]= root->k[pos];
  9. root->k[pos] = y->k[y->num-1];
  10. if(false == z->is_leaf) {
  11. for(int i = z->num; i > 0; i--) {
  12. z->p[i] = z->p[i-1];
  13. }
  14. z->p[0] = y->p[y->num];
  15. }
  16. y->num -= 1;
  17. }
 
 // y向借节点,将root->k[pos]下降至y,将z的最小关键字上升至root的pos处
  1. void btree_shift_to_left_child(btree_node *root, int pos,
  2. btree_node *y, btree_node *z)
  3. {
  4. y->num += 1;
  5. y->k[y->num-1] = root->k[pos];
  6. root->k[pos] = z->k[0];
  7. for(int j = 1; j < z->num; j++) {
  8. z->k[j-1] = z->k[j];
  9. }
  10. if(false == z->is_leaf) {
  11. y->p[y->num] = z->p[0];
  12. for(int j = 1; j <= z->num; j++) {
  13. z->p[j-1] = z->p[j];
  14. }
  15. }
  16. z->num -= 1;
  17. }

 

插入与删除过程(图片为层序遍历的结果)

插入序列[18, 31, 12, 10, 15, 48, 45, 47, 50, 52, 23, 30, 20]

 

删除序列[15, 18, 23, 30, 31, 52, 50, 48, 47, 45, 20, 12, 10]

B+

与B树不同的时,B+树的关键字都存储在叶子节点,分支节点均为索引,在实现上大致与B树类似,在几个细节稍有不同。

(1) 数据结构中增加prev,next指针,用于将叶子节点串成有序双向链表。

(2) 在节点分裂的时候,如果分裂的节点为叶子,则需要把中间节点保留在左(或右)边的分支上,并且需要更新prev和next。

(3) 在节点合的时候,如果合并的节点为叶子,不需要把跟节点下降为中间节点,并且需要更新prev和next。

(4) 在向邻接节点借节点时,借来的关键字并不是父节点的关键字,而是邻接点的关键字,并根据实际情况更新父节点的索引。

btree.rar  C语言实现,因用到了bool变量,编译时请使用g++ -o btree btree.c

b树的实现(c++)的更多相关文章

  1. B树——算法导论(25)

    B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...

  2. ASP.NET Aries 入门开发教程8:树型列表及自定义右键菜单

    前言: 前面几篇重点都在讲普通列表的相关操作. 本篇主要讲树型列表的操作. 框架在设计时,已经把树型列表和普通列表全面统一了操作,用法几乎是一致的. 下面介绍一些差距化的内容: 1:树型列表绑定: v ...

  3. 再讲IQueryable<T>,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  4. HDU1671——前缀树的一点感触

    题目http://acm.hdu.edu.cn/showproblem.php?pid=1671 题目本身不难,一棵前缀树OK,但是前两次提交都没有成功. 第一次Memory Limit Exceed ...

  5. 算法与数据结构(十一) 平衡二叉树(AVL树)

    今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...

  6. [C#] C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  7. bzoj3207--Hash+主席树

    题目大意: 给定一个n个数的序列和m个询问(n,m<=100000)和k,每个询问包含k+2个数字:l,r,b[1],b[2]...b[k],要求输出b[1]~b[k]在[l,r]中是否出现. ...

  8. bzoj1901--树状数组套主席树

    树状数组套主席树模板题... 题目大意: 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]--a[ ...

  9. bzoj3932--可持久化线段树

    题目大意: 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第 ...

  10. jquery-treegrid树状表格的使用(.Net平台)

    上一篇介绍了DataTable,这一篇在DT的基础之上再使用jquery的一款插件:treegrid,官网地址:http://maxazan.github.io/jquery-treegrid/ 一. ...

随机推荐

  1. VS中没有为此解决方案配置选中要生成的项目

    菜单->生成->配置管理器->给要生成的项目打钩

  2. 【python】-- Django ORM(基础)

    Django ORM(基础) Django 框架十分强大,自带数据库操作功能.Django 跟 SQLAchemy 一样,也是通过ORM(Object Relational Mapping,关系对象映 ...

  3. Mysql常用优化方案

    摘自:http://www.jb51.net/article/18934.htm 1.选取最适用的字段属性 MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也 ...

  4. Js算两经纬度间球面距离

    function GetDistance( lat1, lng1, lat2, lng2){ var radLat1 = lat1 * Math.PI / 180.0 var radLat2 = la ...

  5. F110 BADI增强

    F110*JOB*&------------------------------------------------------------- F110 BADI FI_BSTM_MC_EXI ...

  6. Django基础(三)_分页器、COOKIE与SESSION、FORM表单

    分页器(paginator) 分页器的使用 >>> from django.core.paginator import Paginator >>> objects ...

  7. PAT 天梯赛 L1-034. 点赞 【MAP】

    题目链接 https://www.patest.cn/contests/gplt/L1-034 AC代码 #include <cstdio> #include <cstring> ...

  8. Android相机实时自动对焦的完美实现

    https://zhidao.baidu.com/question/873328177698804372.html Android相机实时自动对焦的完美实现 http://blog.csdn.net/ ...

  9. PHP操作MongoDB数据库的示例

    http://www.jquerycn.cn/a_8137 本节内容:PHP操作MongoDB数据库的简单示例. Mongodb的常用操作参看手册,php官方的http://us2.php.net/m ...

  10. 20165101刘天野 2017-2018-2 《Java程序设计》第3周学习总结

    20165101刘天野 2017-2018-2 <Java程序设计>第3周学习总结 编程语言的几个发展阶段 类 构造方法与对象的创建 类与程序的基本结构 参数传值 对象的组合 实例成员与类 ...