先分析一波红黑树吧。。
根节点到任意一个叶子节点所经过的节点数量是相同的
- 每个节点或者是红色,或者是黑色(定义)
- 根节点一定是黑色的(对应2-3树的2节点和3节点)
- 空节点是黑色的
- 如果一个节点是红色的,那么它的孩子节点都是黑色的
- 根节点到任意一个叶子节点所经过的黑节点个数是一样的,即红黑树是一颗保持黑平衡的二叉树,不是平衡二叉树,但左右子树的黑色节点保持绝对的平衡,增删改查复杂度 O(logN),添加和删除操作比AVL树快
我们刚刚提到了很多平衡二叉查找树,现在我们就来看下,为什么在工程中大家都喜欢用红黑树这种平衡二叉查找树?我们前面提到Treap、Splay Tree,绝大部分情况下,它们操作的效率都很高,但是也无法避免极端情况下时间复杂度的退化。尽管这种情况出现的概率不大,但是对于单次操作时间非常敏感的场景来说,它们并不适用。AVL树是一种高度平衡的二叉树,所以查找的效率非常高,但是,有利就有弊,AVL树为了维持这种高度的平衡,就要付出更多的代价。每次插入、删除都要做调整,就比较复杂、耗时。所以,对于有频繁的插入、删除操作的数据集合,使用AVL树的代价就有点高了。红黑树只是做到了近似平衡,并不是严格的平衡,所以在维护平衡的成本上,要比AVL树要低。所以,红黑树的插入、删除、查找各种操作性能都比较稳定。对于工程应用来说,要面对各种异常情况,为了支撑这种工业级的应用,我们更倾向于这种性能稳定的平衡二叉查找树。
跳表和红黑树
Redis 为何选用跳表
- 有序的单链表 O(N)
- 提高查找效率 --》 对链表建立索引
- 建立了多级索引,每层索引中,每个节点指向有两个指向,向右和向下
- 每一层索引节点个数是下层节点个数的一半
- 查询效率O(logN), 插入,删除也是
- 通过随机函数维持平衡性,当往跳表中插入数据时,可以通过随机函数选择选择向哪些索引中添加节点,
Redis中的有序集合是通过跳表来实现的,严格点讲,其实还用到了散列表。如果你去查看Redis的开发手册,就会发现,Redis中的有序集合支持的核心操作主要有下面这几个:
插入一个数据;
删除一个数据;
查找一个数据;
按照区间查找数据(比如查找值在[100, 356]之间的数据);
迭代输出有序序列。
其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。
对于按照区间查找数据这个操作,跳表可以做到O(logn)的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。
当然,Redis之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
B+树
- 支持区间 (叶子节点连在一个链表上)
- 减少磁盘IO,(降低高度);m叉树,m越大越好?:操作系统按页来读取(4KB)。如果读取的数据量超过一页,就会触发多次IO操作。因此,在选择m的时候,尽量让每个节点的大小等于一个页的大小