Leetcode: LRU Cache

本文介绍了一种高效实现LRU缓存的方法,利用哈希表结合双向链表的数据结构,确保get和set操作均可在O(1)时间内完成。通过对节点的操作,使常用项保持在链表前端,空间复杂度适中。
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

难度:90。这是一道非常综合的题目,主要应用在操作系统的资源管理中。

按照题目要求,要实现get和set功能,为了满足get功能:随机访问的需求(lookup)我们首先想到的一般是用数组,如果用链表会有O(n)的访问时间。然而他又有另一个条件就是要维护least used的队列,也就是说经常用的放在前面,用的少的放在后面。这样当资源超过cache的容积的时候就可以把用得最少的资源删掉。这就要求我们要对节点有好的删除和插入操作,这个要求又会让我们想到用链表,其删除插入是O(1),而数组的删除和插入是O(n)复杂度的。

lookup, insert operations in O(1) leads to use HashMap.

Hashtable也是一个不错的选择,本来它的insert, delete, lookup操作amortized代价都是O(1), 但是,这里delete无法O(1). 这是因为这里困难的是得找到合乎条件的节点去删除,需要花时间在search这个Least Recently Used节点。想过用一种方法,if we use a time stamp on each entry, deleting the one with oldest access time stamp, But it will take O(n) time cost. 所以,如果只用HashMap, 删除最老的节点需要O(n).

那么我们能不能维护一个数据结构使得访问操作和插入删除操作都是O(1)复杂度的呢?答案是肯定的。这个数据结构比较复杂,是用一个hash表加上一个双向链表来实现。

这里为什么使用Doubly linkd list 而不使用 Linked list的原因是:

当访问一个节点后,我们需要将他从原来的list中删除,然后将它插入到头节点处,删除过程中需要将其前后节点连接起来,单链表做不到。

查询:

  • 根据键值查询hashmap,若命中,则返回节点,否则返回null。
  • 从双向链表中删除命中的节点,将其重新插入到表头。
  • 所有操作的复杂度均为O(1)。

插入:

  • 将新的节点关联到Hashmap
  • 如果Cache满了,删除双向链表的尾节点,同时删除Hashmap对应的记录
  • 将新的节点插入到双向链表中头部

更新:

  • 和查询相似

删除:

  • 从双向链表和Hashmap中同时删除对应的记录。

