跳跃表通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找。
跳跃表的实现
跳跃表定义在server.h文件中,下图所示为一个跳跃表的示例:
上图中最左边的是zskiplist结构,右边四个是跳跃表节点(zskiplistNode结构),跳跃表的结构定义如下:
// 跳跃表节点
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
各属性说明如下:
- score:跳跃表节点的分值;
- level:是一个数组,数组中每个元素有一个指向表结尾方向某个节点的指针(forward)和当前节点与forward节点的跨度(span),如上图所示,肩头上的数字表示跨度;
- backward:后退指针,上图所示中BW字样标记节点的后退指针,它指向位于当前节点的前一个节点,后退指针在程序从表尾向表头遍历时使用;
- ele:节点保存的成员对象(sds是char *);
zskiplist结构的定义如下:
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
各属性说明如下:
- header:指向跳跃表的表头节点;
- tail:指向跳跃表的表尾节点;
- level:记录目前跳跃表内,层数最大的那个节点的层数(表头结点不算);
- length:记录跳跃表的长度,也即跳跃表目前包含节点的数量(表头节点不计算在内);
注:表头结点和其他节点构造是一样的,表头结点也有后退指针、分值和成员对象,不过表头结点的这些属性都不会用到
跳跃表从表头节点开始以跨度为1向表尾方向前进即可遍历整个跳跃表。跨度可以用来计算排位:在查找某个节点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是目标节点在跳跃表中的排位。
跳跃表中的所有节点都是按照分值从小到大排序的,同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但多个节点保存的分值却可以是相同的,分值相同的节点将按照成员对象在字典中的大小来排序,成员对象较小的节点会排在前面(靠近表头方向),而成员对象较大的节点则会排在后面(靠近表尾方向)。