1.用散列表+链表的方式,在复杂度为O(1)里实现LRU:
LRU(Least Recently Used):维护一个固定大小的链表,如果缓存中存在当前元素,则删除并把它加入链表尾部。如果不存在,1)如果缓存已满(已达到链表最大长度),则删除头节点(最近最少用到的数据优先删除),然后把它加入尾节点;2)如果未满,则直接加入尾结点。
1.1 一个缓存系统主要包含以下三个操作:
-
往缓存中添加一个数据
-
从缓存中删除一个数据
-
在缓存中查找一个数据
如果只是使用链表,复杂度为O(n).如果将散列表和链表两种数据结合使用,可以将上面三个的复杂度都降低为O(1)
- 查找一个数据:通过散列表,足够均匀,基本上可以忽略拉链的遍历次数,复杂度为O(1)
- 删除一个数据:由于使用了了双向链表,可以通过prev节点在O(1)里找到上一个节点,复杂度为O(1),但是对于拉链,如果使用单链表,需要遍历当前链表上的节点,不过也可以基本忽略。如果拉链使用双向链表,则也可以在O(1)里完成操作
- 添加一个元素:如果元素在链表中,则先删除,然后加入尾结点,复杂度为O(1);如果不在,1)缓存已满,则先删除头结点,然后加入尾结点,复杂度为O(1) , 2)缓存未满,直接加入尾结点
2.总结
散列表支持非常高效的数据插入、删除、查找操作,但是散列表中的数据都是通过散列函数打乱之后无规律存储的。也就是说,它无法支持按照某种顺序快速地遍历数据。如果希望按照顺序遍历散列表中的数据,那我们需要将散列表中的数据copy到数组中,然后排序,再遍历。
因为散列表是动态数据结构,不停地有数据插入、删除所以当我们希望按顺序遍历散列表中的数据的时候,都需要先排序,那么效率势必会很低。为了解决这个问题,我们将散列表和链表(或者调表)结合一起使用。
3.思考
如果使用散列表+单链表的实现方式:
- 查找一个数据:通过散列表,足够均匀,基本上可以忽略拉链的遍历次数,复杂度为O(1)
- 删除一个数据:由于使用了单向链表,只能循环找到上一个节点,复杂度为O(n),但是对于拉链,如果使用单链表,需要遍历当前链表上的节点,不过也可以基本忽略。如果拉链使用双向链表,则也可以在O(1)里完成操作, 总复杂度为O(n)
- 添加一个元素:如果元素在链表中,则先删除,然后加入尾结点,即先删除头结点,然后插入到尾结点,复杂度为O(1);如果不在,1)缓存已满,则先删除头结点,然后加入尾结点,复杂度为O(1) , 2)缓存未满,直接加入尾结点