跳表(Skip List)是一种基于链表的数据结构,用于快速地插入、删除和查找元素。跳表通过多层级的指针数组来实现快速的操作,时间复杂度为O(log n),其中n为跳表中元素的个数。Redis中的有序集合(Sorted Set)就是通过跳表来实现的。
1. 跳表的底层原理
1.1 跳表的结构
跳表中的每个节点包含一个键值对,其中键用于排序元素,值用于存储具体的数据。跳表的每个节点都有多个指针,用于指向下一个节点。多个指针可以穿过一些节点,形成多层级的结构,从而实现快速的操作。
下图展示了一个包含7个元素的跳表:
level 2: +---------+---------+---------+---------+
| | | | |
| 21:d | 33:c | 50:a | 75:b |
| | | | |
level 1: +---------+---------+---------+---------+
| | | |
| 21:d | 50:a |
| | | |
level 0: +---------+---------+---------+---------+
| | | | |
| 1:b | 3:c | 4:a | 8:d |
| | | | |
跳表的每一层都是一个有序链表,其中第一层是完整的链表,包含所有元素,而其他层则包含部分元素。跳表中每个节点都包含一个键和一个值,键用于排序元素,值用于存储具体的数据。
跳表的指针数组中的指针数量是随机的,但通常是2或3个。指针数组的数量越多,节点的层数就越高,跳表的效率就越高。
跳表内存布局
1.2 跳表的操作
跳表的插入、删除和查找操作的时间复杂度都是O(log n),其中n为跳表中元素的个数。以下是跳表的常用操作:
插入操作:从头节点开始,沿着每一层的指针数组,找到待插入元素的位置,然后将元素插入到指定位置,并更新每一层的指针数组。
删除操作:从头节点开始,沿着每一层的指针数组,找到待删除元素的位置,然后将元素从指定位置删除,并更新每一层的指针数组。
查找操作:从头节点开始,沿着每一层的指针数组,找到指定元素的位置,并返回其值。
范围查找操作:从头节点开始,沿着每一层的指针数组,找到指定范围内的元素,并返回其值。
1.3 跳表的实现
跳表的实现可以分为以下几个步骤:
初始化:创建一个空的跳表,并设置头节点和尾节点。
插入操作:从头节点开始,沿着每一层的指针数组,找到待插入元素的位置,然后将元素插入到指定位置,并更新每一层的指针数组。
删除操作:从头节点开始,沿着每一层的指针数组,找到待删除元素的位置,然后将元素从指定位置删除,并更新每一层的指针数组。
查找操作:从头节点开始,沿着每一层的指针数组,找到指定元素的位置,并返回其值。
范围查找操作:从头节点开始,沿着每一层的指针数组,找到指定范围内的元素,并返回其值。
在Redis中,有序集合(Sorted Set)就是通过跳表来实现的。Redis使用跳表作为有序集合的底层数据结构,通过使用跳表,可以实现快速的插入、删除和查找操作,时间复杂度都是O(log n),其中n为有序集合中元素的个数。具体来说,Redis使用跳表来实现有序集合的存储,同时使用哈希表来实现成员和分值之间的映射关系,这样可以快速地通过成员查找到对应的分值,从而实现有序集合的排序和范围查找等操作。
跳表的核心设计要点
1.4 zskiplist的核心设计要点为:
- 头结点不持有任何数据, 且其level[]的长度为32
- 每个结点, 除了持有数据的ele字段, 还有一个字段score, 其标示着结点的得分, 结点之间凭借得分来判断先后顺序, 跳跃表中的结点按结点的得分升序排列.
- 每个结点持有一个backward指针, 这是原版跳跃表中所没有的. 该指针指向结点的前一个紧邻结点.
- 每个结点中最多持有32个zskiplistLevel结构. 实际数量在结点创建时, 按幂次定律随机生成(不超过32). 每个zskiplistLevel中有两个字段.
- forward字段指向比自己得分高的某个结点(不一定是紧邻的), 并且, 若当前zskiplistLevel实例在level[]中的索引为X, 则其forward字段指向的结点, 其level[]字段的容量至少是X+1. 这也是上图中, 为什么forward指针总是画的水平的原因.
- span字段代表forward字段指向的结点, 距离当前结点的距离. 紧邻的两个结点之间的距离定义为1.
- zskiplist中持有字段level, 用以记录所有结点(除过头结点外), level[]数组最长的长度.
总结
总之,跳表是一种基于链表的数据结构,用于快速地插入、删除和查找元素。跳表通过多层级的指针数组来实现快速的操作,时间复杂度为O(log n),其中n为跳表中元素的个数。在Redis中,有序集合就是通过跳表来实现的,通过使用跳表,可以实现快速的插入、删除和查找操作,时间复杂度都是O(log n),从而实现有序集合的存储和操作。