Published on

跳表

Authors
  • avatar
    Name
    Ushen
    Twitter

跳表结合了数组和链表的有点,可以快速的搜索和插入删除。通过二分的方式来搜索节点,性能与红黑树不相上下。

每个节点会记录跳跃n步之后到达的节点,方便在搜索的时候进行跳跃。

设i = 0

每次跳跃2的i次方步,如果还没到,i自增,如果超过了,i变成0,重复。

这是一种倍增的方法,跳跃距离是1,2,4,8,16

假设有一个长度为16的序列,需要寻找到15,

为跳表分等级建立索引

1 9

1 5 9 14

1 3 5 7 9 12 14 16

1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17

从最高等级开始搜索,不符合的时候向下降一级 寻找路径为 9,14,15

但是实际中,维持这样一个理想的索引并不现实,因为有节点插入和删除时,维护这样一个结构需要遍历修改。

如何实现O(1)插入。

跳表做法是通过随机挑选索引,每次新增节点时有50%概率上升,最高上升到最大等级+1, 我们希望每一层的节点比他下一层少一半的节点。

参考redis跳表做法。下面是跳表的核心,插入和删除,搜索的方式也在里面能看出。

/* Insert a new node in the skiplist. Assumes the element does not already
 * exist (up to the caller to enforce that). The skiplist takes ownership
 * of the passed SDS string 'ele'. */
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
 unsigned long rank[ZSKIPLIST_MAXLEVEL];
 int i, level;

 serverAssert(!isnan(score));
    x = zsl->header;
 for (i = zsl->level-1; i >= 0; i--) {
        /* store rank that is crossed to reach the insert position */
 rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
 while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                    (x->level[i].forward->score == score &&
 sdscmp(x->level[i].forward->ele,ele) < 0)))
        {
 rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
 update[i] = x;
    }
    /* we assume the element is not already inside, since we allow duplicated
     * scores, reinserting the same element should never happen since the
     * caller of zslInsert() should test in the hash table if the element is
     * already inside or not. */
    level = zslRandomLevel();
 if (level > zsl->level) {
 for (i = zsl->level; i < level; i++) {
 rank[i] = 0;
 update[i] = zsl->header;
 update[i]->level[i].span = zsl->length;
        }
 zsl->level = level;
    }
    x = zslCreateNode(level,score,ele);
 for (i = 0; i < level; i++) {
 x->level[i].forward = update[i]->level[i].forward;
 update[i]->level[i].forward = x;

        /* update span covered by update[i] as x is inserted here */
 x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
 update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels */
 for (i = level; i < zsl->level; i++) {
 update[i]->level[i].span++;
    }

    x->backward = (update[0] == zsl->header) ? NULL : update[0];
 if (x->level[0].forward)
 x->level[0].forward->backward = x;
 else
 zsl->tail = x;
 zsl->length++;
 return x;
}

/* Internal function used by zslDelete, zslDeleteRangeByScore and
 * zslDeleteRangeByRank. */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
 int i;
 for (i = 0; i < zsl->level; i++) {
 if (update[i]->level[i].forward == x) {
 update[i]->level[i].span += x->level[i].span - 1;
 update[i]->level[i].forward = x->level[i].forward;
        } else {
 update[i]->level[i].span -= 1;
        }
    }
 if (x->level[0].forward) {
 x->level[0].forward->backward = x;
    } else {
 zsl->tail = x;
    }
 while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
 zsl->level--;
 zsl->length--;
}

/* Delete an element with matching score/element from the skiplist.
 * The function returns 1 if the node was found and deleted, otherwise
 * 0 is returned.
 *
 * If 'node' is NULL the deleted node is freed by zslFreeNode(), otherwise
 * it is not freed (but just unlinked) and *node is set to the node pointer,
 * so that it is possible for the caller to reuse the node (including the
 * referenced SDS string at node->ele). */
int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
 int i;

    x = zsl->header;
 for (i = zsl->level-1; i >= 0; i--) {
 while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                    (x->level[i].forward->score == score &&
 sdscmp(x->level[i].forward->ele,ele) < 0)))
        {
            x = x->level[i].forward;
        }
 update[i] = x;
    }
    /* We may have multiple elements with the same score, what we need
     * is to find the element with both the right score and object. */
    x = x->level[0].forward;
 if (x && score == x->score && sdscmp(x->ele,ele) == 0) {
 zslDeleteNode(zsl, x, update);
 if (!node)
 zslFreeNode(x);
 else
            *node = x;
 return 1;
    }
 return 0; /* not found */
}