java数据结构和算法08(B树的简单原理)

  这一篇首先会说说前面剩余的一点知识2-3树,而后简单说说B树,不写代码,只是简单看看原理吧!算法

  为何要说一下2-3树呢?了解2-3树以后能更快的了解B树;数据库

 

1.简单看看2-3树数组

  其实咱们学过了前面的2-3-4树以后,再看2-3树就太容易了,2-3树中任意一个节点最多只有三个子节点,并且节点中只有两个空位置能够存数据;除了分裂,其余的都和2-3-4树同样的,就很少说了,下面咱们就随意看看节点分裂吧!缓存

  首先要区分2-3-4树和2-3树分裂的的不一样,对于2-3-4树来讲是插入数据以前首先会把满的叶节点分裂,把三个数据分配完了以后再插入数据到节点中;而对于2-3树来讲,是在插入期间,什么是插入期间呢?看看下图:数据结构

  上图中的操做的目的就是向2-3树中插入85,插入的时候会判断该叶节点是否是满的,假如是满的 ,首先就80、90、85进行从小到大排列 为80、8五、90,而后80不动,中间的数据放进父节点中,最后将90放入新建立的节点当中,就ok了;这里假如85在进入父节点的时候发现父节点满了,那么父节点就会分裂,这里跟2-3-4树差很少,重复上述步骤,左边数据不动,将中间值放入父节点,右边数据放入新建节点;提及来很绕,请看下图:学习

  其实没什么新的东西,弄懂了2-3-4树,再看2-3树几分钟就差很少了,这里也就是随意看看,有兴趣的能够用代码实现一下,这里就是注意一下2-3树和2-3-4树分裂过程的不一样就能够了;spa

 

