链表

链表 

1. 定义:

链表不需要一块连续的内存空间,它通过指针将一组零散的内存块串联起来使用。其中每个内存块叫做链表的结点,记录下一个结点地址的指针叫做后继指针。

2. 单链表:

链表的插入和删除是非常快速的,因为不需要做数据搬移,但是链表想要随机访问第k个元素就没有数组那么高效了,因为链表的数据不是连续的,没有像数组那样的寻址公式,而事需要根据指针一个结点一个结点的一次遍历,直到找到对应的结点,需要O(n)的时间复杂度。

3. 循环链表:

在单链表基础上,尾结点的指针指向链表的头结点。

4. 双向链表:

每个结点不止有一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的结点。

删除和访问操作:当删除结点中"值等于某个给定值"的结点,无论单链表还是双向链表,为了查找到匹配的结点,都需要从头指针一个一个遍历,然后将其删除。尽管删除操作时间复杂度是O(1),但是遍历查找的时间复杂度是O(n)。

当删除给定指针指向的结点,我们已经找到了要删除的结点,需要找到他的前驱结点,单链表不支持获取前驱结点,所以,为了找到前驱结点,还是要从头开始获取前驱结点。但是对双向链表来说,有前驱指针,所以秩序哟O(1)的时间复杂度就可以。

除此之外,对于一个有序链表,双向链表在按值查询时,可以记录上次查找的位置,每次查找的时候,根据查找的值判断大小,然后决定往前还是往后,平均只需要查找一半的数据。

5. 注意点:

插入结点是,注意操作顺序;

删除结点时,记住手动释放内存,如果是java有虚拟机自动管理;

链表的空间换时间设计思想:在时间开发中,尽管双向链表比较耗费内存,但是也比单链表应用更加广泛,比如java中的LinkedHashMap,这里体现一种用空间换时间的思想,当我们内存空间充足的时候,如果我们更加追求代码的执行速度,就可以选用空间复杂度相对较高,但时间复杂度相对较低的算法或者数据结构;放过来也类似。

6. 链表和数组的缺点:

链表的缺点是需要耗费额外的存储空间去存储指针,而且对链表的频繁插入、删除操作,还会导致频繁的内存申请和释放,容易造成内存水平,如果是java语言,有可能导致频繁GC。

数组的缺点是是大小固定,且需要占用整块连续的内存空间,如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致out of momory(内存不足),如果声明得过小,又容易产生动态扩容发生数据搬移。

7.利用链表实现一个LRU缓存淘汰算法:

 链表使用的经典场景,LRU(least recently userd), 最近最少使用策略。

 思路:

维护一个按时间排序的有序单链表, 越靠近尾部的结点是越早被访问的,当有一个新的数据被访问时,分以下两种情况:

  • 如果数据已经在链表中,先遍历找到这个数据对应的结点,将原来数据删除,然后 把数据插入到链表的头部。
  • 如果数据不在链表中,要判断链表是否已经满了,如果未满,将数据插入到链表头部,如果已满,将链表尾结点删除,再将新的数据插入链表头部。

时间复杂度分析的话,不管缓存满不满,我们都需要遍历一遍链表,所以是O(n)的时间复杂度。实际上可以引入散列表来记录每个数据的位置,将缓存访问的时间复杂度降低到O(1)。

一些基础问题:

单链表的操作:插入,删除,查询

LUR缓存淘汰策略算法实现

posted @ 2019-09-15 20:01  vince_z  阅读(91)  评论(0编辑  收藏  举报