Redis的里面的sort-set(有序集)就是采用skiplist来实现的。skiplist的性能和Red-black差不多。算法实现比RBT要简单许多,改动的节点少,不涉及Re-Balance.
先形象的观察下skiplist的结构:
观察上图,可知skiplist的最高有4层,每层有序,第1层包含所有的节点。通过概率算法,可以控制层数越高的节点个数越来越少。那么跳表是如何来搜索的呢?比如在上图搜索17.
为了加快搜索,需要从最高层4开始搜索(因为最高层的节点少,容易定位),首先查看6节点,发现17比其大,向后搜索,发现6后面的节点指向了Nil(第4层),那么搜索的层数降低1层,
从此节点的第3层开始搜索,发现下个节点是25,大于17,那么再降低一层,从2层开始搜索,发现第2层是9,小于17,继续搜索,发现9节点的下一个数是17,搜索完成。总共查询了
4次,完成搜索(不包含NIL节点的访问。),这种情况下普通有序链表需要6次访问。可以设想下,如果层数为1层的话,那么此时跳表为最坏的情况,退化成有序单链表。复杂度O(n)。
搜索OK的话,那么Insert和Delete就没有太大问题,因为这两个操作都需要先搜索。
1、数据结构的定义
如上图中的E节点,表示的是头节点,一般跳表的实现,最大有多少层(MAX_LEVEL)是确定的。所以e的个数是固定的。
1 #define SKIPLIST_MAXLEVEL 32 2 #define ZSKIPLIST_P 0.25 3 4 typedef struct skiplistNode_t{ 5 void* value; 6 double score; 7 struct skiplistNode_t* forward[]; 8 }skiplistNode; 9 10 typedef struct skiplist{ 11 skiplistNode* head; 12 int level; 13 uint32_t length; 14 }skiplist;
MAX_LEVEL = log(1/p)N,带入上面的参数,那么N = 2^64.(具体参见William Pugh的论文)。
struct skiplistNode_t* forward[];在MS的编译器上估计会报错,GUN C支持此扩展特性,这种用法在Redis的源码中比较常见。(推荐Code Block编译器)。
初始化代码:调用此语句skiplist* my_sl = slCreate();内存空间就会把固定的e部分生成。
1 skiplistNode* createNode(int level,void* value,double score) 2 { 3 skiplistNode *slNode = (skiplistNode*)malloc(sizeof(*slNode)+level*sizeof(skiplistNode)); 4 if(!slNode) 5 { 6 return NULL; 7 } 8 slNode->value = value; 9 slNode->score = score; 10 return slNode; 11 } 12 13 skiplist* slCreate(void) 14 { 15 int i; 16 skiplist* sl = (skiplist*)malloc(sizeof(*sl)); 17 if(!sl) 18 { 19 return NULL; 20 } 21 sl->length = 0; 22 sl->level = 1; 23 sl->tail = NULL; 24 sl->head = createNode(SKIPLIST_MAXLEVEL,NULL,0.0); 25 for(i=0;i<SKIPLIST_MAXLEVEL;i++) 26 { 27 sl->head->forward[i] = NULL; 28 } 29 return sl; 30 }