ToB企服应用市场:ToB评测及商务社交产业平台

标题: 使用Go复刻skiplist核心功能 [打印本页]

作者: 万万哇    时间: 5 天前
标题: 使用Go复刻skiplist核心功能
0、引言

正好做LC逐日一题要求实现一个跳表,于是学习了redis的扩展skiplist,并使用Go进行复刻学习。学习参考了文章:Redis内部数据布局详解(6)——skiplist - 铁蕾的个人博客
因为作者本领有限,本文只是对跳表的核心功能:创建节点与跳表、插入节点、删除节点、获取节点rank、根据rank获取节点、获取分数区间的ele集合进行复刻,其余的需要自己去实现。
1、跳表核心布局

源码的数据布局定义如下:
  1. #define ZSKIPLIST_MAXLEVEL 32
  2. #define ZSKIPLIST_P 0.25
  3. /* ZSETs use a specialized version of Skiplists */
  4. typedef struct zskiplistNode {
  5.     sds ele;
  6.     double score;
  7.     struct zskiplistNode *backward;
  8.     struct zskiplistLevel {
  9.         struct zskiplistNode *forward;
  10.         unsigned long span;
  11.     } level[];
  12. } zskiplistNode;
  13. typedef struct zskiplist {
  14.     struct zskiplistNode *header, *tail;
  15.     unsigned long length;
  16.     int level;
  17. } zskiplist;
复制代码
复刻:
  1. package goskiplist
  2. const (
  3.         SKIPLIST_MAXLEVEL = 32
  4.         SKIPLIST_P        = 0.25
  5. )
  6. type GskiplistLevel struct {
  7.         forward *GskiplistNode
  8.         span    uint64
  9. }
  10. type GskiplistNode struct {
  11.         ele      string
  12.         score    float64
  13.         backward *GskiplistNode
  14.         level    []GskiplistLevel
  15. }
  16. type Gskiplist struct {
  17.         header *GskiplistNode
  18.         tail   *GskiplistNode
  19.         length uint64
  20.         level int
  21. }
复制代码
2、创建跳表节点与跳表

创建跳表节点源码:
  1. zskiplistNode *zslCreateNode(int level, double score, sds ele) {
  2.     zskiplistNode *zn =
  3.         zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
  4.     zn->score = score;
  5.     zn->ele = ele;
  6.     return zn;
  7. }
复制代码
复刻:
  1. func createNode(level int, score float64, ele string) *GskiplistNode{
  2.         node := &GskiplistNode{
  3.                 ele:      ele,
  4.                 score:    score,
  5.                 level:    make([]GskiplistLevel, level),
  6.                 backward: nil,
  7.         }
  8.         return node
  9. }
复制代码
创建跳表源码:
  1. /* Create a new skiplist. */
  2. zskiplist *zslCreate(void) {
  3.     int j;
  4.     zskiplist *zsl;
  5.     zsl = zmalloc(sizeof(*zsl));
  6.     zsl->level = 1;
  7.     zsl->length = 0;
  8.     zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
  9.     for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
  10.         zsl->header->level[j].forward = NULL;
  11.         zsl->header->level[j].span = 0;
  12.     }
  13.     zsl->header->backward = NULL;
  14.     zsl->tail = NULL;
  15.     return zsl;
  16. }
复制代码
初始化设置了跳表的层数为1、节点数为0、初始化头节点指针,分配内存。注意,头节点并不计算在length中。
颠末初始化,创建的跳表如下:

3、向跳表插入节点

