数据结构与算法:链表

banner


一、链表和数组的区别

链表与数组相似,但链表是一种比数组稍微复杂的数据结构。数组需要一块连续的内存空间来存储数据,对内存的要求比较高,而链表则不需要,它通过「指针」将不连续的内存块串联起来。如果要申请一个 100MB 大小的数组和链表,当内存中没有连续的,或者没有足够大小的空间时,数组便会申请失败,而链表不会。

区别

链表有很多种结构,常见的有:单链表双向链表循环链表

二、单向链表

链表通过指针将不连续的内存块串联在一起使用,我们把其中的内存块称为「结点」,而为了将所有的结点串起来,链表中的结点除了存储数据之外,还会用指针记录链表结点的下一个结点的地址,我们把这个记录下一个结点的地址的指针叫做「后继指针next」,整个单链表如下图所示。

链表

其中第一个结点和最后一个结点是比较特殊的,通常分别把它们称为「头结点」和「尾结点」。头结点用来记录链表的基地址,有了它就能遍历得到整个链表。尾结点并不指向任何结点,而是指向空地址 null ,表示链表上的最后一个结点。

与数组不同,链表的插入和删除操作并不需要大量的数据搬移,它只需要考虑相邻结点的指针改变,对应的时间复杂度为 O(1) 。同样,链表的访问元素操作也没有数组那样直接用首地址和下标通过寻址公式直接得到对应的内存地址,链表需要通过从指针一个一个结点地遍历,直到找到相应的结点。因此链表的随机访问时间复杂度为 O(n) 。

操作区别


三、循环链表

循环链表 是一种特殊的单链表,它们的区别在于单链表的尾结点指向空地址 null ,而循环链表的尾结点指向链表的头结点,像环一样收尾相连,因此才叫循环链表。

与单链表相比,当要处理的数据具有环型结构特点的问题时(如 约瑟夫问题 ),采用循环链表会简单地多,因为循环链表的优点是从链尾到链头比较方便。

循环链表


四、双向链表

双向链表 在实际的软件开发中更加常用,与单向链表相对应的,双向链表的结点除了有一个后继指针 next 指向后面的结点,还有一个前驱指针 pre 指向前一个结点,因此双向链表需要两个额外的空间来存储前驱结点和后继结点的地址,会占用更多的空间, 大专栏  数据结构与算法:链表但是它的双向遍历也给链表操作增加了更多的灵活性。

双向链表

尽管双向链表会耗费更多的内存,但在实际的开发中,双向链表还是比单链表应用更加广泛,因为在某些情况下,双向链表的操作会比单链表更加高效。比如在实际开发中,删除一个数据无非两种情况:

  • 删除结点中「值等于某个给定值」的结点
  • 删除给定指针指向的结点

对于第一种情况,不管是单向链表还是双向链表,为了找到给定的值的结点都得从头开始遍历,它们的时间复杂度为 O(n) 。而对于第二种情况就不同,第二种情况已经找到了要删除的结点,假设为结点 q ,但是要删除 q 就必须要知道它的前驱结点 p ,所以还是得从头结点重新遍历直到 p->next = q ,时间复杂度为 O(n) 。而由于双向链表具有前驱指针,便不需要像单像链表那样遍历,只用 O(1) 时间复杂度就可以完成删除操作。这种情况对于插入操作也是同理

除此之外,对于一个有序链表,双向链表的按值查询效率也比单项链表要高。因为可以记录上次查找的记录 p ,每次查询时,根据查找的值与 p 的大小关系,可以选择向前遍历或向后遍历,不用重头开始遍历,平均下来质询要查找一半的数据。

这里用到了一个空间换时间的设计思想。就是当内存空间充足的时候,为了追求代码的执行速度,就可以选择空间复杂度高,时间复杂度相对低的算法或数据结构。相反,如果内存紧缺,就要反过来,用时间换空间的设计思路。


五、基于链表实现LRU缓存算法

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

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

假设一个有序的单向链表,越靠近链表尾部的结点是越早访问的,当有一个新数据被访问时,就从表头开始遍历链表。

  1. 如果这个数据之前已经在链表中了,我们将遍历得到的结点删除,然后插入到链表的头部。
  2. 如果这个数据并没有在链表中,这时可分为两种情况
    • 缓存未满,这时可直接将数据结点插入到链表的头部
    • 缓存已满,这时则将链表的尾结点删除,再将新的数据结点插入到链表头部


六、回文字符串问题

假如一个字符串是用单链表存储的,如何判断它是否是一个回文字符串?

解答:

使用快慢两个指针,快指针每次前进两个结点,慢指针每次前进一个结点,以此慢指针找到链表中点,且慢指针在前进过程中修改结点的 next 指针,使链表前半部分反序,最后比较链表中点两侧是否相等。

七、写出正确的链表代码技巧

(暂无)

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12365344.html