(1)二叉树简介: 二叉查找树也称为有序二叉查找树,知足二叉查找树的通常性质,是指一棵空树具备以下性质:数据库
一、任意节点左子树不为空,则左子树的值均小于根节点的值;数据结构
二、任意节点右子树不为空,则右子树的值均大于于根节点的值;性能
三、任意节点的左右子树也分别是二叉查找树; 优化
四、没有键值相等的节点;
3d
上图为一个普通的二叉查找树,按照中序遍历的方式能够从小到大的顺序排序输出:二、三、五、六、七、8。 对上述二叉树进行查找,如查键值为5的记录,先找到根,其键值是6,6大于5,所以查找6的左子树,找到3;而5大于3,再找其右子树;一共找了3次。若是按二、三、五、六、七、8的顺序来找一样需求3次。用一样的方法在查找键值为8的这个记录,此次用了3次查找,而顺序查找须要6次。计算平均查找次数得:顺序查找的平均查找次数为(1+2+3+4+5+6)/ 6 = 3.3次,二叉查找树的平均查找次数为(3+3+3+2+2+1)/6=2.3
指针
(2)局限性及应用
cdn
一个二叉查找树是由n个节点随机构成,因此,对于某些状况,二叉查找树会退化成一个有n个节点的线性链表。以下图:
blog
如上图,若是咱们的根节点选择是最小或者最大的数,那么二叉查找树就彻底退化成了线性结构。上图中的平均查找次数为(1+2+3+4+5+5)/6=3.16次,和顺序查找差很少。显然这个二叉树的查询效率就很低,所以若想最大性能的构造一个二叉查找树,须要这个二叉树是平衡的、从而引出了一个新的定义-平衡二叉树AVL
排序
(1)简介索引
AVL树是带有平衡条件的二叉查找树,通常是用平衡因子差值判断是否平衡并经过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须知足(全部节点的左右子树高度差不超过1)。无论咱们是执行插入仍是删除操做,只要不知足上面的条件,就要经过旋转来保持平衡,而旋转是很是耗时的,由此咱们能够知道AVL树适合用于插入删除次数比较少,查找多的状况。
从上面是一个普通的平衡二叉树,这张图咱们能够看出,任意节点的左右子树的平衡因子差值都不会大于1。
(2)局限性
因为维护这种高度平衡所付出的代价比从中得到的效率收益还大,故而实际的应用很少,更多的地方是用追求局部而不是很是严格总体平衡的红黑树。固然,若是应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL仍是较优于红黑树。
(1)简介
一种二叉查找树,但在每一个节点增长一个存储位表示节点的颜色,能够是red或black。经过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍。它是一种弱平衡二叉树(因为是弱平衡,能够推出,相同的节点状况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来讲,它的旋转次数变少,因此对于搜索、插入、删除操做多的状况下,咱们就用红黑树
(2)性质
一、每一个节点非红即黑;
二、根节点是黑的;
三、每一个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
四、若是一个节点是红的,那么它的两儿子都是黑的;
五、对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;
六、每条路径都包含相同的黑节点;
(3)应用
一、普遍用于C++的STL中,Map和Set都是用红黑树实现的;
二、著名的Linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块,进程的虚拟内存区域都存储在一颗红黑树上,每一个虚拟地址区域都对应红黑树的一个节点,左指针指向相邻的地址虚拟存储区域,右指针指向相邻的高地址虚拟地址空间;
三、IO多路复用epoll的实现采用红黑树组织管理sockfd,以支持快速的增删改查;
四、Nginx中用红黑树管理timer,由于红黑树是有序的,能够很快的获得距离当前最小的定时器;
五、Java中TreeMap的实现;
说了上述的三种树:二叉查找树、AVL和红黑树,彷佛咱们尚未摸到MySQL为何要使用B+树做为索引的实现,不要急,接下来咱们就先探讨一下什么是B树
(1)简介
咱们在MySQL中的数据通常是放在磁盘中的,读取数据的时候确定会有访问磁盘的操做,磁当大规模数据存储到磁盘中的时候,显然是一个很是花费时间的过程,能够经过B树进行优化,提升磁盘读取时定位的效率。 为何B类树能够进行优化呢?咱们能够根据B类树的特色,构造一个多阶的B类树,而后在尽可能多的在结点上存储相关的信息,保证层数尽可能的少,以便后面咱们能够更快的找到信息,磁盘的I/O操做也少一些,并且B类树是平衡树,每一个结点到叶子结点的高度都是相同,这也保证了每一个查询是稳定的。与红黑树相比,在相同的的节点的状况下,一颗B/B+树的高度远远小于红黑树的高度(在下面B/B+树的性能分析中会提到)。B/B+树上操做的时间一般由存取磁盘的时间和CPU计算时间这两部分构成,而CPU的速度很是快,因此B树的操做效率取决于访问磁盘的次数,关键字总数相同的状况下B树的高度越小,磁盘I/O所花的时间越少。 注意B-树就是B树
(2)B树的性质
一、定义任意非叶子结点最多只有M个儿子,且M>2;
二、根结点的儿子数为[2, M];
三、除根结点之外的非叶子结点的儿子数为[M/2, M];
四、每一个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
五、非叶子结点的关键字个数=指向儿子的指针个数-1;
六、非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
七、非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
八、全部叶子结点位于同一层;
这里只是一个简单的B树,在实际中B树节点中关键字不少的,上面的图中好比35节点,35表明一个key(索引),而小黑块表明的是这个key所指向的内容在内存中实际的存储位置,是一个指针。
(1)简介
B+树是应文件系统所需而产生的一种B树的变形树(文件的目录一级一级索引,只有最底层的叶子节点(文件)保存数据)非叶子节点只保存索引,不保存实际的数据,数据都保存在叶子节点中,这不就是文件系统文件的查找吗? 咱们就举个文件查找的例子:有3个文件夹a、b、c, a包含b,b包含c,一个文件yang.c,a、b、c就是索引(存储在非叶子节点), a、b、c只是要找到的yang.c的key,而实际的数据yang.c存储在叶子节点上。 全部的非叶子节点均可以当作索引部分
(2)B+树的性质(下面提到的都是和B树不相同的性质)
一、非叶子节点的子树指针与关键字个数相同
二、非叶子节点的子树指针p[i],指向关键字值属于[k[i],k[i+1]]的子树.(B树是开区间,也就是说B树不容许关键字重复,B+树容许重复);
三、为全部叶子节点增长一个链指针;
四、全部关键字都在叶子节点出现(稠密索引). (且链表中的关键字刚好是有序的);
五、非叶子节点至关因而叶子节点的索引(稀疏索引),叶子节点至关因而存储(关键字)数据的数据层;
六、更适合于文件系统
非叶子节点(好比5,28,65)只是一个key(索引),实际的数据存在叶子节点上(5,8,9)才是真正的数据或指向真实数据的指针。
(3)应用
一、B和B+树主要用在文件系统以及数据库作索引,好比MySQL;
n个节点的平衡二叉树的高度为H(即logn),而n个节点的B/B+树的高度为logt((n+1)/2)+1; 若要做为内存中的查找表,B树却不必定比平衡二叉树好,尤为当m较大时更是如此。由于查找操做CPU的时间在B-树上是O(mlogtn)=O(lgn(m/lgt)),而m/lgt>1;因此m较大时O(mlogtn)比平衡二叉树的操做时间大得多。所以在内存中使用B树必须取较小的m。(一般取最小值m=3,此时B-树中每一个内部结点能够有2或3个孩子,这种3阶的B-树称为2-3树)。
一、 B+树的磁盘读写代价更低:B+树的内部节点并无指向关键字具体信息的指针,所以其内部节点相对B树更小,若是把全部同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的须要查找的关键字也就越多,相对IO读写次数就下降了。
二、B+树的查询效率更加稳定:因为非终结点并非最终指向文件内容的结点,而只是叶子结点中关键字的索引。因此任何关键字的查找必须走一条从根结点到叶子结点的路。全部关键字查询的路径长度相同,致使每个数据的查询效率至关。
三、因为B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只须要扫一遍叶子结点便可,可是B树由于其分支结点一样存储着数据,咱们要找到具体的数据,须要进行一次中序遍历按序来扫,因此B+树更加适合在区间查询的状况,因此一般B+树用于数据库索引。
PS:我在知乎上看到有人是这样说的,我感受说的也挺有道理的:
他们认为数据库索引采用B+树的主要缘由是:B树在提升了IO性能的同时并无解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只须要去遍历叶子节点就能够实现整棵树的遍历。并且在数据库中基于范围的查询是很是频繁的,而B树不支持这样的操做或者说效率过低。