内容简介:循环链表是一种特殊的单链表。与单链表唯一的区别就是在尾节点指针指向链表的头结点。优点:从链表尾到链表头比较方便。当要处理的数据具有环型结构特点时,就特别适合采用循环链表。比如著名的约瑟夫问题。循环链表与双向链表的整合
循环链表是一种特殊的单链表。与单链表唯一的区别就是在尾节点指针指向链表的头结点。
优点:从链表尾到链表头比较方便。当要处理的数据具有环型结构特点时,就特别适合采用循环链表。比如著名的约瑟夫问题。
5. 双向链表
-
与单向链表相比
缺点:如果存储同样多的数据,占用更过的内存空间。
优点:支持双向遍历,操作更灵活。
-
适用场景
在某些情况下的插入、删除等操作都要比单链表简单、高效。
删除操作有两种情况:
a. 删除节点中"值等于某个给定值"的节点
b. 删除给定指针指向的节点
对于a:
删除操作时间复杂度 ,但不管是单链表还是双向链表遍历查找的时间复杂度为 ,删除值等于给定值的节点对应的链表操作的总时间复杂度为。
对于b:
单链表:已经找到了要删除的节点,但是删除某个节点 q 需要知道其前驱节点,儿而单链表并不支持直接获取其前驱节点,所以,为了找到前驱节点,还要从头节点开始遍历链表,知道 p->next=q,说明 p 是 q 的前驱节点。所以时间复杂度为。
双向链表:因为双向链表中的节点已经保存了前驱节点的指针,不需要像单链表那样遍历。所以时间复杂度为。
插入同理。
除了插入、删除操作有优势之外,对于一个有序链表,双向链表的查询效率也比单链表高一些。因为,可以记录上次查找的位置,每次查询时,要根据要查找的值与 p 的大小关系,决定是往前还是往后查找,所以平均只需要查找一半的数据。
所以,双向链表尽管比较耗费内存,但还是比单链表更加高效。如果深入研究LinkedHashMap的实现原理,就会发现其中就用到了双向链表这种数据结构。
-
空间换时间设计思想
对于执行较慢的程序,可以通过空间换时间来进行优化;
而消耗过多内存的程序,可以通过时间换控空间来降低内存消耗。
6. 双向循环链表
循环链表与双向链表的整合
7. 链表 VS 数组性能大比拼
-
时间复杂度
-
有效缓存
数组在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,所以对CPU缓存不好(CPU每次从内存读取数据并不是只读取那个特定要访问的地址,而是读取一个数据块),没办法有效预读。
-
动态扩容
数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致"内存不足(out of memory)"。如果声明的数组过小,则可能出现不够用的情况。这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常耗时。而链表本身没有大小限制,天然地支持动态扩容。
总结
如果对内存的使用非常苛刻,那数组更适合你。因为链表中的每个节点都需要消耗额外的存储空间去存储一份指向下一个节点的指针,所以内存消耗会翻倍。而且,对链表进行频繁的插入、删除操作,还会导致频繁的内存申请和释放,容易造成内存碎片,如果是 Java 语言,就有可能会导致频繁的GC(Garbage Collection,垃圾回收)。
8. 解答开篇
如何基本链表实现LRU缓存淘汰算法?
思路:
维护一个有序单链表,越靠近链表尾部的节点是越早之前访问的,当有一个新数据被访问时,从链表头开始顺序遍历链表。
-
如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的节点,并将其从原来的位置删除,然后再插入到链表的头部。
-
如果此数据没有在缓存列表中,分为以下两种情况:
- 如果此时缓存未满,则将此节点直接插入到链表的头部;
- 如果此时缓存已满,则链表尾节点删除,将新的数据节点输入链表的头部。
缓存访问的时间复杂度为 :因为不管缓存有没有满,我们都需要遍历一遍链表。
代码请戳: 基本链表实现LRU缓存淘汰算法
实际上,可以继续优化这个实现思路,比如引入散列表(Hash table)来记录每个数据的位置,将缓存访问的时间复杂度降到 。
思考
如果字符串是通过单链表来存储的,如何判断一个字符串是否为会问字符串(比如 上海自来水来自海上),时间复杂度是多少?
方案一
1)前提:字符串以单个字符的形式存储在单链表中。
2)遍历链表,判断字符个数是否为奇数,若为偶数,则不是。
3)将链表中的字符倒序存储一份在另一个链表中。
4)同步遍历2个链表,比较对应的字符是否相等,若相等,则是水仙花字串,否则,不是。
方案二
1)遍历链表将元素放入数组A中
2)利用数组随机访问特性,分别从头和尾往中间遍历,并将元素进行比较,直到有元素不相等或者数组A的中点结束。
此方案的空间复杂度为O(n)。
方案三
1)快慢两个指针定位链表中点,同时逆序前半部分链表
2)已逆序的前半部分与后半部分进行比较
3)再次逆序前半部分链表
此方案的空间复杂度为O(1)。
以上所述就是小编给大家介绍的《算法 - 06 | 链表(上):如何实现LRU缓存淘汰算法?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 缓存的一些问题和一些加密算法【缓存问题】
- 数据结构与算法 | 如何实现LRU缓存淘汰算法
- golang实现LRU缓存淘汰算法
- 知多一点 LRU 缓存算法
- 聊聊缓存淘汰算法:LRU 实现原理
- 这几个缓存淘汰算法你知道吗?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。