数组与链表
数组需要一块连续的内存空间来存储,对内存的要求比较高,如果我们申请一个100M大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存剩余的总空间大于100M,仍然会存储失败。
链表并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以如果我们申请的是100M大小的链表,根本不会有问题。
三种最常见的链表结构
链表通过指针将一组零散的内存块串联在一起,内存块称为链表的结点,为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个节点的地址。我们把记录下一个结点地址的指针叫做后继指针。
头结点:用来记录链表的基地址。有了它,就可以遍历整条链表。
尾结点:指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点。
在链表中插入或删除一个数据,并不需要为了保持内存的连续性而搬移结点,因为链表的存储空间本身就不是连续的。所以,在链表中插入和删除数据是非常快速的。
针对链表的插入和删除操作,我们只需要考虑相邻结点的指针改变,所以对应的时间复杂度是 O(1)。
链表想要随机访问第k个元素,就没有那么高效了,需要根据指针一个结点一个结点依次遍历,直到找到相应的结点。
循环链表与单链表的区别就在于尾结点,尾结点指针指向链表的头结点。
循环链表的优点是从链尾到链头比较方便,当需要处理的数据具有环型结构特点时,特别适合采用循环链表。
双向链表,支持两个方向,每个结点不止有一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的结点。
LRU(Least Recently Used),最近最少使用策略。
维护一个有序单链表,越靠近链表尾部的结点,是越早之前被访问的,如果有一个新的数据被访问,我们从链表头开始顺序遍历链表。
1、如果此数据之前已经在缓存链表中,遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
2、如果此数据之前没有缓存在链表中,又可以分为两种情况:
-如果此时缓存未满,则直接插入链表的头部。
-如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表头部。