No.146 LRU Cache
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.
解析:
这题其实并没有考察什么特别难的算法,主要还是考察数据结构的设计问题。
LRU算法:最近最少使用,替换在内存中,但又不使用的内存块
题意理解:key应该是物理块块号,而value为内存块号,相当于下标索引【但对set函数明显不对】
正确理解应该为:key为关键字,value为key对应的值
思考:用什么数据结构来存?
设计:【思路没有任何难度,就是使用什么数据结构的问题!!!】
越靠前,越新
若不存在且未满,头插
若不存在且已满,置最后一个无效pop_back(),头插
若存在,原来的删除,头插
最开始想到使用vector,后来想到头插的话,全部都要移动,还是用链表list比较好,有push_front;但是用list仍然是超时,最后根据网上查到的,使用双向链表和哈希表,来提高性能。
第一种提交方案【超时】:
1 #include "stdafx.h" 2 #include <list> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 /* 7 LRU算法:最近最少使用,替换在内存中,但又不使用的内存块 8 题意理解:key应该是物理块块号,而value为内存块号,相当于下标索引【貌似有问题】 9 key为关键字,value为key对应的值 10 思考:用什么数据结构来存? 11 最开始想到使用vector,后来想到头插的话,全部都要移动,还是用链表list比较好,有push_front 12 list越前越新 13 用vector: 14 若不存在且未满,头插 15 若不存在且已满,置最后一个无效pop_back(),头插 16 若存在,原来的删除,头插 17 */ 18 struct entry 19 { 20 int key; 21 int value; 22 }; 23 class LRUCache 24 { 25 public: 26 LRUCache(int capacity) 27 { 28 capa = capacity;// 29 size = 0; 30 } 31 32 int get(int key) 33 {//输入:物理块号key 34 //输出:若物理块号key在内存中,则输出其下标value(总为正);否则,输出-1 35 auto index = find_if(cache.begin(),cache.end(), [key](const struct entry &a){return a.key == key;}); 36 if(index == cache.end()) 37 return -1; 38 else 39 return (*index).value; 40 } 41 42 void set(int key, int value) 43 {//输入:物理块号key, 44 //输出:若物理块号key不在内存中,将value插入cache中。当cache达到其容量capacity,将最近最少使用的在新的插入前置为无效 45 auto index = find_if(cache.begin(),cache.end(), [key](const struct entry &a){return a.key == key;});//注意写法,尤其是要return 46 entry data; 47 data.key = key; 48 data.value = value; 49 50 if(index == cache.end())//key不存在 51 { 52 if(size < capa)//未满 53 { 54 cache.push_front(data); 55 size++; 56 } 57 else//已满,交换 58 { 59 cache.pop_back(); 60 cache.push_front(data); 61 } 62 } 63 else//key存在 64 { 65 cache.erase(index); 66 cache.push_front(data); 67 } 68 } 69 void show() 70 { 71 for(auto const &a : cache) 72 cout << a.key << " "; 73 cout << endl; 74 //for(auto const &a : cache)//测试的等同key,可以暂时不显示 75 // cout << a.value << " "; 76 cout << endl; 77 } 78 private: 79 int size;//cache中现有数据块 80 int capa;//cache容量 81 list<entry> cache; 82 }; 83 84 int main() 85 { 86 LRUCache test(3); 87 88 test.set(4, 4); 89 test.show();//4 90 91 test.set(3,3); 92 test.show();//3 4 93 94 95 test.set(4,4); 96 test.show();//4 3 97 98 test.set(2,2); 99 test.show();//2 4 3 100 101 test.set(3,3); 102 test.show();//3 2 4 103 104 test.set(1,1); 105 test.show();//1 3 2 106 107 test.set(4,4); 108 test.show();//4 1 3 109 110 test.set(2,2); 111 test.show();//2 4 1 112 113 114 return 0; 115 }
为了使查找、插入和删除都有较高的性能,我们使用一个双向链表 (std::list) 和一个哈希表(std::unordered_map),因为:
• 哈希表保存每个节点的地址,可以基本保证在 O(1) 时间内查找节点【不用map查找要遍历,复杂度太高】
• 双向链表插入和删除效率高,单向链表插入和删除时,还要查找节点的前驱节点
具体实现细节:
• 越靠近链表头部,表示节点上次访问距离现在时间最短,尾部的节点表示最近访问最少
• 访问节点时,如果节点存在,把该节点交换到链表头部,同时更新 hash 表中该节点的地址
• 插入节点时,如果 cache 的 size 达到了上限 capacity,则删除尾部节点,同时要在 hash 表中删除对应的项;新节点插入链表头部
1 #include "stdafx.h"
2 #include <list>
3 #include <unordered_map>
4 #include <iostream>
5 #include <algorithm>
6 using namespace std;
7 /*
8 LRU算法:最近最少使用,替换在内存中,但又不使用的内存块
9 题意理解:key应该是物理块块号,而value为内存块号,相当于下标索引【貌似有问题】
10 key为关键字,value为key对应的值
11 思考:用什么数据结构来存?
12 最开始想到使用vector,后来想到头插的话,全部都要移动,还是用链表list比较好,有push_front
13 list越前越新
14 用vector:
15 若不存在且未满,头插
16 若不存在且已满,置最后一个无效pop_back(),头插
17 若存在,原来的删除,头插
18 */
19 class LRUCache
20 {
21 private:
22 struct CacheNode
23 {
24 int key;
25 int value;
26 CacheNode(int k, int v):key(k), value(v){}//写个构造函数,方便!!
27 };
28 public:
29 LRUCache(int capacity)
30 {
31 this->capacity = capacity;//this的使用,用指针
32 }
33
34 int get(int key)
35 {
36 if(cacheMap.find(key) == cacheMap.end())
37 return -1;
38 //把当前访问的节点移到链表头部,并且更新map中该节点的地址!!!自己没想到,访问也是要更新的
39
40 cacheList.push_front(*(cacheMap[key]));//一定要先插,后删,否则,会出现访问问题
41 cacheList.erase(cacheMap[key]);//!!!要是直接移动,就更好了
42 //cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);//【用splice()反而超时】
43 //splice()链表独有的类型;移动:将cacheMap[key]指向的元素移动到cacheList.begin()之前的位置
44 cacheMap[key] = cacheList.begin();
45 return cacheMap[key]->value;
46 }
47
48 void set(int key, int value)
49 {
50 if(cacheMap.find(key) == cacheMap.end())//不存在
51 {
52 if(cacheList.size() == capacity)//已满
53 {
54 cacheMap.erase(cacheList.back().key);
55 cacheList.pop_back();
56 }
57 //头插,且更新map
58 cacheList.push_front(CacheNode(key,value));
59 cacheMap[key] = cacheList.begin();
60 }
61 else
62 {//存在,则更新位置
63 cacheMap[key]->value = value;//!!!
64 //cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
65 cacheList.push_front(*(cacheMap[key]));
66 cacheList.erase(cacheMap[key]);//!!!
67 cacheMap[key] = cacheList.begin();
68
69 }
70 }
71
72 void show()
73 {
74 for(auto const &a : cacheList)
75 cout << a.key << " ";
76 cout << endl;
77 //for(auto const &a : cache)//测试的等同key,可以暂时不显示
78 // cout << a.value << " ";
79 cout << endl;
80 }
81
82 private:
83 list<CacheNode> cacheList;
84 unordered_map<int, list<CacheNode>::iterator> cacheMap;
85 int capacity;
86 };
87
88 int main()
89 {
90 LRUCache test(3);
91
92 test.set(4, 4);
93 test.show();//4
94
95 test.set(3,3);
96 test.show();//3 4