《深入浅出话数据结构》系列之什么是B树、B+树?为什么二叉查找树不行?
本文将为大家介绍B树和B+树,首先介绍了B树的应用场景,为什么需要B树;然后介绍了B树的查询和插入过程;最后谈了B+树针对B树的改进。
在谈B树之前,先说一下B树所针对的应用场景。那么B树是用来做什么的呢? B树是一种为辅助存储设计的一种数据结构,普遍运用在数据库和文件系统中。举个例子来说,数据库大家肯定都不陌生,比如现在有一张表,其中有100万条记录,现在要查找查找其中的某条数据,如何快速地从100万条记录中找到需要的那条记录呢?大家的第一反应肯定是二叉查找树,下面先谈谈为什么二叉树不行。
为什么二叉查找树不行
还是刚刚那个例子,现在一张表中有100万条记录,我们以表的主键来构建一个二叉查找树。先不考虑二叉树不平衡退化成链表的情况,假设是理想情况,这个二叉树是完全平衡的。那么构建完成之后应该是下面这个样子的(示意图)
树的高度应该为
现在开始查找,加入我们要查找值为100的节点,怎么找呢?首先应该获取树的根节点。一般来说,表的索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。也就是说我们构建的二叉查找树太大了,内存放不下,一般要存在磁盘上,用的时候再从磁盘读入到内存。现在假设我们知道了根节点所在的磁盘位置,那么应该首先将根节点读入内存中,这里进行了一次IO操作,然后判断要找的值比根节点大还是小,100比4大,所以去右子树查找。那么如何找到6所在的节点呢?根节点中存储着6所在节点在磁盘上存放的位置。同样,需要将其先读入内存中,然后再判断继续向下查找,这里又是一次IO操作。下面的过程类似,不再展开。总结一下,查找到我们所需的那条记录大概需要进行20次IO操作,也就是树的高度,因为每向下查找一层,就要进行一次IO操作。
为什么要强调IO操作,而不是在内存中比较的次数呢? 因为磁盘的速度相比内存而言是非常的慢。比如常见的7200RPM的硬盘,摇臂转一圈需要60/7200≈8.33ms,换句话说,让磁盘完整的旋转一圈找到所需要的数据需要8.33ms,这比内存常见的100ns慢100000倍左右,这还不包括移动摇臂的时间。所以在这里制约查找速度的不是比较次数,而是IO操作的次数。换句话说,如果能够减少一次IO操作,那么多在内存中比较100次也无所谓,因为二者的速度相差100000倍。而我们可以认为IO操作的次数就大约等于树的高度,树的高度是如何计算的呢?看一下这个公式
我们想让这个值尽可能的小,就只能让真数小,或者底数大。真数是数据记录的条数,不是我们能决定的。那么底数呢?这个2来自哪呢?当然是来自二叉树中的那个二。那么这个底数能不能变大呢?当然能!!!那就是不要用二叉树,而要用多叉树,这就是我们要说的B树了。
B树是什么
B树也称B-树,它是一颗多路平衡查找树。B树和后面讲到的B+树都是从最简单的二叉树变换而来的。描述一颗B树时需要指定它的阶数,阶数表示了一个节点最多有多少个孩子节点,一般用字母m表示阶数。下面我们来看看B树的定义
(1)树中每个节点至多有m 棵子树(m指的是树的阶);
解释:有的定义说的是每个节点最多有m-1个关键字,是一样的,对于每个节点来说子树的数目等于关键字数目加1,下面会举例说明。
(2)若根结点不是叶子结点,则至少有两棵子树;
解释:当根节点为叶子节点时,可以没有子树;不为叶子节点时,至少有一个关键字,也就是至少有两棵子树。
(3)除根结点之外的所有非叶子结点至少有⌈m/2⌉个子节点;
解释:⌈m/2⌉表示向上取整,比如m=5时,⌈m/2⌉=3,表示至少有3个子节点,当然最多有5个。
(4)所有的非叶子结点中包含以下数据:(n,A0,K1,A1,K2,…,Kn,An)
解释:n为节点中关键字的数目,Ki(i=1,2,…,n)为关键字,且Ki<Ki+1
Ai 为指向孩子节点的指针(i=0,1,…,n),且指针Ai-1 所指子树中所有结点的关键字均小于Ki (i=1,2,…,n),An 所指子树中所有结点的关键字均大于Kn。(这里也可以看到对于每个节点来说子节点数量比关键字数量多1)
(5)所有的叶子结点都出现在同一层次上,即所有叶节点具有相同的深度,等于树高度。叶子节点除了包含了关键字和关键字记录的指针外也有指向其子节点的指针只不过其指针地址都为null。
下面具体举个例子说明,相信看了这个例子会对上面的定义有更加深刻的理解。
B树的查找
以上图为例,假设要查找15的节点,查找流程如下
(1)获取根节点的关键字进行比较,当前根节点关键字为50,50>15,所以找到指向左边的子节点;
(2)拿到关键字10和30,10<15<30 所以直接找到10和30中间的指针指向的子节点;
(3)拿到关键字15,就是要查找的目标值, 所以直接返回关键字和指针信息(如果树结构里面没有包含所要查找的节点则返回null)
至此我们便完成了B树的查找过程,比较简单,且与二叉查找树类似。
关于B树的插入操作,可以参考【为什么有红黑树?什么是红黑树?看完这篇你就明白了】这篇推文中关于2-3树的插入操作的详细介绍,其实2-3树就是一种特殊的B树。限于篇幅,本文不再赘述。
从B树到B+树
B+树是从B树衍生而来的,比B树更具有优越性。B+树相对于B树主要做了两点改进:
(1)非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中。
解释:B+树的非叶子节点不保存关键字记录的指针,只进行数据索引;B+树叶子节点保存了父节点的所有关键字记录的指针,所有数据地址必须要到叶子节点才能获取到。还是举刚才B树的例子,B树中根节点的关键字为50。假如我们就要查找主键为50的记录,那么在B树中只需进行一次IO操作,将根节点读入内存,便可以直接命中。而在B+树中则不同,B+树中任何查询必须要到叶子节点才能获取到,所以每次数据查询的次数都一样,这一点和B树有很大区别。
(2)树的所有叶节点构成一个有序链表,可以按照关键字排序的次序遍历全部记录。
解释:这样做有两点好处。一是进行范围查询更快,数据紧密性很高,缓存的命中率也会比B树高。二是B+树全节点遍历更快,B+树遍历整棵树只需要遍历所有的叶子节点即可,而不需要像B树一样需要对每一层进行遍历,这有利于数据库做全表扫描。
推荐阅读
为什么有红黑树?什么是红黑树?看完这篇你就明白了
都2020年了,听说你还不会归并排序?手把手教你手写归并排序算法
为什么会有多线程?什么是线程安全?如何保证线程安全?
觉得文章有用的话,点赞+关注呗,好让更多的人看到这篇文章,也激励博主写出更多的好文章。
更多关于算法、数据结构和计算机基础知识的内容,欢迎扫码关注我的原创公众号「超悦编程」。
《深入浅出话数据结构》系列之什么是B树、B+树?为什么二叉查找树不行?的更多相关文章
- 【分享】深入浅出WPF全系列教程及源码
本人10月份提出离职,可是交接非常慢,预计年底才会交接完,趁着交接之际,自学了一下WPF,由于这是微软未来的发展趋势,自WIN7以来包含前不久公布的WIN8,核心还是WPF,在此,将自己的学习成果做一 ...
- 【【分享】深入浅出WPF全系列教程及源码
】
因为原书作者的一再要求,在此声明,本书中的部分内容引用了原书名为<深入浅出WPF>的部分内容,假设博文不能满足你现有的学习须要,能够购买正版图书! 本人10月份提出离职,可是交接非常慢,预 ...
- 【分享】深入浅出WPF全系列教程及源代码
来源:http://blog.csdn.net/yishuaijun/article/details/21345893 本来想一篇一篇复制的.但是考虑到别人已经做过了,就没有必要了吧,就给大家一个目录 ...
- JAVA数据结构系列 栈
java数据结构系列之栈 手写栈 1.利用链表做出栈,因为栈的特殊,插入删除操作都是在栈顶进行,链表不用担心栈的长度,所以链表再合适不过了,非常好用,不过它在插入和删除元素的时候,速度比数组栈慢,因为 ...
- [js高手之路]深入浅出webpack教程系列6-插件使用之html-webpack-plugin配置(下)
上文我们对html-webpack-plugin的实例htmlWebpackPlugin进行了遍历分析,讲解了几个常用属性( inject, minify )以及自定义属性的添加,本文,我们继续深入他 ...
- [js高手之路]深入浅出webpack教程系列5-插件使用之html-webpack-plugin配置(中)
上文我们讲到了options的配置和获取数据的方式,本文,我们继续深入options的配置 一.html-webpack-plugin插件中的options除了自己定义了一些基本配置外,我们是可以任意 ...
- [js高手之路]深入浅出webpack教程系列4-插件使用之html-webpack-plugin配置(上)
还记得我们上文中的index.html文件吗? 那里面的script标签还是写死的index.bundle.js文件,那么怎么把他们变成动态的index.html文件,这个动态生成的index.htm ...
- [js高手之路]深入浅出webpack教程系列3-配置文件webpack.config.js详解(下)
本文继续接着上文,继续写下webpack.config.js的其他配置用法. 一.把两个文件打包成一个,entry怎么配置? 在上文中的webpack.dev.config.js中,用数组配置entr ...
- [js高手之路]深入浅出webpack教程系列7-( babel-loader,css-loader,style-loader)的用法
什么是loader呢,官方解释为文件的预处理器,通俗点说webpack在处理静态资源的时候,需要加载各种loader,比如,html文件,要用html-loader, css文件要用css-loade ...
随机推荐
- win10 uwp 依赖属性
本文告诉大家如何使用依赖属性,包括在 UWP 和 WPF 如何使用. 本文不会告诉大家依赖属性的好处,只是简单告诉大家如何使用 在 UWP 和 wpf ,如果需要创建自己的依赖属性,可以使用代码片,在 ...
- C# 判断两条直线距离
本文告诉大家获得两条一般式直线距离 一般式的意思就是 Ax+By+C=0" role="presentation">Ax+By+C=0Ax+By+C=0 如果有两个 ...
- java接口(interface)
引入:抽象类是从多个类中抽象出来的模板,若要将这种抽象进行得更彻底,就得用到一种特殊的“抽象类”→ 接口; 例子: 生活中听说过的USB接口其实并不是我们所看到的那些插槽,而是那些插槽所遵循的一种规范 ...
- java 集合之HashMap的三种遍历
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射. 这周我们只需记住三种遍历方法 1.通过keySet()获取键,再利用hashmap里面的.get(key)方法通过键获取 ...
- 5-1rquests模拟登陆知乎之httpcode
1,状态码: 400错误:请求无效 (Bad request);出现这个请求无效报错说明请求没有进入到后台服务里 2,requests库:python常用的库,有空仔细阅读一下官方文档
- window 系统下修改`CMD`的编码格式的方法,`CHCP` 的 使用
CHCP的使用 CHCP是一个计算机指令,能够显示或设置活动代码页编号. 一般上是在命令提示框中使用,用来查询和修改命令提示框的编码格式 具体使用方法 查看活动代码页编号 方式1: >>& ...
- 【踩坑记录】vue单个组件内<style lang="stylus" type="text/stylus" scoped>部分渲染失效
vue组件化应用,近期写的单个组件里有一个的渲染部分样式渲染不上去 因为同结构的其他组件均没有问题,所以排除是.vue文件结构的问题,应该是<style>内部的问题 <style l ...
- linux alloc_pages 接口
为完整起见, 我们介绍另一个内存分配的接口, 尽管我们不会准备使用它直到 15 章. 现 在, 能够说 struct page 是一个描述一个内存页的内部内核结构. 如同我们将见到的, 在内核中有许多 ...
- dotnet core 发布只带必要的依赖文件
在使用 dotnet core 发布独立项目的时候,会带上大量依赖的库,但是通过微软提供的工具可以去掉一些在代码没有用到的库. 本文介绍的工具是 Microsoft.Packaging.Tools.T ...
- 2019-1-25-win10-uwp-禁用-ScrollViewer-交互
title author date CreateTime categories win10 uwp 禁用 ScrollViewer 交互 lindexi 2019-01-25 21:45:37 +08 ...