数据结构与算法系列课程之五:链表(上)

本文探讨了三种缓存策略:FIFO、LFU和LRU,并对比了数组与链表的数据结构特性。深入分析了单链表、循环链表和双向链表,特别是双向链表在删除操作上的优势。介绍了基于链表实现LRU缓存淘汰算法的方法,以及如何在单链表中判断回文字符串。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍链表之前,先说下三种缓存策略

1,先进先出策略 FIFO(First In, First Out)

2,最少使用策略 LFU(Least Frequently Used)

3,最近最少使用策略 LRU(Least Recently Used)

个人理解,第三种LRU是前两种策略:FIFO 和 LFU 的结合体。

数组和链表的区别:

数组在内存中是连续存储,初始化时即确定了所占空间大小(当存满扩容时,需要重新开辟更大的连续存储空间,并进行数据迁移,也成动态扩容);另外,数组的随机访问时间复杂度是O(1),数组的随机插入、删除操作,因为往往需要做大量的数据迁移,所以时间复杂度是O(n)。

链表在内存中不需要必须是连续存储,可以是分散存储,链表数据所占空间大小是动态变化的;

链表的随机访问时间复杂度是O(n);链表单纯的插入、删除操作时间复杂度是O(1)。之所以说是单纯的,因为如果不单纯的话,还需要考虑确定插入、删除位置的消耗,我个人觉得,总体来说,链表的随机插入、删除操作的时间复杂度和数组是差不多的,属一个量级。

数组简单易用,在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,因此对CPU的缓存不友好,没有办法有效预读。

 

三种常见的链表结构:

1,单链表

2,循环链表

3,双向链表

举个例子说明某些情况下,双向链表相对于单链表的优势

从链表中删除一个数据可以分为两种情况

  • 删除节点中“值等于某个给定值”的节点
  • 删除给定指针指向的节点

第一种情况:不管是单链表还是双向链表,为了查找到值等于给定值的节点,都需要从头结点开始逐一遍历对比,尽管单纯的删除操作时间复杂度为O(1),但是遍历查找是主要的耗时点,对应的时间复杂度为O(n)。根据时间复杂度分析中的加法准则,这种情况下,单链表和双向链表的总时间复杂度都是O(n)

第二种情况:这种情况,对于单链表,在删除之前,需要找到待删节点的前驱节点,这就额外需要时间复杂度为O(n)的遍历操作;而对于双向链表,由于待删节点已保存了前驱节点的指针,因此不需要像单链表那样去遍历。

 

基于链表实现LRU缓存淘汰算法:

我们维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问,我们从链表头开始顺序遍历链表:

1,如果此数据之前已经被缓存到链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。

2,如果此数据没有缓存在链表中,又可分为两种情况:

  • 如果此时缓存未满,则将此结点直接插入到链表头部;
  • 如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。

这样就实现了一个LRU缓存。

 

思考题:

如何判断一个字符串是否是回文字符串:

如果是通过单链表来存储的,那该如何来判断是一个回文串?相应的时间空间复杂度又是多少?

答:

1,快慢指针定位中间节点

    1.1  奇数情况,中点位置不需要矫正

    1.2  偶数情况,使用偶数定位中点策略,要确定是返回上中位数或下中位数

        1.2.1 如果是返回上中位数,后半部分串头取next

        1.2.2 如果是返回下中位数,后半部分串头即是当前结点位置,但前半部分串尾要删除掉当前结点

2,从中间节点对后半部分逆序,或者将前半部分逆序

3,前后半部分逐个比较,判断是否为回文

4,逆序恢复现场

时间复杂度O(n)

空间复杂度O(1)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值