为什么使用跳跃表?
跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要简单,所以有不少程序都使用跳跃表来代替平衡树。
跳跃表是什么?
跳跃表是一种有序数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问的目的。
跳跃表在哪使用?
跳跃表是有序集合的底层实现之一。
一、跳跃表结构与实现
整体结构图:
跳跃表节点redis.h/zskiplistNode
typedef struct zskiplistNode{
//层
struct zskiplistLevel{
//前进指针
struct zskiplistNode *forward;
//跨度
unsigned int span;
}level[];
//后退指针
struct zskiplistNode *backward;
//分值(用于排序)
double score;
//成员对象
robj *obj;
}zskiplistNode;
层(level):上图中的L1、L2、L3等字样,L1表示第一层、L2表示第二层,以此类推。每层有两个属性:前进指针和跨度。前进指针用于访问位于标为方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。上图中带有数字的箭头就代表前进指针,所带的数字表示跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。
后退(backward)指针:上图中BW字样表示后退指针。它指向位于当前节点的前一个节点。后退指针在程序从表尾到表头遍历时使用。
分值(score):上图中的1.0、2.0、3.0即对应节点所保存的分值。
成员对象(obj):上图中的o1、o2、o3即对应节点所保存的对象。
注:表头节点和其他节点构造是一样的,但其中的后退指针、分值、成员对象不会被用到。只会用层。
redis.h/zskiplist
typedef struct zskiplist{
//表头节点和表尾节点
struct zskiplistNode *header,*tail;
//表中节点数量
unsigned long length;
//表中层数最大的节点的层数(表头节点的层高并不计算在内)
int level;
}zskiplist;
二、
层的作用:跳跃表节点的level数组可以包含多个元素,每个元素包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多访问其他节点的速度就越快。
层的高度:
每次创建一个新跳跃表节点的时候,程序都根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1-32之间的只作为level数组的大小,这个大小就是层的高度。
分数排序规则:
1、跳跃表中所有节点都按分值从小到大排序
2、分值相同的节点将按成员对象在字典序中的大小来排序,成员对象较小的节点会排在前面(靠近表头的方向),而成员对象较大的节点则会排在后面(靠近表尾的方向),简言之:分值相同按字典序中表头到表尾的顺序排序。
成员对象唯一性:
在同一个跳跃表中,各个节点保存的成员对象必须是唯一的。
zskiplist作用:
获取表头节点、表尾节点、节点数量、level最大层高的复杂度为O(1)