跳跃表
跳跃表:(skiplist) 是一种有序数据结构,通过每个节点中维持多个指向其他节点的指针,达到快速访问节点的目的。支持平均O(logN)、最坏O(N)复杂度的节点查找,还可通过顺序性操作来批量处理节点。
特点:大部分情况,可媲美平衡树的效率。实现比平衡树简单。
Redis使用跳跃表作为有序集合键的底层实现之一。当集合包含元素比较多,或者元素的成员是比较长的字符串时。
Redis中只在两个地方用到了跳跃表:一是实现有序集合键;一个是在集群节点中用作内部数据结构。
1. 跳跃表的实现
Redis的跳跃表由zskiplistNode和zskiplist两个结构定义。zskiplistNode用于表示跳跃表节点,zskiplist表示保存跳跃表节点的相关信息。节点的数量以及指向表头节点和表尾节点的指针。
zskiplist:
header: 指向跳跃表的表头节点
tail:指向跳跃表的表尾节点
level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)
length:记录跳跃表的长度,即跳跃表目前包含节点的数量(表头节点不包含在内)
zskiplistNode:
层(level):每个层带有两个属性:前进指针和跨度。
前进指针:用于访问位于表尾方向的其他节点
跨度:记录了前进指针指向节点和当前节点的距离。
后退(backward)指针:节点中使用BW字样标记节点的后退指针,指向位于当前节点的前一个节点。后退指针在程序重表尾向表头遍历时使用。
分值(score):在跳跃表中,节点按各自所保存的分值从小到大排列。如上图中,各个节点中的1.0,2.0和3.0是节点所保存的分值。
成员对象(obj):各个节点中的o1、o2和o3是节点所保存的成员对象。
**注意:**表头节点和其他节点的构造是一样的,表头节点也有后退指针、分值和成员对象,只是表头节点中的属性不会被使用到。
1.1 跳跃表节点
跳跃表节点的结构:
typedef struct zskiplistNode{ //层 struct zskiplistLevel{ //前进指针 struct zskiplistNode *forward; //跨度 unsigned int span; } level[]; //后退指针 struct zskiplistNode *backward; //分值 double score; //成员对象 robj *obj;}zskiplistNode;
1.1.1 层
level数组可以包含多个元素,元素中包含指向其他节点的指针。通过层可以加快访问其他节点的速度。层的数量越多,访问其他节点的速度就越快。
创建新跳跃表时,会根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,则为层的高度。
1.1.2 前进指针
每层都有一个指向表尾方向的前进指针(level[i].forward属性),用于从表头向表尾方向访问节点。
(1) 迭代程序先访问跳跃表的第一个节点(表头),然后从第四层的前进指针移动到表中的第二个节点。(2) 在第二个节点时,程序沿着第二层的前进指针移动到表中的第三个节点。
(3) 在第三个节点时,程序同样沿着第二层的前进指针移动到表中的第四个节点。
(4) 再次沿着第四个节点的前进指针移动,程序碰到一个NULL,则此次遍历到达表尾,结束遍历。
1.1.3 跨度
层的跨度(level[i].span)用于记录两个节点间的距离。
(1) 跨度越大,距离越远
(2) 指向NULL的所有前进指针跨度都为0,因为没有连向任何节点。
1.1.4 后退指针
backward属性,用于从表尾向表头方向访问节点。每个节点只有一个后退指针,只能退至前一个节点。如下图BW节点所示:
1.1.5 分值和成员
节点的分值(score属性)是一个double类型的浮点数,节点均是按照从小到大来排序,多个节点的分值可以相同。
节点成员对象(obj属性)是一个指针,指向一个字符串对象,此对象保存着一个SDS值。对象必须是唯一的。
**注:**当多个节点的分值相同时,将按照对象obj在字典序中的大小进行排序。较小的对象所在的节点将会排在前面(靠近表头的方向)。
1.2 跳跃表
跳跃表结构:
typedef struct zskiplist{ // 表头节点和表尾节点 struct zskiplistNode *header, *tail; // 表中节点的数量 unsigned long length; // 表中层数最大节点的层数 int level;}zskiplist;
通过使用zskiplist结构,可以更方便的对整个跳跃表进行处理,能够快速访问表头或表尾节点,能快速回去跳跃表节点的数量。
header 和 tail 指针,分别指向表头和表尾,通过这两个指针,定位表头和表尾节点的复杂度为O(1)。
length 属性记录节点的数量,可以再O(1) 复杂度内返回表长度。
level 属性用于在O(1)复杂度内获取跳跃表中层高最大的节点的层数量。表头结点的层高不计算在内。