源码:
  1. zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
  2.     zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
  3.     unsigned long rank[ZSKIPLIST_MAXLEVEL];
  4.     int i, level;
  5.     serverAssert(!isnan(score));
  6.     x = zsl->header;
  7.     for (i = zsl->level-1; i >= 0; i--) {
  8.         /* store rank that is crossed to reach the insert position */
  9.         rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
  10.         while (x->level[i].forward &&
  11.                 (x->level[i].forward->score < score ||
  12.                     (x->level[i].forward->score == score &&
  13.                     sdscmp(x->level[i].forward->ele,ele) < 0)))
  14.         {
  15.             rank[i] += x->level[i].span;
  16.             x = x->level[i].forward;
  17.         }
  18.         update[i] = x;
  19.     }
  20.     /* we assume the element is not already inside, since we allow duplicated
  21.      * scores, reinserting the same element should never happen since the
  22.      * caller of zslInsert() should test in the hash table if the element is
  23.      * already inside or not. */
  24.     level = zslRandomLevel();
  25.     if (level > zsl->level) {
  26.         for (i = zsl->level; i < level; i++) {
  27.             rank[i] = 0;
  28.             update[i] = zsl->header;
  29.             update[i]->level[i].span = zsl->length;
  30.         }
  31.         zsl->level = level;
  32.     }
  33.     x = zslCreateNode(level,score,ele);
  34.     for (i = 0; i < level; i++) {
  35.         x->level[i].forward = update[i]->level[i].forward;
  36.         update[i]->level[i].forward = x;
  37.         /* update span covered by update[i] as x is inserted here */
  38.         x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
  39.         update[i]->level[i].span = (rank[0] - rank[i]) + 1;
  40.     }
  41.     /* increment span for untouched levels */
  42.     for (i = level; i < zsl->level; i++) {
  43.         update[i]->level[i].span++;
  44.     }
  45.     x->backward = (update[0] == zsl->header) ? NULL : update[0];
  46.     if (x->level[0].forward)
  47.         x->level[0].forward->backward = x;
  48.     else
  49.         zsl->tail = x;
  50.     zsl->length++;
  51.     return x;
  52. }
复制代码
zslInsert重要实现了向跳表中插入一个节点,节点的值为ele,分数为score。
解析:
(1)创建数组与断言检查
  1. zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
  2. unsigned long rank[ZSKIPLIST_MAXLEVEL];
  3. int i, level;
  4. serverAssert(!isnan(score));
  5. x = zsl->header;
复制代码
(2)查找插入位置
  1. for (i = zsl->level-1; i >= 0; i--) {
  2.         /* store rank that is crossed to reach the insert position */
  3.         rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
  4.         while (x->level[i].forward &&
  5.                 (x->level[i].forward->score < score ||
  6.                     (x->level[i].forward->score == score &&
  7.                     sdscmp(x->level[i].forward->ele,ele) < 0)))
  8.         {
  9.             rank[i] += x->level[i].span;
  10.             x = x->level[i].forward;
  11.         }
  12.         update[i] = x;
  13.     }
复制代码
i从当前跳表的最高层向下遍历。在每一次遍历中:
<ul>rank初始赋值上一层的结果,若为最高层则赋值0
若当<strong>前层的当前节点存在下一节点,并且分数 zsl->level) {        for (i = zsl->level; i < level; i++) {            rank = 0;            update = zsl->header;            update->level.span = zsl->length;        }        zsl->level = level;    }[/code]使用zslRandomLevel函数设定新节点的最高层数。假如这个最高层数大于目前跳表的层数,那么就需要设定新高层的rank和update。
zslRandomLevel的实现如下:
  1. level = zslRandomLevel();
  2.     if (level > zsl->level) {
  3.         for (i = zsl->level; i < level; i++) {
  4.             rank[i] = 0;
  5.             update[i] = zsl->header;
  6.             update[i]->level[i].span = zsl->length;
  7.         }
  8.         zsl->level = level;
  9.     }
复制代码
调整每一层的要插入的位置的前一个节点的指针指向,并且更新span。
假设在第i层,我们称update为pre,未更新前pre的下一个节点未next,那么因为要在pre和next之间插入新的节点,更新pre的span为pre到next的距离-cur到next的距离。更新cur的span为cur到next的距离。
第二个循环是为了更新当前节点的更高层未更新节点的span值。
颠末这一次调整,如图:

这里我画图用于形象的表示span的计算过程,它采用了前缀和的方式:

(5)更新新节点的前指针
  1. int zslRandomLevel(void) {
  2.     static const int threshold = ZSKIPLIST_P*RAND_MAX;
  3.     int level = 1;
  4.     while (random() < threshold)
  5.         level += 1;
  6.     return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
  7. }
