跳表是redis中基础数据结构之一,主要用在有序表中,与字典一起使用。
跳表在功能上主要用来快速查询一个或者一个范围内的数据。
首先看跳表的基本结构:
图片来源:https://zhuanlan.zhihu.com/p/68516038
跳表中每个节点定义有以下最基础的几个部分:
1 值,这个值可以直接是个数值,也可以是个指针,根据自己的需求定;
2 层数,如上图所示,跳表中每个节点大小并不完全一致,每个点都可以有若干层,第一层是所有节点都有的,节点中的相同层数会通过链表前后按序连接起来,这样当要查询一个节点是否在表格中时就不需要再遍历所有节点进行了;
3 指针数组,数组中记录了每个节点所在层的下一个节点位置;
4 其它,上面是必须的,像redis里边的跳表中,每个节点又有一个指向前一个节点的指针,用来快速进行逆序遍历;又如距离,redis中记录了一层中一个节点到下一个节点的距离,这样在排序时方便统计,而不用从第一层一个个进行遍历统计;
以上图中查找5为例,首先在最高层(图中是4层)查找,犹豫最高层只有1 一个节点,因此下降到第三层,从1一下跳到4又跳到6,可以确定5在4与6之间,这样4之前及6之后的节点不再查看;再下降到第二层,由于只有4与6,继续下降第一层,从4开始跳到5,查询结束。
类似这种功能二叉树也可以实现,在查询方面双方的速率都是log(n)。之所以使用跳表而不是二叉树主要有以下几点考虑(参考https://zhuanlan.zhihu.com/p/336844178?utm_source=wechat_session&utm_medium=social&utm_oi=39318521380864):
1 跳表比二叉树容易实现,二叉树为了防止退化严重,往往在插入和删除后都要进行平衡操作,而跳表不需要,跳表中每个节点的插入和删除是独立的,每个节点加入时会随机生成一个指定范围内的层数(redis中是32);在查找节点应该插入的位置时,从高到低依次遍历,得到新节点在每一层的前后节点,最后插入时,只需将这些节点前后连接即可;删除操作比较麻烦,需要遍历每一层得到节点的前节点,如果每一层是双向链表的话这一步可以直接省略;而二叉树每一步操作后还有旋转,至少插入操作双方复杂度不同;
2 跳表更节省空间,上面引用的知乎文章中有一个概率估计值,在单向链表的情况下,二叉树每个节点都有两个指针,而跳表平均只有1.33个,空间上更节省;
3 获取指定范围以及查询排名时跳表更方便,当需要获取一个范围内所有的节点值时,跳表可以迅速找到两个头尾节点,然后对中间节点进行一次按序遍历即可,而二叉树不行,二叉树在找到起始节点后需要执行中序遍历,一直到某个节点值超过范围结束,总之没有那么方便;而获取一个节点的排名时,跳表中记录了步长,因此log(n)时间之内即可获得排名值,当然如果不记录的话就得是o(n)了,但实现起来简单,而二叉树同样需要中序遍历,当然如果节点中保存了左子树有多少个节点另算;另外当逆序统计时,跳表中一般会记录总的节点数,因此会很简单,而二叉树不行,除非还是像刚说的,保存右子树有多少节点;