链表 · 应用 · STL

版权声明:Hi,I m Debroon【Q:23609099,青睐互动 https://blog.csdn.net/qq_41739364/article/details/86764956

假设您可以打造一个链表,如果不清楚推荐《啊哈!算法》and 《大话数据结构》,【Code】那里我会聊经验帮您写链表代码。

那我们讲什么,讲链表的应用  Word文档 · LRU缓存淘汰算法 和  链表反转 · 约瑟夫 · 有顺合并 以及 STL链表类的基本操作。 

链表的特点

  • 链表在内存中并不是连续存储,对CPU缓存不友好,没办法预读。
  • 插入、删除数据效率高O(1)级别(只需更改指针指向即可),随机访问效率低O(n)级别(需要从链头至链尾进行遍历)。
  • 和数组相比,内存空间消耗更大,因为每个存储数据的结点都需要额外的空间存储后继指针,特别是双向链表。
  • 对链表进行频繁的插入和删除操作,会导致频繁的内存申请和释放,容易造成内存碎片,如果是Java语言,还可能会造成频繁的GC(自动垃圾回收器)操作。

常用的链表

    单链表 :> 

  •          每个结点只包含一个指针,即后继指针。
  •          单链表有两个特殊的结点,即首结点和尾结点。为什么特殊?用首结点地址表示整条链表,尾节点的后继指针指向空地址NULL。
  •           插入和删除结点的时间复杂度为O(1),查找的时间复杂度为O(n)。
  •           利用 异或^ 可实现双链表

    循环链表 :>

  •          除了尾结点的后继指针指向首结点的地址外均与单链表一致。
  •          适用于存储有循环特点的数据,比如约瑟夫问题。

    双向链表 :>

  •        结点除了存储数据外,还有两个指针分别指向前一个结点地址(前驱指针)和下一个结点地址(后继指针)。
  •        首结点的前驱指针 和尾节点 的后继指针均指向空地址
  •        和单链表相比,存储相同的数据,需要消耗更多的存储空间。
  •        插入、删除[按结点地址]操作比单链表效率更高O(1)
  •        利用 异或^ 可用单链表实现,以减少内存空间消耗。

   双向循环链表 :>

  •        首结点的前驱指针指向尾结点,尾结点的后继指针指向首结点。 

   链表的创建方式


      初学者第一种, 尾插法
      初学者第二种, 头插法
      初学者第三种, 用头结点人性化头插 [简化]

      高手的第四种, 头结点开辟整个链表, 用头结点的指针域充当当前结点的指针.
      教主的第五种, 定义链表外, 也定义管理链表的结构,比如有一个结构体管理第一个指针、最后一个指针、当前链表的大小


        那么为什么会有这么多方法呢,写自己习惯的不就好了?

 答:为考虑链表的管理,以及方方面面可能出现的问题, 提高效率。对于此,我们需要深入理解才能管理好链表。学习多种神奇写法,会渐渐拨开链表的雪山薄雾。

   链表的应用[结合链表的特点]

  •       Word 文档,每个人应该都或多或少用过,您回想一下,ta一开始都是一页,当您需要下一页时,ta 会自动开辟。
  •       WIFI 热点,连接热点的时候,ta有一层线性关系。再大型赛事直播的时候,运营商会派几量移动车,增强信号。移动车是随时进来,随时走的。如图。

缓存

缓存是一种提高数据读取性能的技术,在硬件设计、软件开发中都有着非常广泛的应用,比如常见的 CPU 缓存、数据库缓存、浏览缓存等等。  缓存是一种空间换时间的思想。如果我们把数据存储在硬盘是节省了内存,可每访问都需要跑的硬盘,是非常非常慢的,您想一想超过 6 s 才打开网页,您可以接受嘛 ~ 

缓存的大小有限,当缓存被用满时,哪些数据应该被清理出去,哪些数据应该被清理出去,哪些数据应该被保留?这就需要缓存淘汰策略来决定。常见的策略有三种:先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。

我喜欢举例子。假如,您买了一房间的小说,正在看《元尊》,可是现在您要去远足了[时长达一年]。那么您会选择带什么书去呢?想出的方法,又是不是和上面的策略神似呢,不是嘛 ^_^ 。

 LRU 缓存淘汰算法[链表实现 , 哈希优化 ]  

         基于链表实现 LRU 具体思路

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

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

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

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

基于这种思路实现的 LRU 缓存算法时间复杂度为 O(n) 因为不管缓存有没有满,我们都需要遍历一遍链表。实际生活中,网页的缓存采用哈希表,我们也可加上哈希来记录每个数据的位置,将缓存访问的时间复杂度降为 O(1)。

我们以后实现,先保留这个项目。

---------------------------------------------------------------------------------------------------------------------

下面是,一些 面试题 或者 o j 的例题与解析。

【1】 链表的反转 

  1. 使用快慢两个指针找到链表中点,慢指针每次前进一步,快指针每次前进两步。在慢指针前进的过程中,同时修改其 next 指针,使得链表前半部分反序。最后比较中点两侧的链表是否相等。[结点数是 2n 要确定是返回上中位数或下中位数]
  2.  把链表一分为二,比例是 1 : len - 1。然后以只有 1 个结点的部分为起始,从另一部分拿结点头插, 即可实现反序。

  【2】 约瑟夫环

  1.      单向循环链表模拟这个故事的过程【数组也可以,不过,单向循环链表最合适】
  2.      数学方法解

     

      【3】 俩个有序链表合并为一个有序链表

  1.     俩个链表开始俩俩比较,按升序或者降序逐个插入
  2.     把俩个链表的值,拿到数组里面,排序

------------------------------------------------------------------------------------------------------------------------

【Code】 

    任何写出正确的链表代码:
①  决心, 花一个礼拜的时间就一个链表代码
②  理解指针或者引用的含义. 保存的是变量地址

  • p->next=p->next->next。这行代码表示,p 结点的 next 指针存储了 p 结点的下下一个结点的内存地址

③  小心指针迷失和内存泄露 


④  利用哨兵简化, 带头结点不存储数据

  • 用带头结点[不存储数据的]当哨兵。

⑤  判断边界与特殊结点特殊考虑

  • 如果链表为空, 代码能否正常工作 ?
  • 如果链表只包含一个结点, 代码能否正常工作 ?
  • 如果链表只包含俩个结点, 代码能否正常工作 ?
  • 代码逻辑在处理头结点和尾结点时, 能否正常工作

⑥  举例画图, 动态思考

  • 链表插入操作顺序写反了,会指针迷途。

⑦  多写多练, 是捷径

  • 练习题LeetCode对应编号:206,141,21,19,876。

链表结课标准

  1. 实现单链表、循环链表、双向链表,支持增删操作
  2. 实现单链表反转
  3. 实现两个有序的链表合并为一个有序链表
  4. 实现求链表的中间结点
  5. 单链表异或实现双链表
  6. 链式前向星
  7. 三种结链法

猜你喜欢

转载自blog.csdn.net/qq_41739364/article/details/86764956