复制代码
假如update[0]不是头节点,那么它就是x的前一个节点。假如x的后节点存在,则更新x的后节点的前指针指向x,否则x是末端节点,让tail指向它。
复刻Go源码:
  1. x = zslCreateNode(level,score,ele);
  2.     for (i = 0; i < level; i++) {
  3.         x->level[i].forward = update[i]->level[i].forward;
  4.         update[i]->level[i].forward = x;
  5.         /* update span covered by update[i] as x is inserted here */
  6.         x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
  7.         update[i]->level[i].span = (rank[0] - rank[i]) + 1;
  8.     }
  9. /* increment span for untouched levels */
  10.     for (i = level; i < zsl->level; i++) {
  11.         update[i]->level[i].span++;
  12.     }
复制代码
4、删除跳表节点
  1. x->backward = (update[0] == zsl->header) ? NULL : update[0];
  2.     if (x->level[0].forward)
  3.         x->level[0].forward->backward = x;
  4.     else
  5.         zsl->tail = x;
  6.     zsl->length++;
  7.     return x;
复制代码
先来看zslDelete:它是删除节点的最上层,update的更新方法与插入同等。接着就是删除score和ele相同的节点,其中node参数用于提供保存删除节点的作用。在Go语言的复刻中,我们可以直接返回node和是否删除成功。
再看zslDeleteNode,它是删除节点的下游详细实现,详细细节如下:

  • 逐层删除x,假如当前层有x,则需要将前一个节点的后指针指向x的后指针,然后更新前一个节点的span;否则只用更新span
  • 假如x的后节点存在,则更新后节点的backward指针,否则修改跳表的tail。
  • 假如存在高层,在删除x后为空层,要修改跳表的层数。
  • 减去一个length
Go复刻如下:
  1. // 向跳表插入一个节点,同时返回插入好的节点。
  2. // ele不能为空串,否则返回nil。
  3. func (this *Gskiplist) Insert(score float64, ele string) *GskiplistNode {
  4.         if ele == "" {
  5.                 return nil
  6.         }
  7.         update := make([]*GskiplistNode, SKIPLIST_MAXLEVEL)
  8.         rank := make([]uint64, SKIPLIST_MAXLEVEL)
  9.         var x *GskiplistNode
  10.         x = this.header
  11.         //更新update以及rank
  12.         for i := this.level - 1; i >= 0; i-- {
  13.                 rank[i] = 0
  14.                 if i != this.level-1 {
  15.                         rank[i] = rank[i+1]
  16.                 }
  17.                 for x.level[i].forward != nil &&
  18.                         (x.level[i].forward.score < score ||
  19.                                 (x.level[i].forward.score == score && x.level[i].forward.ele < ele)) {
  20.                         rank[i] += x.level[i].span
  21.                         x = x.level[i].forward
  22.                 }
  23.                 update[i] = x
  24.         }
  25.         level := this.randomLevel()
  26.         //更新最大层数
  27.         if level > this.level {
  28.                 for i := this.level; i < level; i++ {
  29.                         rank[i] = 0
  30.                         update[i] = this.header
  31.                         update[i].level[i].span = this.length
  32.                 }
  33.                 this.level = level
  34.         }
  35.         x = createNode(level, score, ele)
  36.         //插入操作
  37.         for i := 0; i < level; i++ {
  38.                 x.level[i].forward = update[i].level[i].forward
  39.                 update[i].level[i].forward = x
  40.                 //更新x和前一个节点的span
  41.                 x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
  42.                 update[i].level[i].span = (rank[0] - rank[i]) + 1
  43.         }
  44.         //更新更高层
  45.         for i := level; i < this.level; i++ {
  46.                 update[i].level[i].span++
  47.         }
  48.         //更新前节点指针指向
  49.         x.backward = nil
  50.         if update[0] != this.header {
  51.                 x.backward = update[0]
  52.         }
  53.         if x.level[0].forward != nil {
  54.                 x.level[0].forward.backward = x
  55.         } else {
  56.                 this.tail = x
  57.         }
  58.         this.length++
  59.         return x
  60. }