2.硬盘存储数据操作系统

  咱们前面说的全部数据结构都是存在于内存中的,当电脑一关机内存就会所有释放,全部的数据结构都会消失;可是有没有想过硬盘中是怎么存数据的啊?指针

  因而就有了B树,属于一种多叉树,在外部存储器存数据的时候起很大的做用,外部存储暂时就理解为硬盘便可!话说数据为何要存到硬盘中呢?最大的有点就是硬盘便宜,并且硬盘空间比内存大得多,能够存不少不少的数据,并且硬盘最大的优势就是能够持久化,就是电脑即便关机了,数据仍是存在硬盘中不会消失;blog

  可是存在硬盘中有个很大的问题,就是从硬盘中读取数据的时候太慢太慢了,而从内存中读取数据的速度大概比硬盘读取快几万倍,相差一个数量级;其实对于cpu的运算速度来讲从内存中读数据仍是太慢了,因而就有了缓存,后面有机会再说......

  虽然每一年硬盘技术都在提升,可是内存技术提升的更快,能够想象内存和硬盘的速度只会愈来愈大!

  2.1找到硬盘中数据的正确位置

  假设咱们要存一个城市的电话记录,大概50万条数据,每条数据512个字节,那么总共应该是50万x512=2亿5千6百万  字节,差很少就是256M,这个确定不能存在内存中,要想办法把这256M的数据存到硬盘中还要保证咱们从硬盘中查找,插入和删除指定记录的速度要足够快才行,否则用户体验太差了。。。

  咱们知道计算机想要读取硬盘中的数据是经过驱动(其实就是磁盘驱动器)去对硬盘进行操做,硬盘内部以下图所示,其实最终就是经过磁头对盘片进行操做,可是怎么操做呢?咱们能够把盘片看做打靶的那个靶子同样有不少个圈,磁头首先要找到目标数据所在的圈(也叫作磁道)须要几毫秒,而后盘片须要旋转一下磁头才能在当前磁道中找到正确的位置(平均下来是要旋转半圈)须要几毫秒,找到正确的位置后,最后就是实际的读写操做了,差很少也须要几毫秒;假设硬盘这里的全部操做共须要10毫秒(10-3),而假如从内存中访问正确的数据则只须要几微秒(10-9),能够看到速度相差了好多好多倍;

 

  那确定有人要问了,既然内存这么快那干吗不直接用内存条当硬盘来用呢?emmm....最主要的缘由就是内存条很贵啊,你能够去淘宝或者京东查查几个G的内存条多少RMB,至少好几百,咱们电脑存储量至少也要五六百个G吧,因而你买内存条就能够破产了;可是硬盘的话1T也就一两百块,价格才是最主要的。

  2.2.读取数据块

  磁头在硬盘的盘片中找到数据的正确位置了以后,难道要一条一条数据慢慢读么?固然不会用这么愚蠢的方法,咱们能够把盘片中的数据分红一块一块的,须要的时候直接读取一块数据到内存中的缓存区,一般块的大小和操做系统和磁盘驱动器的容量相关,并且必须是2的倍数,假设这里咱们把块的大小设置为8192字节(213),那么上面那两亿多个字节的数据就变成31250块;

  块分完以后,假设咱们要读取100字节的数据,那么磁盘驱动器首先直接读取一块数据,而后将这块数据前100字节留下其余的都扔了;假如是读取8292字节,那么就会读取两块数据,而后将第二块数据留下100字节就将第二块数据其余的都扔了;

  顺便一说,上面说了一条数据512字节,一块数据是8192字节,能够知道一块数据其实就保存有16条记录,因此咱们一次读取16条数据效率是最高的,不须要对数据进行丢弃操做;

  2.3.硬盘中数据有序

  咱们存到硬盘中的数据,能够是有序和无序的;

  假如硬盘中的数据是无序的,那么插入确定是很快的,可是查询就比较坑了,由于硬盘中这么多数据要慢慢的进行遍历,那就只能慢慢等等了。。。

  假如硬盘中的数据是有序排列的,那么咱们去查找一条记录的时候就会很快,能够用二分法查找,到底有多快呢?假如你要从50万条数据中查找某条数据,最多须要查找19次,若是一次10微秒,那总共190微秒,比咱们眨一下眼睛的时间还短;

  二分法其实很容易的一个东西,举个例子一个数组中有顺序的数据0、一、二、三、四、五、六、七、八、九、10,咱们要查找9所在的位置!假如咱们用遍历那就须要10次;若是用二分法,首先9和中间的5比较,比5大,那就再和右半部分中间的8比较,比8大,继续在右边查找,能够找到9,只须要三次操做,数据量越大二分法的效果越明显;可是二分法也有缺陷就是必需要让数据有序,这就致使插入(或删除)的时候比较坑爹,就相似有序数组的插入,插入一个数据以后就要将这个数据后面的全部都日后移动一个位置,并且数据越多插入的效率越糟糕;

  回到硬盘中数据的存储,假如咱们将硬盘中的数据弄成有序的,分块完了以后(后面操做是以块为单位),查找能够用二分查找先找到某块数据,读取到磁盘驱动器的缓存中,而后里面就16条数据很快就能够找到;可是插入数据的话平均要移动一半的块,每移动一个块都须要一次硬盘的读写操做(就是都要通过2.1到2.2这个步骤而后把数据写入硬盘,贼坑!),下面就随意说说一次读写究竟是怎么作的;

  假如咱们如今要插入一条记录A,第一步:首先会通过2.1和2.2读取一块数据并存在缓存中,将这块数据最后一条记录保存下来,而后判断记录A能够放在这块数据的哪里,适当移动这块数据中记录的位置并插入数据A,而后就把缓存区中的这块数据写入磁盘中;第二步:读取下一块数据,也是保存这块数据的最后一条记录,将这块数据中的记录都日后移动一个空位置,让上一块数据中保存的最后的一条记录插入到这块的最开始的位置,而后将本块数据写入磁盘;这个第二步会一直重复,直到将全部记录都从新写入。。。

  咱们上面数据分块是31250块,假如每次读写都要10毫秒,那么咱们插入一条记录差很少须要5分钟,真是足够坑爹!

 

