SkipList 跳表搜索

  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 }
View Code

 

2、Insert operation

  

在上图中,需要插入17节点。其中update[i]保存待插入点的位置。

备注:插入的时候,会调用一个randomLevel的函数,他可以概率返回1~MAX_LEVEL之间的值,但是level的值越大,概率越小。

         判断是否存在相同的score,是通过forwar[0]来判断的,因为level=1存放的是所有的节点。

 1 skiplistNode *slInsert(skiplist *sl, void* value, double score)
 2 {
 3     skiplistNode* sn,*update[SKIPLIST_MAXLEVEL];
 4     int i, level;
 5     sn = sl->head;
 6     for(i=sl->level-1;i>=0;i--)
 7     {
 8         while(sn->forward[i]&&(sn->forward[i]->score<score)){
 9             sn = sn->forward[i];
10         }
11         update[i] = sn;
12     }
13     if(sn->forward[0]&&sn->forward[0]->score == score)
14     {
15         printf("insert failed,exist!!\n");
16         return NULL;
17     }
18 
19     level = slRandomLevel();
20     printf("score:%.2lf level:%d\n",score,level);
21     if(level>sl->level){
22         for(i=sl->level;i<level;i++)
23         {
24             update[i] = sl->head;
25         }
26         sl->level = level;
27     }
28     sn = createNode(level,value,score);
29     for(i=0;i<level;i++)
30     {
31         sn->forward[i] = update[i]->forward[i];
32         update[i]->forward[i] = sn;
33     }
34     sl->length++;
35     return sn;
36 }
View Code

2、Delete Operation

删除的时候和插入相同,都是先搜索。唯一注意的一点是删除的节点也许要修改skiplist->length的值。

备注:也许存在level=MAX_LEVEL的节点个数大于等于2,所以按下面的更新length是安全的。

 1 int slDelete(skiplist *sl, double score)
 2 {
 3     skiplistNode *update[SKIPLIST_MAXLEVEL];
 4     skiplistNode *sn;
 5     int i;
 6     sn = sl->head;
 7     for(i=sl->level-1;i>=0;i--)
 8     {
 9         while(sn->forward[i]&&sn->forward[i]->score<score){
10             sn = sn->forward[i];
11         }
12         update[i] = sn;
13     }
14     sn = sn->forward[0];
15     if(sn->score != score)
16     {
17         return -1;
18     }
19     for(i=0;i<sl->level;i++)
20     {
21         if(update[i]->forward[i] != sn){
22             break;
23         }
24         update[i]->forward[i] = sn->forward[i];
25     }
26     free(sn);
27     while(sl->level>1&&sl->head->forward[sl->level-1] == NULL){
28         sl->level--;
29     }
30     sl->length--;
31     return 0;
32 }
View Code

3、Free SkipList

销毁整个调表的时候,从level=1销毁即可,别忘记释放head和skiplist。

 1 void slFree(skiplist *sl)
 2 {
 3     skiplistNode* sn = sl->head,*st;
 4     st = sn->forward[0];
 5     free(sn);
 6     while(st)
 7     {
 8         sn = st->forward[0];
 9         free(st);
10         st = sn;
11     }
12     free(sl);
13 }
View Code

 

总结:

  平均复杂度 最坏复杂度
空间代价 O(n) O(n*log n)
查询操作 O(log n) O(n)
插入操作 O(log n) O(n)
删除操作 O(log n)  O(n)

     

 

 



 

 

 

 

posted @ 2013-05-30 17:37  lonelytree  阅读(1088)  评论(0编辑  收藏  举报