复制代码
5、获取节点的rank
  1. void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
  2.     int i;
  3.     for (i = 0; i < zsl->level; i++) {
  4.         if (update[i]->level[i].forward == x) {
  5.             update[i]->level[i].span += x->level[i].span - 1;
  6.             update[i]->level[i].forward = x->level[i].forward;
  7.         } else {
  8.             update[i]->level[i].span -= 1;
  9.         }
  10.     }
  11.     if (x->level[0].forward) {
  12.         x->level[0].forward->backward = x->backward;
  13.     } else {
  14.         zsl->tail = x->backward;
  15.     }
  16.     while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
  17.         zsl->level--;
  18.     zsl->length--;
  19. }
  20. int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
  21.     zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
  22.     int i;
  23.     x = zsl->header;
  24.     for (i = zsl->level-1; i >= 0; i--) {
  25.         while (x->level[i].forward &&
  26.                 (x->level[i].forward->score < score ||
  27.                     (x->level[i].forward->score == score &&
  28.                      sdscmp(x->level[i].forward->ele,ele) < 0)))
  29.         {
  30.             x = x->level[i].forward;
  31.         }
  32.         update[i] = x;
  33.     }
  34.     /* We may have multiple elements with the same score, what we need
  35.      * is to find the element with both the right score and object. */
  36.     x = x->level[0].forward;
  37.     if (x && score == x->score && sdscmp(x->ele,ele) == 0) {
  38.         zslDeleteNode(zsl, x, update);
  39.         if (!node)
  40.             zslFreeNode(x);
  41.         else
  42.             *node = x;
  43.         return 1;
  44.     }
  45.     return 0; /* not found */
  46. }
复制代码
从高层逐个寻找,找到即返回。
6、根据排名获取节点
  1. //删除节点,返回这个节点以及是否成功
  2. func (this *Gskiplist) Delete(score float64, ele string) (*GskiplistNode, bool) {
  3.         update := make([]*GskiplistNode, SKIPLIST_MAXLEVEL)
  4.         var x *GskiplistNode
  5.         x = this.header
  6.         for i := this.level - 1; i >= 0; i-- {
  7.                 for x.level[i].forward != nil &&
  8.                         (x.level[i].forward.score < score ||
  9.                                 (x.level[i].forward.score == score && x.level[i].forward.ele < ele)) {
  10.                         x = x.level[i].forward
  11.                 }
  12.                 update[i] = x
  13.         }
  14.         x = x.level[0].forward
  15.         //从底层删除
  16.         if x != nil && x.score == score && x.ele == ele {
  17.                 this.deleteNode(x, update)
  18.                 return x, true
  19.         }
  20.         //未找到对应节点
  21.         return nil, false
  22. }
  23. func (this *Gskiplist) deleteNode(x *GskiplistNode, update []*GskiplistNode) {
  24.         for i := 0; i < this.level; i++ {
  25.                 if update[i].level[i].forward == x {
  26.                         //在这一层,存在x
  27.                         update[i].level[i].span += x.level[i].span - 1
  28.                         update[i].level[i].forward = x.level[i].forward
  29.                 } else {
  30.                         //不存在则只更新span
  31.                         update[i].level[i].span--
  32.                 }
  33.         }
  34.         if x.level[0].forward != nil {
  35.                 x.level[0].forward.backward = x.backward
  36.         } else {
  37.                 this.tail = x.backward
  38.         }
  39.         //若x独占高层,需要逐个清除
  40.         for this.level > 1 && this.header.level[this.level-1].forward == nil {
  41.                 this.level--
  42.         }
  43.         this.length--
  44. }
复制代码
Go复刻:
[code]// 根据排名获取节点func (this *Gskiplist) GetElementByRank(rank uint64) *GskiplistNode {        return this.getElementByRankFromNode(this.header, this.level-1, rank)}func (this *Gskiplist) getElementByRankFromNode(startNode *GskiplistNode, startLevel int, rank uint64) *GskiplistNode {        x := startNode        var traversed uint64        for i := startLevel; i >= 0; i-- {                for x.level.forward != nil && traversed+x.level.span = 0; i-- {                for x.level.forward != nil && x.level.forward.score < low {                        x = x.level.forward                }        }        x = x.level[0].forward        for x != nil && x.score




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4