Introduction

Skip List gets O(log n) time complexity on average. And it is easy to implement compared to AVL tree or Red-Black tree. So Redis uses it to implement ordered set.

Implementation

Data Structure

Related File: redis.h

Redis adds a backward pointer for each skip list node to traverse reversely. Also there is a span variable in level entry to record how many nodes must be crossed when reaching to next node. Actually, when traverse list, we can accumulate span to get the rank of a node in sorted set.

Here is an example of skip list without dumb head node. There are three nodes and they are sorted by score. There are two lists: level 1 and level2. We can reach to node3 from node1 by level2 list directly, and its span is 2. Also, we can cross node2 to get to node3 with level1 list.

typedefstructzskiplistNode{robj*obj;// redis generic object
doublescore;structzskiplistNode*backward;// backward pointer, only exist in level zero list
structzskiplistLevel{structzskiplistNode*forward;// next node, may skip a lot of nodes
unsignedintspan;// number of nodes need be crossed to reach to next node
}level[];// level array to make up lists
}zskiplistNode;typedefstructzskiplist{structzskiplistNode*header,*tail;unsignedlonglength;// number of nodes
intlevel;// current level
}zskiplist;

Core API

Related file: t_zset.c

Init

Allocate memory for skip list and create a dumb head skip list node. This dumb head has the max levels(ZSKIPLIST_MAXLEVEL), all level’s pointer is initialised to null, so the skip list is empty. Actually, it has ZSKIPLIST_MAXLEVEL empty single lists.

zskiplist*zslCreate(void){intj;zskiplist*zsl;zsl=zmalloc(sizeof(*zsl));zsl->level=1;// only one level
zsl->length=0;// no node at present
zsl->header=zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);// initialise each level to empty list
for(j=0;j<ZSKIPLIST_MAXLEVEL;j++){zsl->header->level[j].forward=NULL;zsl->header->level[j].span=0;}zsl->header->backward=NULL;zsl->tail=NULL;returnzsl;}zskiplistNode*zslCreateNode(intlevel,doublescore,robj*obj){zskiplistNode*zn=zmalloc(sizeof(*zn)+level*sizeof(structzskiplistLevel));zn->score=score;zn->obj=obj;returnzn;}

Destroy

In Redis, there is an abstract data type: robj. It can be shared by many structures using reference count to save memory. So when destroy node, just decrease robj reference count.

voidzslFree(zskiplist*zsl){zskiplistNode*node=zsl->header->level[0].forward,*next;zfree(zsl->header);// free dumb head
// free all nodes one by one
// just use level[0] list because all nodes must be in level[0] list
while(node){next=node->level[0].forward;zslFreeNode(node);node=next;}zfree(zsl);/freeskiplistitself}voidzslFreeNode(zskiplistNode*node){decrRefCount(node->obj);// decrease reference count
zfree(node);}

Insert

zslInsert is the most important function of skip list. The update array stores previous pointers for each level, new node will be added after them. rank array stores the rank value of each skiplist node.