在redis中,有序集合zset使用了跳跃表,此外,集群节点中的内部数据结构也使用到了跳跃表
- 以空间换时间的一种优化链表算法 优化有序链表 时间复杂度:O(logN) 空间复杂度:2N
- 兼顾链表与数组优势的数据结构 索引: span的累加--->rank 作为索引
- 从内存占用上来说,skiplist比平衡树更少 AVL: 每个节点有2个指针 sl:每个节点有1/1-P=1.33 个指针 P:0.25
2.1、跳跃表的基本思想
跳跃表是有序集合(sorted-set)的底层实现,将有序链表中的部分节点分层,每一层都是一个有序链表。
- 分层,每层由有序链表构成
- 头节点在每层出现
- 某节点在上层出现,则在下层也出现
- 节点层数随机
2.2、节点与结构
跳跃表节点
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
- ele:存储字符串数据
- score:存储排序分值
- backward:后退指针,指向当前节点最底层的前一个节点
- level[]:柔性数组,随机生成1-64的值 forward:指向本层下一个节点
- span:本层下个节点到本节点的元素个数
跳跃表链表
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
- zskiplistNode *header *tail:头节点和尾节点
- length:跳跃表长度(不包括头节点)
- level:跳跃表高度
zslInsert
插入节点:
-
从最上层开始遍历节点
-
如果 i 不是 zsl->level-1 层,那么 i 层的起始 rank 值为 i+1 层的 rank 值
-
如果当前分值大于下一个分值,则累加span(比对分值,如果分值一样就比对ele)ele按字典序
-
指向本层的下一个节点
-
记录新节点的前一个节点
-
调用zslRandomLevel函数获得一个随机层数
-
如果新节点层数大于跳跃表层高,那么初始化表头节点中未使用的层,并将它们记录到 update 数组中
-
创建新节点
-
将前面记录的指针指向新节点,并做相应的设置
-
设置新节点的 forward 指针
-
将沿途记录的各个节点的 forward 指针指向新节点
-
计算新节点跨越的节点数量
-
更新新节点插入之后,沿途节点的 span 值
-
未接触的节点的 span 值也需要增一,这些节点直接从表头指向新节点
-
设置新节点的后退指针
-
跳跃表的节点计数增一
-
返回新节点
span:本次后驱节点跨越多少个第一层节点(包括跨越到的节点)
rank也叫节点索引值,是span的累加值
阶段1:查找要插入的位置
阶段2:调整层高
阶段3:生成节点并插入
阶段4:设置backward
zslGetRank
查找排位:
- 排位就是累积跨越的节点数量
- 从最上层开始遍历节点并对比元素
- 如果当前分值大于下一个分值,则累加span(比对分值,如果分值一样就比对ele)ele按字典序
- 指向本层的下一个节点
- 如果找到了(ele相同)则返回