算法笔记-链表1

版权声明:本文为博主原创文章,随意转载,转载请注明出处! https://blog.csdn.net/penglei123456/article/details/84888698

数据结构之链表

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

是什么

什么是链表
  • 数据结构上:链表也是一种线性表数据结构。
  • 内存结构上:链表不需要连续的内存空间,通过指针将零散的内存块串联起来,构成链表的存储空间。
  • 链表中每一块内存区域又叫节点,节点中保存有基础数据,以及指向下一个节点地址的后继指针。

为什么

为什么有链表
  • 对于插入和删除操作,链表的时间复杂度都为 O ( 1 ) O(1) ,对于访问某个节点,时间复杂度为 O ( n ) O(n)
  • 由于链表利用的是零散的内存空间,所以堆内存空间的利用率比数组更高。

怎么办

怎么学习链表
  • 先认识常用的几种链表:单链表,循环链表,双向链表
  • 其次学习基于链表实现的LRU缓存淘汰算法
单链表
  • 每个节点包含一个数据域,存储数据,一个指针域,指针域存储下一节点的内存地址。
  • 单链表中的两个特殊节点:头结点,记录链表的基地址,有了它就能让我们遍历整个链表的节点。尾节点,链表的最后一个节点,它的指针域存储的并不是下一个节点地址,而是存储的 null 值。
  • 单链表中的插入删除操作时间复杂度就是 O ( 1 ) O(1) 。如果删除某个节点,只需将该节点的前一个节点的指针域指向该节点后续节点的内存地址,该节点的指针域指向 null 就完成了删除。如果在某个位置添加节点,只需将新节点的指针域指向原位置上节点的后续节点的内存地址,原位置上节点的指针域指向新节点的内存地址就可以了。但是链表访问第 N 个节点的时候就需要遍历整个链表,时间复杂度就是 O ( n ) O(n) 了。
循环链表
  • 循环链表顾名思义就是首尾相接,循环往复的链表。在循环列表中,尾节点的指针域不在存储 null 了,而是存储头结点的内存地址。
  • 当要处理的数据具有环形结构的时候,最适合用循环链表来解决,比如约瑟夫问题。
双向链表
  • 在双向链表中,每个节点包含一个前驱指针域,数据域,后驱指针域。前驱指针域存储前驱节点的内存地址,后驱指针域存储后驱节点的内存地址。
  • 由于双向链表任一节点查找前驱节点和后驱节点的时间复杂度为 O ( 1 ) O(1) ,所以它在插入删除操作上比单链表更加效率。
  • 对比单链表,插入和删除操作分为以下两种情况:以删除为例,给定数值,删除值等于该数值的节点;给定节点内存地址,删除链表中的该节点。删除给定值的时候,两种链表删除操作时间复杂度为 O ( 1 ) O(1) ,但是找到该节点的时间复杂度都是 O ( n ) O(n) ,所以最终时间复杂度为 O ( 1 ) O(1) 。如果是删除给定地址的节点,单链表中,仍然需要遍历链表,找到该节点的前一个节点,才能完成删除,遍历的时间复杂度为 O ( n ) O(n) ,最终的时间复杂度还是 O ( n ) O(n) 。但是对于双向链表来说,通过给定节点可以直接查到它的前一个节点,时间复杂度为 O ( 1 ) O(1) ,所以最终的时间复杂度为 O ( 1 ) O(1)
  • 在一个有序的单链表和双向链表中,查找到某一个值得节点,双向链表的效率相比更高。因为我们可以记录上一次查找的节点 node ,下次查找时,对比一下 node 的值,我们就知道该往链表的前面还是后面查找,平均每次只会查找一半的数据。
  • 双向链表牺牲了空间,换取了时间上的效率。在java语言中, LinkedHashMap 底层就是用的双向链表实现的。
数组与链表
  • 数组与链表同样都是线性数据结构,但是存储占用的内存空间确实不一样的。数组占据连续的内存空间,如果申请一个 10M 的数组存储空间,内存中的剩余空间大于 10M ,但是没有连续的 10M 内存空间,数组申请依然会失败。链表占用的确是内存中零散的内存空间,内存利用率上更高。
  • 数组的存储结构决定了数组随机访问其中的元素时,时间复杂度为 O ( 1 ) O(1) ,但是插入删除就是 O ( n ) O(n) ,而链表正好相反,随机访问元素的时间复杂度为 O ( n ) O(n) ,给定地址插入删除的时间复杂度为 O ( 1 ) O(1) 。另外,链表支持动态扩容,虽然一些数组的容器也提供了动态扩容,但是数组足够大的时候,再次扩容效率是很低的。
  • 对链表频繁的删除插入会导致频繁的内存申请与释放,容易造成内存碎片,对于java语言来说,更会导致频繁地GC。
基于链表实现的LRU缓存淘汰算法
  • 我们维护一个有序的链表,越靠近尾部的节点,说明是越早被访问过的。当有新数据被访问时,遍历链表,找到值相同的节点,将其从链表中删除,并在头结点后面插入该值相同的节点。如果链表中没有存储该数据的节点:1,链表尚未存满,则创建新的节点,存储该数据,节点插入到链表的头结点后面。2.链表已经存满,则删除链表的最后一个节点,创建新的节点,存储该数据,节点插入到链表的头结点后面。这就实现了一个LRU缓存淘汰算法。
  • 常见的三种缓存策略:先进先出策略 FIFO ,最少使用策略 LFU ,最近最少使用策略 LRU 。

总结

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

猜你喜欢

转载自blog.csdn.net/penglei123456/article/details/84888698