算法笔记-跳表

为什么 Redis 要用跳表来实现

什么是跳表

二分查找中,底层数据结构是数组。如果底层数据结构换成链表,使其支持二分查找,那么就城这种数据结构为跳表。

跳表是一种各方面性能都比较优秀的动态数据结构。它支持快速的查找,插入以及删除操作,实现起来也不复杂,甚至可以替代红黑树。

理解跳表

原始链表{1,3,4,5,7,8,9,10,13,16,17,18}
一级索引{1,4,7,9,13,17}
二级索引{1,7,13}

为了快速查找,对链表建立索引。首先对当前层级,每两个元素抽出一个,作为上级数据(索引层)。例如,对原始数据集合建立索引的索引就是一级索引。

在一级索引上查找元素 16,遍历一级索引到元素 13 的位置,发现元素 16 在区间 13 到 17 之间。然后跳转到原始链表,从元素 13 这个节点开始往后找,最终找到元素 16 的节点。在这个过程中,一共遍历节点 6 个。如果没有索引,那么在原始链表中找到元素 16 需要遍历10 个节点。

如果在一级索引上再次创建索引,每两个元素提取一个,组成下一级索引层就是二级索引。在二级索引上查找元素 16,遍历一级索引到元素 13 的位置,然后跳转到一级索引,发现元素 16 在区间 13 到 17 之间。然后跳转到原始链表,从元素 13 这个节点开始往后找,最终找到元素 16 的节点。在这个过程中,一共遍历节点 4 个。如果没有索引,那么在原始链表中找到元素 16 需要遍历10 个节点。

上述例子中,数据量比较小,但是也足以让我们看出来构建过索引之后,再查找某个元素,效率已经提升了。这种链表加多级索引的结构,就是跳表。

跳表查询时间复杂度分析

根据刚才的距离,可以发现没增加一级索引,该索引层的节点个数就是上一层及的二分之一。所以可以得出每层索引元素的个数:n/2,n/4,n/8…2/(n^k),其中 n 为初始数据元素个数,k 为索引层级。

假设索引层级为 h ,最高索引有两个节点,那么 n/(2^h) = 2,h = log2|n - 1。在跳表查询某个数据时,每一层要遍历 m 个节点,那么查询到某个数据,时间复杂度就是 O(m*logn)。

那么 m 值等于多少呢?根据刚才构建索引的数据结构来看,当指针节点从上一层索引转移到下一层索引的时候,上层索引区间对应的下层索引数据,只有三个元素,所以 m = 3。例如上面跳表,查找值为 8 的元素。在二级索引中,指针在 7 的位置需要跳转到一级索引查找,但是我们已经明确要查找的元素就在 7~13 的区间,指针跳转到一级索引的时候,7~13 这个区间只有三个元素,当指针跳转到初始数据的时候,7~9 这个区间也只有三个元素。所以,类推出结论:按照每两个元素提取一个构建的索引,每一次最多遍历三个元素,所以 m = 3,最终的时间复杂度就是 O(*logn)。

跳表的内存占用

上述描述中,我们已经推导出每一层索引节点的个数,那么索引层总共创建的节点个数是:2/n + 4/n + … + 4 + 2 = n - 2。所以,跳表的空间复杂度为 O(n)。虽然时间复杂度为 O(n),但是实际开发工程中,我们构建索引的时候,并不会把整个对象拿出来构建索引,而是利用对象的个别属性的值创建索引。所以,跟整个对象占用的内存来比,依据属性值创建的索引,占据的空间就很小了。

跳表的查找删除操作时间复杂度

首先,我们要找到要插入或者要删除的节点的位置,时间复杂度已经是 O(logn)。对于双向链表来说,插入删除操作时间复杂度是 O(1),所以双向链表实现的跳表数据结构,插入删除操作的时间复杂度也是O(logn)。

跳表索引的动态更新

关于这一点,我理解的还是不太透彻。王争老师阐述的是利用一个随机函数,要插入一个节点 6,如果随机函数生成值 k ,那么就在一级到 k 级索引中全部插入节点 6 的索引。从概率上讲,最终生成的索引也将是均匀分布的。

为什么 Redis 要用跳表来实现

Redis 的有序集合是通过跳表来实现的,严格的讲,还用到了散列表。这就如同开发语言中的排序方法,针对不同的数据规模,会有不同的排序实现。

Redis 有序集合的核心操作:插入,删除,查找,按区间查找,迭代输出有序序列。按照区间查找,红黑树实现的效率要低于跳表。

跳表相对于红黑树代码更容易实现,容易让人理解。跳表更加灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。

多数编程语言的 Map 就是基于红黑树实现的,使用起来更加方便。而跳表没有现成的实现,需要自己开发。

总结

本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。

初入算法学习,必是步履蹒跚,一路磕磕绊绊跌跌撞撞。看不懂别慌,也别忙着总结,先读五遍文章先,无他,唯手熟尔~
与诸君共勉

-------------本文结束感谢您的阅读-------------