文章目录

  • 搜索结构
  • B树
    • B树的插入
    • B树的遍历
    • B树的性能
  • B+树
    • B+树的插入
    • B+树的遍历
  • B*树
    • B*树的插入
  • 总结

搜索结构

如果我们有大量的数据需要永久存储,就需要存储到硬盘之中,但是硬盘的访问速度远远小于内存,并且由于数据量过大,无法一次性加载到内存中。此时,就可以考虑将数据存储在硬盘中,而数据的地址则加载到内存中,通过某种搜索结构进行存储,使用时只需要通过该结构查找到地址,在通过地址去找到对应的数据即可。

之前我博客中介绍了几种搜索结构:二叉搜索树、AVL树、红黑树、哈希、位图、布隆过滤器。
考虑到查找性能以及内存消耗,其中适合这种场景的只有平衡二叉搜索树(AVL、红黑树)

但即使平衡二叉搜索树的搜索性能能达到logN,由于数据量过于庞大,例如存储了10亿个数,则可能最多需要查找30次。这个数字看起来不是很多,因为之前我们比较的是内存的速度,即使是10亿个数也能一瞬间查找完。但是对于硬盘来说,由于硬盘的速率低,每一次IO都意味这大量的损耗,所以这种方法也不太合适。

如果想要提高查找的效率,那么唯一的方法就是压缩树的高度,而压缩的方法,就是将二叉树变为M叉树,也就是使用到M路平衡搜索树,B树。


B树

B树即一棵平衡的M路平衡搜索树(M > 2),可以是空树或者满足以下性质

  • 根节点至少有两个孩子
  • 每个非根节点至少有M/2(上取整)个孩子,至多有M个孩子
  • 每个非根节点至少有M/2-1(上取整)个关键字,至多有M-1个关键字,并且以升序排列
  • key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
  • 所有的叶子节点都在同一层

节点设计

template<class K, int M = 3>
struct BTreeNode
{K _keys[M]; // 存放元素BTreeNode<K, M>* _pSub[M+1]; // 存放孩子节点,注意:孩子比数据多一个BTreeNode<K, M>* _pParent; // 在分裂节点后可能需要继续向上插入,为实现简单增加parent域size_t _size; // 节点中有效元素的个数BTreeNode(): _pParent(NULL), _size(0){for(size_t i = 0; i <= M; ++i)_pSub[i] = NULL;}
};

B树的插入

下面拿一个M=3的三叉B树来举例子。(ps:三叉树即每个节点至多3个孩子,2个key,key的数量永远比孩子少一个)

假设使用以下数据构建B树{63, 131, 85, 39, 148, 31, 111}(B树从叶子节点的位置进行插入

首先依次插入前三个节点,当插入到第三个时,由于三叉树的key只能有2个,所以此时会采取分裂的方法来维持树的平衡

B树分裂的规则是:创建一个兄弟节点,拷贝右半区间的数据到兄弟节点,左半区间保留,中位数放到父亲节点(如果没有则创建新的根节点)。
B树与AVL的旋转,红黑树的旋转+变色不一样,它使用了分裂的方法来维持树的平衡这样的好处是既能做到平衡,也保证了非根节点至少有一半的空间利用率

所以此时按照上面的规则进行分裂

接着插入39, 148。

此时插入31,开始分裂

接着插入111,此时再次发生分裂

此时可以看到,叶子节点已经分裂成了四个,并且根节点的key也达到了三个,所以此时规则不成立,按照分裂的规则继续分裂

此时,树重新平衡。所以从上面可以看出来,本质上B树的设计还是参考了二叉搜索树。


B树的遍历

B树的有序遍历还是通过中序遍历来完成,不过需要通过队列或者递归遍历完这个节点的所有的key

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]);
}

B树的性能

作为一个M路平衡搜索树,B树的搜索性能达到了 logM+1Nlog_{M+1}{N}logM+1​N~logM/2Nlog_{M/2}{N}logM/2​N之间,查找到对应的节点后,通过节点存储的地址来进行二分查找,就可以确定数据的位置。比起二叉平衡搜索树,速度快了一大截,并且大大的减少了硬盘IO的次数,所以在文件系统以及数据库索引等方面使用的都是这种数据结构。


B+树

