当前位置:   article > 正文

6、Redis系统-数据结构-06-跳表

6、Redis系统-数据结构-06-跳表

六、跳表(Skiplist)

跳表是一种高效的动态数据结构,可以用于实现有序集合(Sorted Set,Zset)。与平衡树相比,跳表具有实现简单、效率高的优点,因此被 Redis 选用作为有序集合的底层数据结构之一。

1. 跳表的结构设计

跳表通过多级链表的形式实现,每一级链表都跳过一些元素,从而在查询时能够快速跳过不必要的元素。跳表的每个节点包含一个或多个前进指针,这些前进指针指向不同级别的节点,使得跳表具有高效的查询性能。

节点结构

跳表节点的结构定义如下:

  1. typedef struct zskiplistNode {
  2.     struct zskiplistNode *backward; // 后退指针,指向前一个节点
  3.     double score;                   // 节点的分值,用于排序
  4.     sds ele;                        // 节点的元素
  5.     struct zskiplistLevel {
  6.         struct zskiplistNode *forward; // 前进指针,指向下一个节点
  7.         unsigned int span;             // 跨度,表示当前节点和下一个节点之间的距离
  8.     } level[]; // 按级别划分的前进指针数组
  9. } zskiplistNode;
跳表结构

跳表的结构定义如下:

  1. typedef struct zskiplist {
  2.     struct zskiplistNode *header, *tail; // 跳表的头节点和尾节点
  3.     unsigned long length;                // 跳表的长度,即节点数量
  4.     int level;                           // 跳表的最大级别
  5. } zskiplist;
2. 跳表的操作

跳表支持多种操作,包括插入、删除、查找等。以下是一些常见操作的实现示例:

插入操作

插入新节点时,首先确定新节点的级别,然后在每一级链表中找到插入位置,将新节点插入到相应的位置。

  1. zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
  2.     zskiplistNode *update[ZSKIPLIST_MAXLEVEL];
  3.     unsigned int rank[ZSKIPLIST_MAXLEVEL];
  4.     int i, level;
  5.     zskiplistNode *x;
  6.     x = zsl->header;
  7.     for (i = zsl->level-1; i >= 0; i--) {
  8.         rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
  9.         while (x->level[i].forward && 
  10.                (x->level[i].forward->score < score || 
  11.                (x->level[i].forward->score == score && 
  12.                 sdscmp(x->level[i].forward->ele,ele) < 0))) {
  13.             rank[i] += x->level[i].span;
  14.             x = x->level[i].forward;
  15.         }
  16.         update[i] = x;
  17.     }
  18.     level = zslRandomLevel();
  19.     if (level > zsl->level) {
  20.         for (i = zsl->level; i < level; i++) {
  21.             rank[i] = 0;
  22.             update[i] = zsl->header;
  23.             update[i]->level[i].span = zsl->length;
  24.         }
  25.         zsl->level = level;
  26.     }
  27.     x = zslCreateNode(level,score,ele);
  28.     for (i = 0; i < level; i++) {
  29.         x->level[i].forward = update[i]->level[i].forward;
  30.         update[i]->level[i].forward = x;
  31.         x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
  32.         update[i]->level[i].span = (rank[0] - rank[i]) + 1;
  33.     }
  34.     for (i = level; i < zsl->level; i++) {
  35.         update[i]->level[i].span++;
  36.     }
  37.     x->backward = (update[0] == zsl->header) ? NULL : update[0];
  38.     if (x->level[0].forward) 
  39.         x->level[0].forward->backward = x;
  40.     else
  41.         zsl->tail = x;
  42.     zsl->length++;
  43.     return x;
  44. }
查找操作

