数据结构之MySQL独爱B+树(二叉树、AVL树、红黑树、B树对比)
一、二叉查找树#
(1)二叉树简介:
二叉查找树也称为有序二叉查找树,满足二叉查找树的一般性质,是指一棵空树具有如下性质:
1、任意节点左子树不为空,则左子树的值均小于根节点的值;
2、任意节点右子树不为空,则右子树的值均大于于根节点的值;
3、任意节点的左右子树也分别是二叉查找树;
4、没有键值相等的节点;
上图为一个普通的二叉查找树,按照中序遍历的方式可以从小到大的顺序排序输出:15、20、30、50、60、70。
对上述二叉树进行查找,如查键值为 30 的记录,先找到根,其键值是 50,50 大于 30,因此查找 50 的左子树,找到 20;而 30 大于 20,再找其右子树;一共找了 3 次。如果按 15、20、30、50、60、70 的顺序来找同样需求 3 次。用同样的方法在查找键值为 70 的这个记录,这次用了 3 次查找,而顺序查找需要 6 次。计算平均查找次数得:顺序查找的平均查找次数为(1+2+3+4+5+6)/ 6 = 3.3 次,二叉查找树的平均查找次数为(3+3+3+2+2+1)/6=2.3 次。二叉查找树的平均查找速度比顺序查找来得更快。
(2)局限性及应用
一个二叉查找树是由 n 个节点随机构成,所以,对于某些情况,二叉查找树会退化成一个有 n 个节点的线性链。
大家看上图,如果我们的根节点选择是最小或者最大的数,那么二叉查找树就完全退化成了线性结构。上图中的平均查找次数为(1+2+3+4+5+5)/6=3.16 次,和顺序查找差不多。显然这个二叉树的查询效率就很低,因此若想最大性能的构造一个二叉查找树,需要这个二叉树是平衡的(这里的平衡从一个显著的特点可以看出这一棵树的高度比上一个输的高度要大,在相同节点的情况下也就是不平衡),从而引出了一个新的定义 - 平衡二叉树 AVL。
二、AVL 树#
(1)简介
AVL 树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过 1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过 1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道 AVL 树适合用于插入删除次数比较少,但查找多的情况。
从上面是一个普通的平衡二叉树,这张图我们可以看出,任意节点的左右子树的平衡因子差值都不会大于 1。
(2)局限性
由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么 AVL 还是较优于红黑树。
(3)应用
1、Windows NT 内核中广泛存在;
三、红黑树#
(1)简介
一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是 red 或 black。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍。它是一种弱平衡二叉树 (由于是若平衡,可以推出,相同的节点情况下,AVL 树的高度低于红黑树),相对于要求严格的 AVL 树来说,它的旋转次数变少,所以对于搜索、插入、删除操作多的情况下,我们就用红黑树。
(2)性质
1、每个节点非红即黑;
2、根节点是黑的;
3、每个叶节点 (叶节点即树尾端 NULL 指针或 NULL 节点) 都是黑的;
4、如果一个节点是红的,那么它的两儿子都是黑的;
5、对于任意节点而言,其到叶子点树 NULL 指针的每条路径都包含相同数目的黑节点;
6、每条路径都包含相同的黑节点;
(3)应用
1、广泛用于 C++ 的 STL 中,Map 和 Set 都是用红黑树实现的;
2、著名的 Linux 进程调度 Completely Fair Scheduler,用红黑树管理进程控制块,进程的虚拟内存区域都存储在一颗红黑树上,每个虚拟地址区域都对应红黑树的一个节点,左指针指向相邻的地址虚拟存储区域,右指针指向相邻的高地址虚拟地址空间;
3、IO 多路复用 epoll 的实现采用红黑树组织管理 sockfd,以支持快速的增删改查;
4、Nginx 中用红黑树管理 timer,因为红黑树是有序的,可以很快的得到距离当前最小的定时器;
5、Java 中 TreeMap 的实现;
四、B/B + 树#
(1)简介
B/B + 树是为了磁盘或其它存储设备而设计的一种平衡多路查找树 (相对于二叉,B 树每个内节点有多个分支),与红黑树相比,在相同的的节点的情况下,一颗 B/B + 树的高度远远小于红黑树的高度 (在下面 B/B + 树的性能分析中会提到)。B/B + 树上操作的时间通常由存取磁盘的时间和 CPU 计算时间这两部分构成,而 CPU 的速度非常快,所以 B 树的操作效率取决于访问磁盘的次数,关键字总数相同的情况下 B 树的高度越小,磁盘 I/O 所花的时间越少。
注意 B - 树就是 B 树,- 只是一个符号。
(2)B 树的性质
1、定义任意非叶子结点最多只有 M 个儿子,且 M>2;
2、根结点的儿子数为 [2, M];
3、除根结点以外的非叶子结点的儿子数为 [M/2, M];
4、每个结点存放至少 M/2-1(取上整)和至多 M-1 个关键字;(至少 2 个关键字)
5、非叶子结点的关键字个数 = 指向儿子的指针个数 - 1;
6、非叶子结点的关键字:K [1], K [2], …, K [M-1];且 K [i] < K [i+1];
7、非叶子结点的指针:P [1], P [2], …, P [M];其中 P [1] 指向关键字小于 K [1] 的子树,P [M] 指向关键字大于 K [M-1] 的子树,其它 P [i] 指向关键字属于 (K [i-1], K [i]) 的子树;
8、所有叶子结点位于同一层;
这里只是一个简单的 B 树,在实际中 B 树节点中关键字很多的。
五、B + 树#
(1)简介
B + 树是应文件系统所需而产生的一种 B 树的变形树(文件的目录一级一级索引,只有最底层的叶子节点(文件)保存数据)非叶子节点只保存索引,不保存实际的数据,数据都保存在叶子节点中。所有的非叶子节点都可以看成索引部分!
(2)B + 树的性质 (下面提到的都是和 B 树不相同的性质)
1、非叶子节点的子树指针与关键字个数相同;
2、非叶子节点的子树指针 p [i], 指向关键字值属于 [k [i],k [i+1]] 的子树.(B 树是开区间,也就是说 B 树不允许关键字重复,B + 树允许重复);
3、为所有叶子节点增加一个链指针;
4、所有关键字都在叶子节点出现 (稠密索引). (且链表中的关键字恰好是有序的);
5、非叶子节点相当于是叶子节点的索引 (稀疏索引), 叶子节点相当于是存储 (关键字) 数据的数据层;
6、更适合于文件系统;
非叶子节点(比如 5,28,65)只是一个 key(索引),实际的数据存在叶子节点上(5,8,9)才是真正的数据或指向真实数据的指针。
(3)应用
1、B 和 B + 树主要用在文件系统以及数据库做索引,比如 MySQL;
六、B/B + 树性能分析#
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 树更适合数据库索引?#
1、 B + 树的磁盘读写代价更低:B + 树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对 B 树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对 IO 读写次数就降低了。
2、B + 树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
3、由于 B + 树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是 B 树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以 B + 树更加适合在区间查询的情况,所以通常 B + 树用于数据库索引。
4、B 树在提高了 IO 性能的同时并没有解决元素遍历的我效率低下的问题。B + 树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而 B 树不支持这样的操作或者说效率太低
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: