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 Cache设计一个数据结构,它支持两个操作:

   1)get(key):如果key在cache中,则返回对应的value值,否则返回-1

   2)set(key,value):如果key不在cache中,则将该(key,value)插入cache中(注意,如果cache已满,则必须把最近最久未使用的元素从cache中删除);如果key在cache中,则重置value的值。

  • 解题思路:题目让设计一个LRU Cache,即根据LRU算法设计一个缓存。在这之前需要弄清楚LRU算法的核心思想,LRU全称是Least

Recently Used,即最近最久未使用的意思。在操作系统的内存管理中,有一类很重要的算法就是内存页面置换算法(包括FIFO,LRU,LFU等几种常见页面置换算法)。事实上,Cache算法和内存页面置换算法的核心思想是一样的:都是在给定一个限定大小的空间的前提下,设计一个原则如何来更新和访问其中的元素。下面说一下LRU算法的核心思想,LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。

  而用什么数据结构来实现LRU算法呢?可能大多数人都会想到:用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。

  这种实现思路很简单,但是有什么缺陷呢?需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。

  那么有没有更好的实现办法呢?

  那就是利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。

  总结一下:根据题目的要求,LRU Cache具备的操作:

  1)set(key,value):如果key在hashmap中存在,则先重置对应的value值,然后获取对应的节点cur,将cur节点从链表删除,并移动到链表的头部;若果key在hashmap不存在,则新建一个节点,并将节点放到链表的头部。当Cache存满的时候,将链表最后一个节点删除即可。

  2)get(key):如果key在hashmap中存在,则把对应的节点放到链表头部,并返回对应的value值;如果不存在,则返回-1。

  仔细分析一下,如果在这地方利用单链表和hashmap,在set和get中,都有一个相同的操作就是将在命中的节点移到链表头部,如果按照传统的遍历办法来删除节点可以达到题目的要求么?第二,在删除链表末尾节点的时候,必须遍历链表,然后将末尾节点删除,这个能达到题目的时间要求么?

  试一下便知结果:

  第一个版本实现:

  1. #include <iostream>
  2. #include <map>
  3. #include <algorithm>
  4. using namespace std;
  5.  
  6. struct Node
  7. {
  8. int key;
  9. int value;
  10. Node *next;
  11. };
  12.  
  13. class LRUCache{
  14. private:
  15. int count;
  16. int size ;
  17. map<int,Node *> mp;
  18. Node *cacheList;
  19. public:
  20. LRUCache(int capacity) {
  21. size = capacity;
  22. cacheList = NULL;
  23. count = 0;
  24. }
  25.  
  26. int get(int key) {
  27. if(cacheList==NULL)
  28. return -1;
  29. map<int,Node *>::iterator it=mp.find(key);
  30. if(it==mp.end()) //如果在Cache中不存在该key, 则返回-1
  31. {
  32. return -1;
  33. }
  34. else
  35. {
  36. Node *p = it->second;
  37. pushFront(p); //将节点p置于链表头部
  38. }
  39. return cacheList->value;
  40. }
  41.  
  42. void set(int key, int value) {
  43. if(cacheList==NULL) //如果链表为空,直接放在链表头部
  44. {
  45. cacheList = (Node *)malloc(sizeof(Node));
  46. cacheList->key = key;
  47. cacheList->value = value;
  48. cacheList->next = NULL;
  49. mp[key] = cacheList;
  50. count++;
  51. }
  52. else //否则,在map中查找
  53. {
  54. map<int,Node *>::iterator it=mp.find(key);
  55. if(it==mp.end()) //没有命中
  56. {
  57. if(count == size) //cache满了
  58. {
  59. Node * p = cacheList;
  60. Node *pre = p;
  61. while(p->next!=NULL)
  62. {
  63. pre = p;
  64. p= p->next;
  65. }
  66. mp.erase(p->key);
  67. count--;
  68. if(pre==p) //说明只有一个节点
  69. p=NULL;
  70. else
  71. pre->next = NULL;
  72. free(p);
  73. }
  74. Node * newNode = (Node *)malloc(sizeof(Node));
  75. newNode->key = key;
  76. newNode->value = value;
  77.  
  78. newNode->next = cacheList;
  79. cacheList = newNode;
  80.  
  81. mp[key] = cacheList;
  82. count++;
  83. }
  84. else
  85. {
  86. Node *p = it->second;
  87. p->value = value;
  88. pushFront(p);
  89. }
  90. }
  91.  
  92. }
  93.  
  94. void pushFront(Node *cur) //单链表删除节点,并将节点移动链表头部,O(n)
  95. {
  96. if(count==1)
  97. return;
  98. if(cur==cacheList)
  99. return;
  100. Node *p = cacheList;
  101. while(p->next!=cur)
  102. {
  103. p=p->next;
  104. }
  105. p->next = cur->next; //删除cur节点
  106.  
  107. cur->next = cacheList;
  108. cacheList = cur;
  109. }
  110.  
  111. void printCache(){
  112.  
  113. Node *p = cacheList;
  114. while(p!=NULL)
  115. {
  116. cout<<p->key<<" ";
  117. p=p->next;
  118. }
  119. cout<<endl;
  120. }
  121. };
  122.  
  123. int main(void)
  124. {
  125. /*LRUCache cache(3);
  126. cache.set(2,10);
  127. cache.printCache();
  128. cache.set(1,11);
  129. cache.printCache();
  130. cache.set(2,12);
  131. cache.printCache();
  132. cache.set(1,13);
  133. cache.printCache();
  134. cache.set(2,14);
  135. cache.printCache();
  136. cache.set(3,15);
  137. cache.printCache();
  138. cache.set(4,100);
  139. cache.printCache();
  140. cout<<cache.get(2)<<endl;
  141. cache.printCache();*/
  142.  
  143. LRUCache cache(2);
  144. cout<<cache.get(2)<<endl;
  145. cache.set(2,6);
  146. cache.printCache();
  147. cout<<cache.get(1)<<endl;
  148. cache.set(1,5);
  149. cache.printCache();
  150. cache.set(1,2);
  151. cache.printCache();
  152. cout<<cache.get(1)<<endl;
  153. cout<<cache.get(2)<<endl;
  154. return 0;
  155. }

  提交之后,提示超时:

  因此要对算法进行改进,如果把pushFront时间复杂度改进为O(1)的话是不是就能达到要求呢?

  但是  在已知要删除的节点的情况下,如何在O(1)时间复杂度内删除节点?

  如果知道当前节点的前驱节点的话,则在O(1)时间复杂度内删除节点是很容易的。而在无法获取当前节点的前驱节点的情况下,能够实现么?对,可以实现的。

  具体的可以参照这几篇博文:

  http://www.cnblogs.com/xwdreamer/archive/2012/04/26/2472102.html

  http://www.nowamagic.net/librarys/veda/detail/261

  原理:假如要删除的节点是cur,通过cur可以知道cur节点的后继节点curNext,如果交换cur节点和curNext节点的数据域,然后删除curNext节点(curNext节点是很好删除地),此时便在O(1)时间复杂度内完成了cur节点的删除。

  改进版本1:

  1. void pushFront(Node *cur) //单链表删除节点,并将节点移动链表头部,O(1)
  2. {
  3. if(count==1)
  4. return;
  5. //先删除cur节点 ,再将cur节点移到链表头部
  6. Node *curNext = cur->next;
  7. if(curNext==NULL) //如果是最后一个节点
  8. {
  9. Node * p = cacheList;
  10. while(p->next!=cur)
  11. {
  12. p=p->next;
  13. }
  14. p->next = NULL;
  15.  
  16. cur->next = cacheList;
  17. cacheList = cur;
  18. }
  19. else //如果不是最后一个节点
  20. {
  21. cur->next = curNext->next;
  22. int tempKey = cur->key;
  23. int tempValue = cur->value;
  24.  
  25. cur->key = curNext->key;
  26. cur->value = curNext->value;
  27.  
  28. curNext->key = tempKey;
  29. curNext->value = tempValue;
  30.  
  31. curNext->next = cacheList;
  32. cacheList = curNext;
  33.  
  34. mp[curNext->key] = curNext;
  35. mp[cur->key] = cur;
  36. }
  37. }

  提交之后,提示Accepted,耗时492ms,达到要求。

  有没有更好的实现办法,使得删除末尾节点的复杂度也在O(1)?那就是利用双向链表,并提供head指针和tail指针,这样一来,所有的操作都是O(1)时间复杂度。

  改进版本2:

  1. #include <iostream>
  2. #include <map>
  3. #include <algorithm>
  4. using namespace std;
  5.  
  6. struct Node
  7. {
  8. int key;
  9. int value;
  10. Node *pre;
  11. Node *next;
  12. };
  13.  
  14. class LRUCache{
  15. private:
  16. int count;
  17. int size ;
  18. map<int,Node *> mp;
  19. Node *cacheHead;
  20. Node *cacheTail;
  21. public:
  22. LRUCache(int capacity) {
  23. size = capacity;
  24. cacheHead = NULL;
  25. cacheTail = NULL;
  26. count = 0;
  27. }
  28.  
  29. int get(int key) {
  30. if(cacheHead==NULL)
  31. return -1;
  32. map<int,Node *>::iterator it=mp.find(key);
  33. if(it==mp.end()) //如果在Cache中不存在该key, 则返回-1
  34. {
  35. return -1;
  36. }
  37. else
  38. {
  39. Node *p = it->second;
  40. pushFront(p); //将节点p置于链表头部
  41. }
  42. return cacheHead->value;
  43. }
  44.  
  45. void set(int key, int value) {
  46. if(cacheHead==NULL) //如果链表为空,直接放在链表头部
  47. {
  48. cacheHead = (Node *)malloc(sizeof(Node));
  49. cacheHead->key = key;
  50. cacheHead->value = value;
  51. cacheHead->pre = NULL;
  52. cacheHead->next = NULL;
  53. mp[key] = cacheHead;
  54. cacheTail = cacheHead;
  55. count++;
  56. }
  57. else //否则,在map中查找
  58. {
  59. map<int,Node *>::iterator it=mp.find(key);
  60. if(it==mp.end()) //没有命中
  61. {
  62. if(count == size) //cache满了
  63. {
  64. if(cacheHead==cacheTail&&cacheHead!=NULL) //只有一个节点
  65. {
  66. mp.erase(cacheHead->key);
  67. cacheHead->key = key;
  68. cacheHead->value = value;
  69. mp[key] = cacheHead;
  70. }
  71. else
  72. {
  73. Node * p =cacheTail;
  74. cacheTail->pre->next = cacheTail->next;
  75. cacheTail = cacheTail->pre;
  76.  
  77. mp.erase(p->key);
  78.  
  79. p->key= key;
  80. p->value = value;
  81.  
  82. p->next = cacheHead;
  83. p->pre = cacheHead->pre;
  84. cacheHead->pre = p;
  85. cacheHead = p;
  86. mp[cacheHead->key] = cacheHead;
  87. }
  88. }
  89. else
  90. {
  91. Node * p = (Node *)malloc(sizeof(Node));
  92. p->key = key;
  93. p->value = value;
  94.  
  95. p->next = cacheHead;
  96. p->pre = NULL;
  97. cacheHead->pre = p;
  98. cacheHead = p;
  99. mp[cacheHead->key] = cacheHead;
  100. count++;
  101. }
  102. }
  103. else
  104. {
  105. Node *p = it->second;
  106. p->value = value;
  107. pushFront(p);
  108. }
  109. }
  110.  
  111. }
  112.  
  113. void pushFront(Node *cur) //双向链表删除节点,并将节点移动链表头部,O(1)
  114. {
  115. if(count==1)
  116. return;
  117. if(cur==cacheHead)
  118. return;
  119.  
  120. if(cur==cacheTail)
  121. {
  122. cacheTail = cur->pre;
  123. }
  124.  
  125. cur->pre->next = cur->next; //删除节点
  126. if(cur->next!=NULL)
  127. cur->next->pre = cur->pre;
  128.  
  129. cur->next = cacheHead;
  130. cur->pre = NULL;
  131. cacheHead->pre = cur;
  132. cacheHead = cur;
  133. }
  134.  
  135. void printCache(){
  136.  
  137. Node *p = cacheHead;
  138. while(p!=NULL)
  139. {
  140. cout<<p->key<<" ";
  141. p=p->next;
  142. }
  143. cout<<endl;
  144. }
  145. };
  146.  
  147. int main(void)
  148. {
  149. LRUCache cache(3);
  150. cache.set(1,1);
  151. //cache.printCache();
  152.  
  153. cache.set(2,2);
  154. //cache.printCache();
  155.  
  156. cache.set(3,3);
  157. cache.printCache();
  158.  
  159. cache.set(4,4);
  160. cache.printCache();
  161.  
  162. cout<<cache.get(4)<<endl;
  163. cache.printCache();
  164.  
  165. cout<<cache.get(3)<<endl;
  166. cache.printCache();
  167. cout<<cache.get(2)<<endl;
  168. cache.printCache();
  169. cout<<cache.get(1)<<endl;
  170. cache.printCache();
  171.  
  172. cache.set(5,5);
  173. cache.printCache();
  174.  
  175. cout<<cache.get(1)<<endl;
  176. cout<<cache.get(2)<<endl;
  177. cout<<cache.get(3)<<endl;
  178. cout<<cache.get(4)<<endl;
  179. cout<<cache.get(5)<<endl;
  180.  
  181. return 0;
  182. }

  提交测试结果:

  可以发现,效率有进一步的提升。

  其实在STL中的list就是一个双向链表,如果希望代码简短点,可以用list来实现:

  具体实现:

  1. #include <iostream>
  2. #include <map>
  3. #include <algorithm>
  4. #include <list>
  5. using namespace std;
  6.  
  7. struct Node
  8. {
  9. int key;
  10. int value;
  11. };
  12.  
  13. class LRUCache{
  14. private:
  15. int maxSize ;
  16. list<Node> cacheList;
  17. map<int, list<Node>::iterator > mp;
  18. public:
  19. LRUCache(int capacity) {
  20. maxSize = capacity;
  21. }
  22.  
  23. int get(int key) {
  24. map<int, list<Node>::iterator >::iterator it = mp.find(key);
  25. if(it==mp.end()) //没有命中
  26. {
  27. return -1;
  28. }
  29. else //在cache中命中了
  30. {
  31. list<Node>::iterator listIt = mp[key];
  32. Node newNode;
  33. newNode.key = key;
  34. newNode.value = listIt->value;
  35. cacheList.erase(listIt); //先删除命中的节点
  36. cacheList.push_front(newNode); //将命中的节点放到链表头部
  37. mp[key] = cacheList.begin();
  38. }
  39. return cacheList.begin()->value;
  40. }
  41.  
  42. void set(int key, int value) {
  43. map<int, list<Node>::iterator >::iterator it = mp.find(key);
  44. if(it==mp.end()) //没有命中
  45. {
  46. if(cacheList.size()==maxSize) //cache满了
  47. {
  48. mp.erase(cacheList.back().key);
  49. cacheList.pop_back();
  50. }
  51. Node newNode;
  52. newNode.key = key;
  53. newNode.value = value;
  54. cacheList.push_front(newNode);
  55. mp[key] = cacheList.begin();
  56. }
  57. else //命中
  58. {
  59. list<Node>::iterator listIt = mp[key];
  60. cacheList.erase(listIt); //先删除命中的节点
  61. Node newNode;
  62. newNode.key = key;
  63. newNode.value = value;
  64. cacheList.push_front(newNode); //将命中的节点放到链表头部
  65. mp[key] = cacheList.begin();
  66. }
  67. }
  68. };
  69.  
  70. int main(void)
  71. {
  72. LRUCache cache(3);
  73. cache.set(1,1);
  74.  
  75. cache.set(2,2);
  76.  
  77. cache.set(3,3);
  78.  
  79. cache.set(4,4);
  80.  
  81. cout<<cache.get(4)<<endl;
  82.  
  83. cout<<cache.get(3)<<endl;
  84. cout<<cache.get(2)<<endl;
  85. cout<<cache.get(1)<<endl;
  86.  
  87. cache.set(5,5);
  88.  
  89. cout<<cache.get(1)<<endl;
  90. cout<<cache.get(2)<<endl;
  91. cout<<cache.get(3)<<endl;
  92. cout<<cache.get(4)<<endl;
  93. cout<<cache.get(5)<<endl;
  94.  
  95. return 0;
  96. }

  

LRU Cache的更多相关文章

  1. [LeetCode] LRU Cache 最近最少使用页面置换缓存器

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  2. 【leetcode】LRU Cache

    题目简述: Design and implement a data structure for Least Recently Used (LRU) cache. It should support t ...

  3. LeetCode:LRU Cache

    题目大意:设计一个用于LRU cache算法的数据结构. 题目链接.关于LRU的基本知识可参考here 分析:为了保持cache的性能,使查找,插入,删除都有较高的性能,我们使用双向链表(std::l ...

  4. LRU Cache实现

    最近在看Leveldb源码,里面用到LRU(Least Recently Used)缓存,所以自己动手来实现一下.LRU Cache通常实现方式为Hash Map + Double Linked Li ...

  5. 【leetcode】LRU Cache(hard)★

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  6. [LintCode] LRU Cache 缓存器

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  7. LRU Cache [LeetCode]

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  8. 43. Merge Sorted Array && LRU Cache

    Merge Sorted Array OJ: https://oj.leetcode.com/problems/merge-sorted-array/ Given two sorted integer ...

  9. LeetCode——LRU Cache

    Description: Design and implement a data structure for Least Recently Used (LRU) cache. It should su ...

随机推荐

  1. Cocos2d中使用颜色混合:加算,减算

    转自http://blog.sina.com.cn/s/blog_7a2ffd5c0100xtid.html CCSprite有一个ccBlendFunc类型的blendFunc_结构体成员,可以用来 ...

  2. Apple individual program 加入之后的玩法 官方资源

    Apple individual program 加入之后的玩法  官方资源   0. 开发资源文档 0.1 开发文档: https://developer.apple.com/support/res ...

  3. ECSHOP始终显示全部分类方法

    商品分类树需要始终显示所有类别,默认的Ecshop的显示方式为在当前产品页面只显示当前的产品所在的同级及下级分类,这就导致当点开某个产品或者子分 类的时候全局的分类树就不见了. 其实修改的方法很简单. ...

  4. Eclipse shortcuts

    Editor Shortcut Description Alt + / Content assist. A great help for our coding. Ctrl + Shift + F Fo ...

  5. 面向.Net程序员的后端性能优化实战

    最近2个月没做什么新项目 完全是对于旧的系统进行性能优化 避免超时 死锁 数据处理能力不够等常见的性能问题 这里不从架构方面出发 毕竟动大手脚成本比较高 那么我们以实例为前提 从细节开始 优化角度 一 ...

  6. Request is not available in this context

    部署到新服务器的IIS的时候发现这个错误: Request is not available in this context 解决方案: <system.web> <customEr ...

  7. JSP Servlet性能分析

    JSP Servlet性能分析:http://www.docin.com/p-757790851.html

  8. Hadoop学习笔记(老版本,YARN之前),MapReduce任务Namenode DataNode Jobtracker Tasktracker之间的关系

    一.基本概念 在MapReduce中,一个准备提交执行的应用程序称为“作业(job)”,而从一个作业划分出的运行于各个计算节点的工作单元称为“任务(task)”.此外,Hadoop提供的分布式文件系统 ...

  9. WinStore控件之TextBox

    1 TextBox简单实例 内容摘要:包含文本的选中,弹出什么类型的键盘,回车隐藏键盘, <Grid Name="root" Background="Transpa ...

  10. Selenium WebDriver屏幕截图(C#版)

    Selenium WebDriver屏幕截图(C#版)http://www.automationqa.com/forum.php?mod=viewthread&tid=3595&fro ...