是什么让普通的链表也能达到二分查找的效率,你知道吗?

跳表是一种数据结构,用于优化链表的查询效率。通过建立多层索引,使查找、插入、删除操作的平均时间复杂度达到O(logn)。文章详细介绍了跳表的工作原理、时间空间复杂度分析,并通过实例展示了跳表的动态更新和代码实现,指出其在Redis等场景中的应用。
摘要由CSDN通过智能技术生成

继承,是幸福的延续;重载,是幸福的重生。

数组与链表

数组

在计算机科学中,数组数据结构(英语:array data structure),简称数组(英语:Array),是由相同类型的元素(element)的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引(index)可以计算出该元素对应的存储地址。

优点

  • 随机访问速度较快(基于下标访问)。
  • 实现简单,使用简单。
  • 内存地址连续,对cpu缓存很友好,比如高性能队列 disruptor 也是利用了cpu缓存+数组地址的连续性大大的优化了性能。

缺点

  • 内存连续可以是优点,也可以是缺点,如果在内存紧张的情况下,数组将会被大大限制。
  • 插入和删除的时候会导致元素的移动(数据拷贝),较慢。
  • 数组大小固定,大大的限制了元素的个数,对于很多动态的数据不友好。

链表

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间。

优点

  • 链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
  • 删除插入不用移动其他元素。
  • 不受元素大小限制,可以随意扩展。

缺点

  • 失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
  • 随机访问效率相对数组来说较低。

时间复杂度分析

  • 随机访问
    数组:O(1)
    不支持随机访问

  • 插入,删除
    数组:有序数组 ->O(n),无序数组 ->(1)
    链表:O(1)

    链表又可以分为单链表和双向链表,不过这都不是这篇文章需要讨论的问题,这里讲一下数组和链表,只是做一下铺垫,因为下面讲到的跳表就会用到链表的相关知识,所以对链表还不怎么熟悉的同学,可以先学习一下,然后在开始今天的正文:跳表。

什么是跳表?

上文讲到了链表的概念,链表的查询时间复杂度为:O(n),即使是有序的链表,也得从链表的第一个元素开始遍历查找,时间复杂度偏高,那如果让你来优化,你有什么解决方案吗?这个时候跳表就出现了。

跳表,全程:跳跃链表,在计算机科学中,跳跃列表是一种数据结构。它使得包含n个元素的有序序列的查找和插入操作的平均时间复杂度都是:
l o g n logn logn
我们来画一个普通的链表结构
普通链表

这是一个普通的单链表结构,如果我们想查找出 58 这个节点,我们就得查询 1 -> 4 -> 7 -> 15 -> 20 -> 35 -> 50 -> 58 ,一共查找了8次,才将我们需要的结果查找出来,时间复杂度:O(n),效率可想而知,那我们一起试着来优化一下。

mysql在数据量大的时候查询效率也会急剧下降,他们是怎么来优化效率问题呢?那就是索引,这里我们也可以使用一个类似 “索引” 的东西来对这个普通的单链表进行优化。
file
我们将原始链表中每隔两个结点之后的节点复制一份出来,用于制作索引,方便数据的查找。

我们还是继续查找58,我们只需要经过:1 -> 7 -> 20 --> 50 -> 58,发现了没有,这里居然只搜索了5次就找到了我们想要的结点,效率是不是提升了一丢丢?我现在来解释一下这张图,由于我们建立了一层索引,所以查询的时候优先往第一层索引层搜索,搜索索引层时,发现 50 以及之前的节点都小于 58 ,遍历到 50 的时候,他的下一个节点 66 大于需要查询的节点 ,所以这个时候就会找到50这个节点中的down(原始链表节点为 50 的指针)节点,然后再往下遍历一个就就找到了 58 。

加了“索引” 也才少查询了三次,优化的并不是很明显啊,这个优化是否有存在的必要呢?我们这里之建立了一层“索引”,我们多建立几层”索引“之后呢?会是什么样呢?
file
这个时候我们发现只需要4次就能找到我们需要查找的节点 58 ,4 次相对于8次来说已经快了一半了,我这里的数据不是太多,当数据足够多的时候,效果将会更为明显。

这种一层一层的索引组成的数据结构,我们称他为:跳表,现在相信你对什么是跳表应该有了比较深的理解了吧,这个时候你可能又有一个疑问了?我们为了建立索引层,肯定会消耗大量的空间,这是属于空间换时间的一种方式,但是在数据量大的时候,会不会存在空间不够的情况呢?

那我们现在一起来分析一下跳表的时间/空间复杂度吧

跳表分析

假设原始链表结点个数为:n,我们知道,我们每隔两个节点抽离一个结点作为索引层的一个结点,那么第一层索引层的结点个数应该是原始链表结点个数的1/2,也就是n/2,第二层索引的结点个数是第一层索引结点个数的1/2,是 1/4 原始链表结点个数,也就是 n/4,第三层索引的结点个数是第二层结点个数的1/2,是第一层索引结点个数的4/1,是原始链表结点个数的1/8,依次类推,在k层索引的节点个数是k-1层索引的1/2,那么k层索引的节点个数
n 2 k \frac {n}{2^k} 2kn
一个链表,假设我们都是每隔两个结点分配一个索引结点,最高层索引只有两个结点,索引层高度为:h,那么将会得到这么一个公式:
n 2 h = 2 \frac {n}{2^h}=2 2hn=2
我们求解一下 h:
2 h = n 2 2^h=\frac {n}{2} 2h=2n

h = l o g 2 ( n 2 ) h=log_2(\frac{n}{2}) h=log2(2n

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值