3.简单看看B树

  根据上面咱们能够知道数据存到硬盘中是分为两种状况的,可是两种状况各有优缺点,那有没有汲两者优势的存储方式呢?聪明的大佬们早就想出来了,这就像数组和链表的关系,最终咱们引入了树的概念完美解决了数组和链表的缺陷,相似的如今咱们在硬盘中也要引入一种树来解决,这种树就是B树;

  B树是一种多叉树,也有人称为B-树,其实就是一种树!有点相似2-3-4树,只是B树每一个节点都有不少个节点,那到底能够是多少个呢?咱们慢慢看,补充一点,前面咱们学习过的2-3树和2-3-4树都只是B树的两种特殊状况,若是对这两种树不熟悉的必定要先去看看;

  根据前面的2-3-4树能够知道一个非叶节点的子节点数目 = 节点数据项+1,在B树中也是这样;还有,既然都说了2-3树和2-3-4树都只是B树的特殊状况,那就能够猜到B树的节点中的数据估计有不少个,咱们该怎么选取节点才是最高效的呢?还记得前面说的分块吗,咱们这里就是将一块数据做为一个节点;

  咱们再回顾一下上面说的城市的电话记录的例子,总共有2亿5千6百万字节,一条记录512字节,根据每一块数据8192字节(16条记录)进行分块,能够分为31250块,换句话说每块数据中存有16条数据,这就有点意思了,咱们把每16条记录看做B树的一个节点的数据项,那么就应该有17个子节点才对;咱们还知道每个节点要保存子节点的引用,怎么作比较好呢?比较奢侈的作法是:让每一个节点只保存15条记录,还有一条记录大小的空间用于存放子节点和父节点的引用吧!此时只有16个子节点;可是比较高效的作法是:一个节点中最好存偶数个数据,而后适当缩小每条记录的大小为507个字节,那么每一块中16条数据会占用8112字节,还剩下80字节,咱们有17个子节点,每一个子节点引用时int类型(一个int数据占4个字节)的数据,17x4=68 < 80,说明节点空间中即便保存子节点引用仍是够用,下图所示:

  下面咱们简单说说B树的一些基本操做,不用代码来表示的,了解原理便可;

  3.1 查找

  这个查找的操做和2-3-4树差很少,就是子节点多了不少而已,很容易!首先将根节点数据读到内存中,而后根据搜索算法对这个节点进行搜索,从0开始,其实就是比较在哪一个范围下,而后继续到对应的子节点那里去找。。。重复这个步骤就能找到目的数据;

  3.2 插入

  插入这个操做就跟2-3树差很少了,为何不用2-3-4树那样的插入方式呢?由于咱们能够知道2-3-4树中的节点不少都是没有放满的,有不少节点只存了一个数据,有太多空位置,假如B树用这样的方式硬盘中会很浪费空间,而用相似2-3树这种方式利用率就比较高,咱们能够看看B树中是怎么分裂的;

·  在插入数据的时候,假设该节点已经满了,咱们还要向其中插入一个数据下图所示;

 

  咱们将70和节点中的数据进行从小到大排列,而后以中间数据60为界限,左边的数据不动,中间数据60放入父节点(根节点比较特殊,要新建一个父节点),右边的数据放入新建的节点:

 

  继续插入1八、30、15,过程以下,比较相似2-3树的分裂,没什么特别很差理解的,只是子节点比较多而已

  

  最后看一个比较复杂的节点分裂,其实跟前面差很少。。。

 

  能够简单看到B树中,除了根节点以外,其余的节点最低也会用一半的空间,利用率最低也是50%,这就已经很能够了;

 

4.B树效率

  B树的效率如何呢?空间利用率还行,咱们仍是以最开始的那个城市电话记录为例子简单说说,总共有50万条记录以B树的形式存在硬盘中,每一个节点至少也是半满,咱们就以每一个节点装满一半数据计算,能够算出树的高度大概为6,每一个节点有8条记录(对应9个子节点);

  至于到底怎么计算的,感受没什么好说的,B树节点存数据的大小(或者说的块数据)是必定的,跟操做系统等因素有关,咱们这里为16,每一个节点存一半就是存8条数据,拥有9个子节点,是在不行用计算器算一下树的高度最低要为6才能存满50万数据,虽然我是对于这种计算的东西没多大兴趣。。。

  因为树只有6层,那么咱们查找任意一条数据也就须要6次比较而已,假设每次为10毫秒,那么最多就花费60毫秒,6/100秒;你想一想从50万条数据中任意查找一条记录最可能是6/100秒,这个效率已经很ok了,虽然查找效率已经很不错了,可是插入和删除操做才能显示出B树的最大优越性;

   就好比说插入,假如插入数据到叶节点中,该叶节点没有满,那就不须要进行分裂,也就须要对硬盘7次操做,前六次是比较使得找到正确节点,第7次就是读取叶节点数据到缓存,插入数据而后写入硬盘;假如插入数据以后叶节点要进行分裂的话,找到叶节点、移动节点中的数据和建立新的节点等操做合在一块儿也就须要对硬盘的12步操做,这效率很厉害了,不能再多说了,这篇已经足够长了。。。

 

5.总结

  B树其实也就这样吧,多是没有仔细实现B树代码,可是感受还行,最复杂的仍是分裂这里,不过也就和2-3-4树差不了多少!

  其实B树有不少种,咱们常说的B树和B-树是同样的,还有B+树、B*树;其中B+树是对B-树的一个改进(多应用于操做系统索引和数据库索引),B*树又是对B+树的一个改进,了解了B-树以后再看后面这两种树很容易的,无非是增长几个指针,提升了节点利用率什么的,后面有时间再说吧!话说还有个R树,emmm....须要的时候再去看吧!

相关文章
相关标签/搜索