前言
上一篇文章,我们学习了最基础的数据结构数组,这次学习一下和数组“相反”的数据结构——链表
什么是链表
上次我们说到了一类结构叫做:线性表,那么这次的主角链表也一种线性表。
从存储上说,链表是的内存结构是不连续的内存空间,是将一组零散的内存通过指针的方式串联起来,从而进行数据存储的数据结构。
链表中的每一个内存块被成为节点Node。节点除了存储数据外,还需记录下一个节点的地址,也就是大家熟知的指针next。
链表的特点
对于插入和删除操作来说,链表更加高效一些,时间复杂度为O(1),只需要改变指针的方向即可。
链表在存储方面,需要更多的内存,需要额外存储后面节点的地址。
常见的几种链表
链表五花八门,这里只说几种常见的链表
单链表
单链表中每个一个节点只包含一个指针next,之乡下一个节点的地址。
它有两个特殊的节点,分别是首节点和尾节点,都指向空地址null。
循环链表
循环链表和和单链表的区别在于循环链表的尾节点的next指针指向首节点。
循环链表适合用于存储有循环特点的问数据,比如约瑟夫问题。
双向链表
单链表支持一个方向,那么双向链表它就支持两个方向喽。每个节点不知有next指针指向后面的节点,还有一个前驱指针prev指向前面的结点。
相对于其他链表,双向链表需要更多的存储空间,需要额外的两个空间来存储后续结点和前驱结点的地址。
也正是它存储了前驱结点的地址,在删除、插入等操作中比单链表更简单、高效。
比如删除操作,分两种情况
- 删除结点中"值等于某个给定值"的结点。 这种情况下,无论是单链表还是双向链表都需要从首结点开始遍历去找到给定的结点,所有时间复杂度都是O(n)。
- 删除给定指针指向的结点 这种情况下,我们只需要之前要删除结点的前驱结点就可以进行删除操作了,这时候双向链表的优势就显现出来了,因为双向链表存储了前驱结点的地址,能够更快的找到前驱结点。双向链表的时间复杂度是O(1),单链表还是O(n).
数组和链表的区别
就时间复杂度而言
时间复杂度 | 数组 | 链表 |
---|---|---|
删除插入 | O(n) | O(1) |
随机访问 | O(1) | O(n) |
很多时候,我们不能只看复杂度分析来决定用哪一种数据结构,要根据具体的情况,具体分析。
数组简单易用,对于CPU缓存来说,访问效率高。因为数组是一块连续的空间。
数组的缺点就是大小固定,一旦使用了数组,那么它的大小就固定了,如果发现大小不能满足我们需要的时候,就需要申请更大的空间,然后把之前的数组复制过来,无形中增加了CPU的工作量。
那么对于容器来说,是不是就没有这方面的顾虑呢? 确实容器类是支持动态扩容的,但是他也是一个连续的空间,比如ArryList,当它得知当前空间无法满足需要的时候,它会自动申请1.5倍的空间。这时候就会出现这么一个情况,如果内存不足的情况下,这个1.5倍的连续空间就申请不下来了。
如果内存充足,推荐链表。如果对空间的要求苛刻,推荐数组。
实现LRU缓冲淘汰策略
链表实现LRU缓存淘汰策略
假如是用单链表进行缓存的,那么越靠近尾结点的的结点访问次数越少。 当又一个新的数据被访问时,分两种情况。
- 这个数据存在缓存链表中,先遍历找到该结点删除,然后把该数据放置在链表头部。
- 不在缓存链表中。
- 缓存未满,直接将数据插入链表头部。
- 缓存已满,删除尾结点,将数据插入链表头部。
数组实现LRU缓存淘汰策略
-
首位置保存最新访问数据,末尾位置优先清理 当访问的数据未存在于缓存的数组中时,直接将数据插入数组第一个元素位置,此时数组所有元素需要向后移动1个位置,时间复杂度为O(n);当访问的数据存在于缓存的数组中时,查找到数据并将其插入数组的第一个位置,此时亦需移动数组元素,时间复杂度为O(n)。缓存用满时,则清理掉末尾的数据,时间复杂度为O(1)。
-
首位置优先清理,末尾位置保存最新访问数据 当访问的数据未存在于缓存的数组中时,直接将数据添加进数组作为当前最有一个元素时间复杂度为O(1);当访问的数据存在于缓存的数组中时,查找到数据并将其插入当前数组最后一个元素的位置,此时亦需移动数组元素,时间复杂度为O(n)。缓存用满时,则清理掉数组首位置的元素,且剩余数组元素需整体前移一位,时间复杂度为O(n)。(优化:清理的时候可以考虑一次性清理一定数量,从而降低清理次数,提高性能。)
设计思想
当内存空间充足的时候,如果我们更加追求代码的执行速度,我们就可以选择空间复杂度比较高,时间复杂度比较低的算法和数据结构,比如缓存机制。
如果内存比较紧缺,这时候就要选择用时间换空间的思路,选择时间复杂度较高,空间复杂度比较低的算法和数据结构。
思考
如果字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?
用快慢指针先找到中点,然后把后半段链表reversed,然后一个指针在头部,一个指针再中点,开始逐个比较,时间复杂度是O(n)