跳跃表:是一种支持平均O(log(n)),最坏O(N)复杂度的节点查找,可以通过顺序性操作来处理节点的有序数据结构。
举例来理解一下跳跃表的情况:
跳跃表的效率可以和平衡二叉树相比较,但是实现起来却比平衡二叉树简单不少(不用旋转来,旋转去,哈哈)
跳跃表本身呢是有序链表的一种形式,但是在有序链表中选取了一些关键节点,举个例子序列为
1 2 3 4 5 6的有序链表 选取 2 4 6 作为上层的关键节点,这样是不是用空间换时间,然后就使得查找的效率提高了原来的2倍。
根据这样的思想,当链表很长有N个节点的时候我们可以从第一层提取大约N/2个节点,然后再从第二层提取大约N/4个节点。。。。。最终最上层只有2个节点这个时候查找效率变成了最高,当然这只是理想情况,redis中的实现跟着有些不同。
添加节点:
当要添加进一个节点的时候节点按照查找的顺序到最底层加入链表中,然后对于新加入的节点我们要判断是否将其提拔为关键节点(也就是是否让其上升),这个时候应用的策略是抛硬币,也就是每次节点都有百分之50的几率上升到上一层。
删除节点:
逐层查找到第一次出现节点的索引,然后逐层找到每一层的对应的节点
删除每一层查找到的节点,如果该层只剩下一个节点就删除整个一层。O(log(N))
跳跃表的用途
1. 实现有序集合键
2. 集群节点用作内部的数据结构
上面就是关于跳跃表的简单介绍,下面我们来看一看在redis中跳跃表的实现
首先介绍两个结构:zskiplist和zskiplistNode,zskiplist是用来保存跳跃表节点的相关信息的,比如节点的数量,以及指向表头节点和表尾节点的指针。
这里看图就能理解大体的zskiplist和zskiplistNode的样子了。
下面就是关于跳跃表如何实现关键节点也就是索引的选取,
typedef struct zskiplistNode{
//后退指针
struct zskiplistNode *backward;
//分值 节点的顺序按score来排列,当score的值相同的时候就按照指向的SDS的字典顺序排列
double score;
//成员对象 指向一个字符串(SDS)对象,
robj * obj;
//层 注意正是这个数据结构代表了这个节点最高可以作为第几层的索引 也正是redis中实现索引选取/的方法---->1-32中选择一个随机数作为该节点的高度 竟然是随机设置高度!
struct zskiplistLevel{
//前进指针
struct zskiplistNode *forward;
//跨度
unsigned int span;
}level[];
}zskiplistNode
层这个结构体中存储的是指向一个跳跃表节点的指针和跨度 就是相距多少个节点的距离。一个跳跃表节点中层数组的大小就是这个跳跃表节点的高度了,而Redis是通过这样的方式来选取每一层的索引,是不是很有趣。同时呢,数组的大小是一个1-32的随机数,也就是说redis中的跳跃表层数最大为32,所以最高层的索引就是32层的索引了。