通过hash表访问结点我们可以认为是O(1)的操作(假设hash函数足够好),然后双向链表的插入删除操作也是O(1)的操作。如此我们便实现了用O(1)时间来完成所有LRU cache的操作。空间上就是对于每一个资源有一个hash表的的项以及一个对应的结点(包含前后指针和资源的<key, value>)。代码如下:

 1 public class LRUCache {
 2     class Node {
 3         Node prev, next;
 4         int key;
 5         int value;
 6         public Node(int key, int value) {
 7             this.key = key;
 8             this.value = value;
 9         }
10     }
11     
12     int capacity;
13     int num;
14     Node first, last;
15     HashMap<Integer, Node> map;
16     
17     public LRUCache(int capacity) {
18         this.capacity = capacity;
19         this.num = 0;
20         this.first = null;
21         this.last = null;
22         this.map = new HashMap<Integer, Node>();
23     }
24     
25     public int get(int key) {
26         Node n = map.get(key);
27         if (n == null) { //no find the key
28             return -1;
29         }
30         int val = n.value;
31         shiftToLast(n);
32         return val; //return the value of the key node
33     }
34     
35     public void set(int key, int value) {
36         Node n = map.get(key);
37         if (n != null) { //find the node, need to change its value and put to last
38             shiftToLast(n);
39             n.value = value; //change the value
40         }
41         else { //not find the node, need to create one, put to the last
42             Node newNode = new Node(key, value);
43 
44             if (num >= capacity) { //no capacity, need to delete first Node
45                 map.remove(first.key);
46                 first = first.next;
47                 if (first != null) first.prev = null;
48                 else last = null;
49                 num--;
50             }
51             if (num == 0) {
52                 first = newNode;
53                 last = newNode;
54             }
55             else {
56                 newNode.next = null;
57                 newNode.prev = last;
58                 last.next = newNode;
59                 last = last.next;
60             }
61             num++;
62             map.put(key, newNode);
63         }
64     }
65     
66         
67     public void shiftToLast(Node n) {
68         if (n != last) {  //find the key, and the node is not the last, need to put the node to the last of the LinkedList
69             if (n == first) {  //delete the node from its original place, when the node is the first
70                 first.next.prev = null;
71                 first = first.next;
72             }
73             else {  //delete the node from its original place, when the node is neither the first nor the last
74                 n.next.prev = n.prev;
75                 n.prev.next = n.next;
76             }
77             n.next = last.next; //put the node to the last of the Linkedlist
78             n.prev = last;
79             last.next = n;
80             last = n;
81         }
82     }
83 }

 

 关于set()函数还有另一版写法,看哪个好理解

 1     public void set(int key, int value) {
 2         if (map.containsKey(key)) { // key exist, change the value of the corresponding node
 3             CacheNode targetNode = map.get(key);
 4             if (targetNode != last) { //if target node is not the last node, put it to the last
 5                 if (targetNode != first) { //if target node is not the first node, normal operation
 6                     targetNode.prev.next = targetNode.next;
 7                     targetNode.next.prev = targetNode.prev;
 8                 }
 9                 else { // if target node is the first node, special operation
10                     targetNode.next.prev = targetNode.prev;
11                     first = targetNode.next;
12                 }
13                 targetNode.next = last.next;
14                 last.next = targetNode;
15                 targetNode.prev = last;
16                 last = last.next;
17             }
18             targetNode.val = value;
19         }
20         else { // key not exist, add the value to the LRUCache
21             CacheNode newNode = new CacheNode(key, value);
22             if (curnum < Capacity) { // LRU Capacity not reached, can add the node the LRU without deletion
23                 if (last == null) { // LRU is initially empty, newNode is the first node added
24                     last = newNode;
25                     first = newNode;
26                 }
27                 else { // LRU is not initially empty, add newNode to the last
28                     newNode.next = last.next;
29                     last.next = newNode;
30                     newNode.prev = last;
31                     last = last.next;
32                 }
33                 curnum++;
34                 map.put(key, newNode);
35             }
36             else { // curnum >= Capacity, LRU Capacity reached, deletes first node before adding newNode
37                 if (first != null) { // Capacity is not 0, it Capacity is 0, do nothing, directly go to next if()
38                     if (first == last) { // only one node in the LRU
39                         map.remove(first.key);
40                         first = null;
41                         last = null;
42                     }
43                     else { //more than one node in the LRU, delete first node
44                         first.next.prev = first.prev;
45                         map.remove(first.key);
46                         first = first.next;
47                     }
48                     curnum = Math.max(0, curnum-1);
49                 }
50                 if (curnum < Capacity) { //after deleting the firstNode, add the newNode to last.next
51                     if (last == null) { // LRU is initially empty, newNode is the first node added
52                         last = newNode;
53                         first = newNode;
54                     }
55                     else { // LRU is not initially empty, add newNode to the last
56                         newNode.next = last.next;
57                         last.next = newNode;
58                         newNode.prev = last;
59                         last = last.next;
60                     }
61                     curnum++;
62                     map.put(key, newNode);
63                 }
64             }
65         }
66     }

 

转载于:https://www.cnblogs.com/EdwardLiu/p/3984155.html

内容概要:本文系统介绍了算术优化算法(AOA)的基本原理、核心思想及Python实现方法,并通过图像分割的实际案例展示了其应用价值。AOA是一种基于种群的元启发式算法,其核心思想来源于四则运算,利用乘除运算进行全局勘探,加减运算进行局部开发,通过数学优化器加速函数(MOA)和数学优化概率(MOP)动态控制搜索过程,在全局探索与局部开发之间实现平衡。文章详细解析了算法的初始化、勘探与开发阶段的更新策略,并提供了完整的Python代码实现,结合Rastrigin函数进行测试验证。进一步地,以Flask框架搭建前后端分离系统,将AOA应用于图像分割任务,展示了其在实际工程中的可行性与高效性。最后,通过收敛速度、寻优精度等指标评估算法性能,并提出自适应参数调整、模型优化和并行计算等改进策略。; 适合人群:具备一定Python编程基础和优化算法基础知识的高校学生、科研人员及工程技术人员,尤其适合从事人工智能、图像处理、智能优化等领域的从业者;; 使用场景及目标:①理解元启发式算法的设计思想与实现机制;②掌握AOA在函数优化、图像分割等实际问题中的建模与求解方法;③学习如何将优化算法集成到Web系统中实现工程化应用;④为算法性能评估与改进提供实践参考; 阅读建议:建议读者结合代码逐行调试,深入理解算法流程中MOA与MOP的作用机制,尝试在不同测试函数上运行算法以观察性能差异,并可进一步扩展图像分割模块,引入更复杂的预处理或后处理技术以提升分割效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值