B+树是B树的变形,主要性质如下

  • 其定义基本与B树相同
  • 非叶子节点的孩子与key个数相同(简化规则
  • 非叶子节点由叶子节点的最小值构成(充当索引,所以不可能在非叶子节点命中
  • 所有数据都出现在叶子节点,并且所有叶子节点都链接起来,同时是有序的。(方便遍历

如下图


B+树的插入

这里为了方便,使用三阶B+树举例子,这里还是使用同样的数据。{63, 131, 85, 39, 148, 31, 111}
首先插入63,并把63作为父节点的索引

接着插入131, 85
当插入39时,发生分裂,分裂规则与之前略有区别。

B+树的分裂规则发生了变化,因为此时父节点存储的是索引,所以此时只会将左半部分数据保留,右半部分数据放入新建的兄弟节点,并且会向上更新父节点的索引。

接着插入148, 31

当插入111时,发生分裂


B+树的遍历

从上面可以看出来,B+树的主要特点其实就是更方便进行遍历,因为其将所有数据存储在叶子节点,并且将所有叶子节点连接起来,就可以像遍历链表一样遍历B+树。而所有非叶子节点就相当于一个索引,这样的结构使得B+树的查找相当于对关键字全集做一次二分查找。

所以通常文件的索引系统都会采用B+树的结构。


B*树

B*树则又是对B+树的变形,其性质如下

  • 其定义基本与B+树相同
  • 将非叶子节点也连接起来
  • 分裂方式再次修改,保证每个节点中key的数量[2/3 * M, M](提高空间利用率,从1/2提升到了2/3

B*树的插入

B*树再次修改了插入规则,规则修改为如果当前节点数据已满而兄弟节点未满,则将数据放入兄弟节点,而当两个节点都满了之后再进行分裂,在原节点与兄弟节点之间创建新节点,从两个节点分别取出1/3的数据放入新创建的结点。

还是原来那些数据{63, 131, 85, 39, 148, 31, 111}

此时插入111,发生分裂,从两边各取走1/3的数据

从上面可以看出,B*树的最大改进就是将B+树的空间利用率从1/2提升到了2/3,并且对非叶子节点也进行了连接,查找更加便利。


总结


高级数据结构与算法 | B树、B+树、B*树相关推荐

  1. 高级数据结构与算法 | LFU缓存机制(Least Frequently Used)

    文章目录 LFUCache 结构设计 LFUCache的实现 在之前我写过一篇LRU的博客,如果不了解的建议先看看这篇 高级数据结构与算法 | LRU缓存机制(Least Recently Used) ...

  2. 高级数据结构与算法 | AVL树 (高度平衡树)

    文章目录 AVL树 实现思路 数据结构 查找 平衡因子 旋转 右旋 左旋 右左双旋 左右双旋 插入 删除 AVL树的验证 中序遍历 平衡判断 AVL树的性能 完整代码实现 AVL树 AVL树是最先发明 ...

  3. 数据结构与算法学习笔记(五)树

    本文针对树结构中,常见的二叉树和多叉树类型进行介绍和代码分析(主要针对二叉树) 目录 一.树 1.1 介绍: 1.2 常用的概念: 1.3 树的种类: 1.4 常见的存储结构: 二.二叉树 2.1 二 ...

  4. 【数据结构与算法基础】哈夫曼树与哈夫曼编码(C++)

    前言 数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷. 也因如此,它作为博主大二上学期最重 ...

  5. 数据结构与算法(十一)Trie字典树

    本文主要包括以下内容: Trie字典树的基本概念 Trie字典树的基本操作 插入 查找 前缀查询 删除 基于链表的Trie字典树 基于Trie的Set性能对比 LeetCode相关线段树的问题 Lee ...

  6. 【数据结构与算法】【14】以树状形式打印二叉树

    技术难点 以树状形式打印二叉树的关键难点在于,如何计算和控制每个节点的打印位置 解决思路 将二叉树的所有节点从左往右全部打印出来,正好和二叉树中序遍历的结果是一样的 利用这个特点,我们就可以通过中序遍 ...

  7. 数据结构与算法--Tree(二叉树、B±树、红黑树)

    在MySQL中,索引的的实现方式中使用的最多的就是B+Tree,那么为什么要选择B+Tree呢?我们就需要从最基本的二叉查找树说起 什么是二叉树? 二叉树 = (空树) | (节点+左右子二叉树) 解 ...

  8. 【数据结构和算法笔记】哈夫曼树的概念,构造和应用(利用哈夫曼编码压缩文本)

    目录 哈夫曼树定义: 构造哈夫曼树: 哈夫曼编码 前缀编码: 应用(压缩文本) 哈夫曼树定义: 构造哈夫曼树: 哈夫曼编码 前缀编码:  哈夫曼编码是前缀编码 哈夫曼树的性质 哈夫曼树的任意非叶结点的 ...

  9. C++(数据结构与算法):52---平衡搜索树之分裂树/伸展树

    一.分裂树简介 当使用AVL树或红黑树来实现字典时,最坏情况下,每一个字典操作的时间复杂性是字典大小的对数.在已知的数据结构中,没有一个会提供更好的性能.然而,在字典的很多实际应用中,令我们更感兴趣的 ...

最新文章

  1. STM32中I2C总线上数据的读、写。
  2. 从Github一开源项目ADSEC【学习域渗透攻防基础】
  3. JS 小数的常用处理方法
  4. JVM内存管理(一)--GC简介
  5. Petapoco使用SQLite的异常问题
  6. 【牛客 - 318E】爱摸鱼的Dillonh(数学,暴力,细节)
  7. LeetCode 385. 迷你语法分析器(栈)
  8. NoSQL和传统数据库的区别
  9. python从入门到精通需要多久-Python 从入门到精通:一个月就够了!
  10. android:scaleType=centerCrop
  11. 2048小游戏——网页版(基础篇)
  12. [转] React之Immutable学习记录
  13. 2017中国之旅系列之十一:山西绵山之旅(上)
  14. c语言求绝对值作业,C语言求绝对值
  15. 驾考科目三考试经验谈
  16. @RunWith注解作用
  17. c语言stl用法,C STL快速入门!学习使我快乐
  18. root后没反应怎么办,没有root怎么办?
  19. 服务器与pc机的区别
  20. mysql _外键、实体关系与ER图

热门文章

  1. 负载均衡器 Ribbion
  2. 外观模式源码解析(springjdbc+myabtis+tomcat)
  3. 数据库表设计、 数据库分层、myslq水平拆分、oracle表分区
  4. 什么?ES6 中还有 Tail Calls!
  5. Sass函数-数字函数-floor()函数
  6. Rails测试《十一》添加邮件发送程序及测试邮件发送程序
  7. Spring+Quartz实现定时任务
  8. 外媒:为何说中国对美国科技行业的影响与日俱增
  9. C++ Tricks
  10. 『教程』Batch Normalization 层介绍