跳跃表(skiplist)的定义与实现
在对排行榜数据进行排序时,我们会用到Redis中的有序集合对象(sorted set),跳跃表就是有序集合的底层实现之一。跳跃表是一种有序的数据结构,每个节点中都维持着多个指向其他节点的指针,所以可以快速地访问节点。跳跃表的查找速度平均为O(logN),最差为O(N)。
src下的redis.h中用zskiplistNode和zskiplist分别定义跳跃表节点以及跳跃表:
/* ZSETs use a specialized version of Skiplists*/
/*
* 跳跃表节点
*/
typedef struct zskiplistNode {
// 成员对象
robj*obj;
// 分值
doublescore;
// 后退指针
structzskiplistNode *backward;
// 层
structzskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned int span;
}level[];
} zskiplistNode;
/*
* 跳跃表
*/
typedef struct zskiplist {
// 表头节点和表尾节点
structzskiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中层数最大的节点的层数
intlevel;
} zskiplist;
zskiplistNode结构体中包含了以下属性:
1. 成员对象(obj)以及对应的分值(score):在跳跃表中,节点的成员变量是一个指向字符串对象的指针,且是唯一的;但分值是一个double类型,可以相同,跳跃表默认会以分值的大小递增排序,分值相同则根据成员对象的字典序进行排序。
2. 后退指针backward:用于从表尾逆向访问各节点,每个节点只有一个后退指针
3. 层level[]:层是一个level数组,其内部保存着的结构体为zskiplistLevel。zskiplistLevel内有两个属性,前进指针forward以及跨度span。每个层都有指向表尾方向的前进指针,用于至后访问节点。跨度span则记录着两个节点之间的距离,可以用来计算节点在跳跃表中的排位(rank):在查找节点过程中,将访问过的所有层的跨度累加就是目标节点的排位。值得一提的是,在新建跳跃表节点时,其层数是通过幂次定律(越大的数生成几率越小)随机生成的一个介于1-32之间的值。
跳跃表zskiplist中定义了表头节点及表尾节点、节点数目以及层数最大的节点的层数。因此用跳跃表访问表头及表尾节点的你复杂度均为O(1),这便省去了遍历的时间大大增强了效率。通过length属性也可以在O(1)时间内得到表中节点的数目。