Redis的里面的sort-set(有序集)就是采用skiplist来实现的。skiplist的性能和Red-black差不多。算法实现比RBT要简单许多,改动的节点少,不涉及Re-Balance.

先形象的观察下skiplist的结构:

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

相关文章:

  • 2021-07-31
  • 2021-09-28
猜你喜欢
  • 2022-01-13
  • 2021-07-15
  • 2021-08-15
  • 2021-12-04
  • 2021-12-15
相关资源
相似解决方案