「Redis数据结构」跳跃表(SkipList)
文章目录
- 「Redis数据结构」跳跃表(SkipList)
- 一、概述
- 二、结构
- 跳跃表节点
- 跳跃表
- 三、特点
一、概述
跳表(SkipList,全称跳跃表)是用于有序元素序列快速搜索查找的一个数据结构,跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。
跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。它在性能上和红黑树,AVL树不相上下,但是跳表的原理非常简单,实现也比红黑树简单很多。
二、结构
Redis 的跳跃表由 redis.h/zskiplistNode
和 redis.h/zskiplist
两个结构体定义。
跳跃表节点
redis.h/zskiplistNode结构用于保存跳跃表节点的相关信息。
/* 跳跃表节点 */
typedef struct zskiplistNode {
robj *obj; // 成员对象
double score; // 分值
struct zskiplistNode *backward; // 后退指针
// 层
struct zskiplistLevel
{
struct zskiplistNode *forward; // 前进指针
unsigned int span; // 跨度
} level[];
};
- 层
跳跃表节点的level数组可以包含多个元素,每个元素都包含一直指向其他节点的指针。
程序可以通过这些层来加快访问其他节点的速度。 - 前进指针
- 跨度
层的跨度用于记录2个节点之间的距离- 2个节点之间的
- 后退指针
- 分值和成员
跳跃表
/* 跳跃表 */
typedef struct zskiplist
{
struct zskiplistNode *header; // 头节点
struct zskiplistNode *tail; // 尾节点
unsigned long length; // 表中节点的数量
int level; // 表中层数最大的节点的层数
} zskiplist;
header
指向跳跃表的表头节点tail
指向跳跃表的表尾节点level
记录米钱跳跃表层内,层数最大呃那个节点层数。length
记录跳跃表的长度
下面是一个普通的排序单向链表。
如果你想寻找这个链表中有没有节点8,那么你就要从节点1开始,依次向后遍历,直到遍历到一个数值等于8的节点,返回true这个答案。
那么如何能让这个遍历的次数减少呢?
我们可以考虑这原有链表上面建立一个索引层,索引层的节点是最底层链表节点的一部分,然后索引层的节点有一个指针指向下一层节点自己的本体节点。
在查询的时候,我们可以从索引层去查询,先找到节点1,然后到节点4,然后到节点7,节点7之后没有节点了,那么就去下一层去寻找,下一层的节点7之后就是节点8,这次我们只遍历了4次就找到了答案。
那么上层的节点层的个数取多少合适呢?如果我们下层的节点个数是10W个,而上层只有三个索引节点的话,从节点7之后遍历,也要遍历9W多个,肯定是达不到我们预期的效率的。所以最好的策略就是,上层节点的个数是下层节点个数的一半,且尽量是随机均匀的,这样就可以节省一半的遍历次数,比如有10W个节点,我们就建立5W个索引节点,这样遍历次数就可以减少一半。
但是遍历5W次肯定也是不行的,我们知道O(n)和O(2n)这种在算法中是近似相等的,那么怎么办呢?一层不够我们就建立两层,两层不够就建立三层。直到最上层的节点个数是一两个,这样一层一层查找下去,我们就可以做到对数级别的时间复杂度,就可以媲美二分查找。
为什么不选用红黑树
红黑树也拥有二叉搜索树的特性,查找节点的时候拥有二分查找的性能,而且不用多余的内存空间。但是红黑树对范围搜索的支持就比较单调了,使用跳表来做范围查询,只需要找到头节点,然后往后遍历就好了,但是红黑树呢?找到头节点之后,你依然不知道它之后节点在哪,你只知道在左子树上,剩下你啥都不知道,就要继续遍历去找。
Redis的作者也说明过为什么使用跳表,大概就三点
- 虽然跳表消耗较多的内存,但是我们内部优化过跳表,可以接受。
- 跳表的实现很简单,至少比红黑树简单的多。
- 多范围查询的支持,跳表完胜红黑树。
三、特点
- 跳表结合了链表和类似二分查找的思想;
- 有很多层结构,由原始链表和一些通过“跳跃”生成的链表组成;
- 每一层都是一个有序的链表;
- 最底层(Level 1)的链表包含所有元素,越上层“跳跃”的越高,元素(索引)越少;
- 查找时从顶层向下,不断缩小搜索范围;
- 上层链表是下层链表的子序列;
- 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
参考
《Redis 设计与实现》
Redis跳表底层实现