跳表实现原理
介绍
跳表(Skip List) 是一种基于链表的数据结构,但引入了多级索引来加快查找速度。它克服了单纯链表查找的低效率问题,通过层次化设计,实现类似于二分查找的效果。
Redis中的 有序集合 sorted set 就是用跳表实现的。
原理
跳表查询
对于单链表来说,即使有序,想要查找数据也只能从头开始遍历,时间复杂度为
为了提高查询效率,我们使用二分查找的思想,对于有序的单链表建立一级索引,索引层的节点包含两个指针,一个是指向下一个节点,一个是指向下一级节点。
每两个节点建一个 索引:
此时比如说想要查找18这个节点,那么就在一级索引遍历,5次到14这个节点,然后向下找到18,总共7次,而如果没有索引需要10次。次数会减少,如果继续增加层级索引,效率会继续提高,相当于使用空间来换取时间了,例如:
每三个节点建一个索引:
此时我们发现想要走到18,只需要6次操作了。
时间复杂度
建立层级索引的时候,如果我们每两个节点建立一层索引,第一层是个节点,第二层是,第三层是:,第层是
最后一层索引会有两个节点,也就是初始节点和结束节点,那么,解得:,加上原来的单链表,共
在跳表查询时,每一级索引 最多需要遍历3个节点,总的查询时间复杂度为
空间复杂度
新加的节点数为:
这个和是一个等比数列,其中首项为 ,公比,各项呈现递减的规律。
根据等比数列求和公式 ,我们可以计算出总和。
在这里,,,当 时,,因此总和为:
所以新加的节点数的总和为 。也就是时间复杂度为
优化
在跳表中,如果我 们每三个节点建立一层索引,第一层将会有个节点,第二层将会有个节点,第三层将会有个节点,第层将会有个节点。
最后一层索引会有两个节点,也就是初始节点和结束节点,那么,解得:,加上原来的单链表,共有层。
在跳表查询时,每一级索引 最多需要遍 历3个节点,因此总的查询时间复杂度为。
新加的节点数为:。
这个和是一个等比数列,其中首项为 ,公比,各项呈现递减的规律。
根据等比数列求和公式 ,我们可以计算出总和。
在这里,,,当 时,,因此总和为:
所以新加的节点数的总和为 。也就是时间复杂度为,空间复杂度为。
跳表插入
为保证索引的有序性,可以先使用多级索引找到对应的位置,然后进行插入,再更新索引。
查询的时间复杂度为,插入的时间复杂度为
跳表删除
删除也是一样,先查询,然后删除,最后更新索引
索引更新
在跳表中,频繁的删除和插入操作如果不及时更新索引,可能导致索引结构退化为单链表。这种退化会显著降低查询效率,因此为了保持高效的查询性能,我们需要动态更新索引,以实现类似于红黑树的平衡状态。跳表通过引入随机函数来决定每个节点的层级。
当向跳表中插入数据时,我们选择同时将这个数据插入到部分索引层中。 如何决定插入到哪些索引层中呢? 通过一个随机函数来决定,比如通过 随机函数得到某个值 K, 那么就将这个节点添加到第一级到第K级索引中。
为什么Redis的Zset使用跳表而不是红黑树
-
实现简单:红黑树的插入、删除操作需要复杂的旋转和重平衡操作,以保持其平衡性。而跳表通过随机化的层次结构,只需要修改相邻节点,无需复杂的平衡操作,大大简化了实现。
-
范围查询:跳表本质是多级索引链表,跳表的结构非常适合范围查询,红黑树需要依赖中序遍历
-
内存更优:平衡树每个节点两个指针,跳表为