在跳表中查找目标节点时,从最高级别的链表开始,通过前进指针逐级向下查找,直到找到目标节点或确认目标节点不存在。

  1. zskiplistNode *zslFind(zskiplist *zsl, double score, sds ele) {
  2.     zskiplistNode *x;
  3.     int i;
  4.     x = zsl->header;
  5.     for (i = zsl->level-1; i >= 0; i--) {
  6.         while (x->level[i].forward && 
  7.                (x->level[i].forward->score < score || 
  8.                (x->level[i].forward->score == score && 
  9.                 sdscmp(x->level[i].forward->ele,ele) < 0))) {
  10.             x = x->level[i].forward;
  11.         }
  12.     }
  13.     x = x->level[0].forward;
  14.     if (x && score == x->score && sdscmp(x->ele,ele) == 0) {
  15.         return x;
  16.     } else {
  17.         return NULL;
  18.     }
  19. }
删除操作

删除节点时,通过查找操作确定节点位置,然后在每一级链表中移除该节点,并调整相关节点的前进指针和跨度。

  1. int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
  2.     zskiplistNode *update[ZSKIPLIST_MAXLEVEL];
  3.     zskiplistNode *x;
  4.     int i;
  5.     x = zsl->header;
  6.     for (i = zsl->level-1; i >= 0; i--) {
  7.         while (x->level[i].forward && 
  8.                (x->level[i].forward->score < score || 
  9.                (x->level[i].forward->score == score && 
  10.                 sdscmp(x->level[i].forward->ele,ele) < 0))) {
  11.             x = x->level[i].forward;
  12.         }
  13.         update[i] = x;
  14.     }
  15.     x = x->level[0].forward;
  16.     if (x && score == x->score && sdscmp(x->ele,ele) == 0) {
  17.         for (i = 0; i < zsl->level; i++) {
  18.             if (update[i]->level[i].forward == x) {
  19.                 update[i]->level[i].span += x->level[i].span - 1;
  20.                 update[i]->level[i].forward = x->level[i].forward;
  21.             } else {
  22.                 update[i]->level[i].span -= 1;
  23.             }
  24.         }
  25.         if (x->level[0].forward) {
  26.             x->level[0].forward->backward = x->backward;
  27.         } else {
  28.             zsl->tail = x->backward;
  29.         }
  30.         while (zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
  31.             zsl->level--;
  32.         zsl->length--;
  33.         if (node) *node = x;
  34.         else zslFreeNode(x);
  35.         return 1;
  36.     } else {
  37.         return 0;
  38.     }
  39. }
3. 跳表的优点
  1. 高效查询:跳表的查询性能接近于平衡树,时间复杂度为 O(log N)。
  2. 实现简单:与红黑树等平衡树相比,跳表的实现相对简单,容易理解和维护。
  3. 动态调整:跳表能够高效地进行插入和删除操作,同时保持整体结构的有序性。
4. 跳表的使用示例

以下是一些使用 Redis 跳表实现有序集合的示例,展示了如何利用跳表进行数据的存储和操作。

插入数据

  1. ZADD myzset 1 "one"
  2. ZADD myzset 2 "two"
  3. ZADD myzset 3 "three"

获取数据

  1. ZRANGE myzset 0 -1 WITHSCORES
  2. # 1) "one"
  3. # 2) "1"
  4. # 3) "two"
  5. # 4) "2"
  6. # 5) "three"
  7. # 6) "3"

删除数据

  1. ZREM myzset "two"
  2. ZRANGE myzset 0 -1 WITHSCORES
  3. # 1) "one"
  4. # 2) "1"
  5. # 3) "three"
  6. # 4) "3"
结论

通过上述解析,我们可以更好地理解跳表的设计思想和实现原理,从而在实际开发中更好地利用跳表提供的优势。在 Redis 中,跳表通过高效的多级链表结构,实现了有序集合的快速插入、删除和查询操作,适用于需要有序数据存储的场景。了解跳表的内部实现,可以帮助我们在实际应用中更好地利用 Redis 的性能和功能。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/800809
推荐阅读
相关标签
  

闽ICP备14008679号