LeetCode:146_LRU cache | LRU缓存设计 | Hard
2014-10-10 16:54
429 查看
题目:LRU cache
LRU是一种应用在操作系统上的缓存替换策略,和我们常见的FIFO算法一样,都是用于操作系统中内存管理中的页面替换,其全称叫做Least Recently Used(近期最少使用算法),算法主要是根据数据的历史访问记录来进行数据的淘汰,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
LRU算法设计
数据结构的选择:因为涉及到数据元素的查找,删除,替换,移动等操作,所以我们选择列表来进行数据的存储,为了考虑时间复杂度,我们分析一下,单链表插入删除操作的时间复杂度为O(n),双链表为O(1),所以,首选肯定是双链表,另外,元素的查找操作,map的查找效率为O(lgn),首选应该是map,但还有一个hashmap,能够达到O(1)的查找效率,我们后面再编程的时候都试一下这几种方法,看看其能不能通过编译,通过了时间又是多少?
为了能够比较形象的了解LRU的执行过程,我们举一个例子,如下:
假定现有一进程的页面访问序列为:
4,7,0,7,1,0,1,2,1,2,6
缓存容量为5,则随着进程的访问,缓存栈中页面号的变化情况如下图所示。在访问页面6时发生了缺页,此时页面4是最近最久未被访问的页,应将它置换出去。
在算法实现时,我们可以把最近最久没有使用的数据放在链表的最后,当缓存空间满时(即发生缺页),直接将最后一个数据淘汰即可,同时,如果一个数据发生命中,或者新来一个数据,我们都将该数据移到链表的头部,这样就能保证在链表头部的数据都是最近访问过的,而链表后面的数据就是最近最久没有访问过的。如下所示:
代码实现,为了验证上面所提出数据结构是否能通过LeetCode的编译,我们都实现一遍,下面是single list+map的实现,时间复杂度为O(n)+O(lgn),开始我还以为通过不了,最后还是通过了,耗时大约900ms。
下面是double list+map版本,时间复杂度为O(1)+O(lgn),耗时大约300s
下面是hashmap+list版本,如果是C++,list和hashmap都是STL自带的功能实现,所以,我们直接应用STL库,代码量大大减少,时间复杂度为O(1).^-^代码参考:dancingrain
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是一种应用在操作系统上的缓存替换策略,和我们常见的FIFO算法一样,都是用于操作系统中内存管理中的页面替换,其全称叫做Least Recently Used(近期最少使用算法),算法主要是根据数据的历史访问记录来进行数据的淘汰,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
LRU算法设计
数据结构的选择:因为涉及到数据元素的查找,删除,替换,移动等操作,所以我们选择列表来进行数据的存储,为了考虑时间复杂度,我们分析一下,单链表插入删除操作的时间复杂度为O(n),双链表为O(1),所以,首选肯定是双链表,另外,元素的查找操作,map的查找效率为O(lgn),首选应该是map,但还有一个hashmap,能够达到O(1)的查找效率,我们后面再编程的时候都试一下这几种方法,看看其能不能通过编译,通过了时间又是多少?
为了能够比较形象的了解LRU的执行过程,我们举一个例子,如下:
假定现有一进程的页面访问序列为:
4,7,0,7,1,0,1,2,1,2,6
缓存容量为5,则随着进程的访问,缓存栈中页面号的变化情况如下图所示。在访问页面6时发生了缺页,此时页面4是最近最久未被访问的页,应将它置换出去。
在算法实现时,我们可以把最近最久没有使用的数据放在链表的最后,当缓存空间满时(即发生缺页),直接将最后一个数据淘汰即可,同时,如果一个数据发生命中,或者新来一个数据,我们都将该数据移到链表的头部,这样就能保证在链表头部的数据都是最近访问过的,而链表后面的数据就是最近最久没有访问过的。如下所示:
代码实现,为了验证上面所提出数据结构是否能通过LeetCode的编译,我们都实现一遍,下面是single list+map的实现,时间复杂度为O(n)+O(lgn),开始我还以为通过不了,最后还是通过了,耗时大约900ms。
/************************************************************************/ /* 单链表版本 /************************************************************************/ struct Node { int m_nKey; int m_nValue; Node* m_pNext; }; class LRUCache { public: LRUCache(int capacity) { m_nSize = capacity; m_nCount = 0; m_lruList = NULL; } int get(int key) { if (NULL == m_lruList) return -1; map<int, Node *>::iterator it = m_lruMap.find(key); if (it == m_lruMap.end()) //没有找到 return -1; else { Node *p = it->second; //把节点移到链表的开头 pushFront(p); } return m_lruList->m_nValue; } void set(int key, int value) { if (NULL == m_lruList) { m_lruList = new Node(); m_lruList->m_nKey = key; m_lruList->m_nValue = value; m_lruList->m_pNext = NULL; m_nCount ++; m_lruMap[key] = m_lruList; } else { map<int, Node *>::iterator it = m_lruMap.find(key); if (it == m_lruMap.end()){ //没有命中,将链表的最后一个节点删除 if (m_nSize == m_nCount) { //cache已满 Node *pHead = m_lruList; Node *pTemp = pHead; while(pHead->m_pNext != NULL) { pTemp = pHead; pHead = pHead->m_pNext; } m_lruMap.erase(pHead->m_nKey); m_nCount --; if (pHead == pTemp) //只有一个节点 pHead = NULL; else pTemp->m_pNext = NULL; } Node *p = new Node(); //插入新的节点于头部 p->m_nKey = key; p->m_nValue = value; p->m_pNext = m_lruList; m_lruList = p; m_lruMap[key] = m_lruList; m_nCount ++; } else { //命中,则将该命中的节点移至链表头部 Node *pCur = it->second; pCur->m_nValue = value; pushFront(pCur); } } } void pushFront(Node *pCur) { //把节点移动到链表头部,时间复杂度O(n) if (NULL == pCur) return; if (m_nCount == 1 || pCur == m_lruList) return; Node *pHead = m_lruList; while (pHead->m_pNext != pCur) pHead = pHead->m_pNext; pHead->m_pNext = pCur->m_pNext; pCur->m_pNext = m_lruList; m_lruList = pCur; } void printCache() { Node *p = m_lruList; while (p) { cout << p->m_nKey << ":" << p->m_nValue << " "; p = p->m_pNext; } } private: int m_nSize; int m_nCount; map<int, Node *> m_lruMap; Node* m_lruList; };
下面是double list+map版本,时间复杂度为O(1)+O(lgn),耗时大约300s
/************************************************************************/ /* 双链表版本 /************************************************************************/ struct Node { int m_nKey; int m_nValue; Node* m_pNext; Node* m_pPre; }; class LRUCache { public: LRUCache(int capacity) { m_nSize = capacity; m_nCount = 0; m_lruListHead = NULL; m_lruListTail = NULL; } int get(int key) { if (NULL == m_lruListHead) return -1; map<int, Node *>::iterator it = m_lruMap.find(key); if (it == m_lruMap.end()) //没有找到 return -1; else { Node *p = it->second; //把节点移到链表的开头 pushFront(p); } return m_lruListHead->m_nValue; } void set(int key, int value) { if (NULL == m_lruListHead) { m_lruListHead = new Node(); m_lruListHead->m_nKey = key; m_lruListHead->m_nValue = value; m_lruListHead->m_pNext = NULL; m_lruListHead->m_pPre = NULL; m_lruListTail = m_lruListHead; m_nCount ++; m_lruMap[key] = m_lruListHead; } else { map<int, Node *>::iterator it = m_lruMap.find(key); if (it == m_lruMap.end()){ //没有命中,将链表的最后一个节点删除 if (m_nSize == m_nCount) { //cache已满 if (m_lruListHead == m_lruListTail) {//只有一个节点 m_lruMap.erase(m_lruListHead->m_nKey); m_lruListHead->m_nKey = key; m_lruListHead->m_nValue = value; m_lruMap[key] = m_lruListHead; } else { Node *p = m_lruListTail; m_lruListTail->m_pPre->m_pNext = NULL; m_lruListTail = m_lruListTail->m_pPre; m_lruMap.erase(p->m_nKey); p->m_nKey = key; p->m_nValue = value; p->m_pNext = m_lruListHead; p->m_pPre = NULL; m_lruListHead->m_pPre = p; m_lruListHead = p; m_lruMap[key] = m_lruListHead; } } else { Node *p = new Node(); //插入新的节点于头部 p->m_nKey = key; p->m_nValue = value; p->m_pNext = m_lruListHead; p->m_pPre = NULL; m_lruListHead->m_pPre = p; m_lruListHead = p; m_lruMap[key] = m_lruListHead; m_nCount ++; } } else { //命中,则将该命中的节点移至链表头部 Node *pCur = it->second; pCur->m_nValue = value; pushFront(pCur); } } } void pushFront(Node *pCur) { //把节点移动到链表头部,时间复杂度O(1) if (NULL == pCur) return; if (m_nCount == 1 || pCur == m_lruListHead) return; if (pCur == m_lruListTail) { //假如是尾节点 pCur->m_pPre->m_pNext = NULL; pCur->m_pNext = m_lruListHead; m_lruListTail = pCur->m_pPre; m_lruListHead->m_pPre = pCur; m_lruListHead = pCur; } else { pCur->m_pPre->m_pNext = pCur->m_pNext; pCur->m_pNext->m_pPre = pCur->m_pPre; pCur->m_pNext = m_lruListHead; m_lruListHead->m_pPre = pCur; m_lruListHead = pCur; } } void printCache() { Node *p = m_lruListHead; while (p) { cout << p->m_nKey << ":" << p->m_nValue << " "; p = p->m_pNext; } } private: int m_nSize; int m_nCount; map<int, Node *> m_lruMap; Node* m_lruListHead; Node* m_lruListTail; };
下面是hashmap+list版本,如果是C++,list和hashmap都是STL自带的功能实现,所以,我们直接应用STL库,代码量大大减少,时间复杂度为O(1).^-^代码参考:dancingrain
#include <iostream> #include <hash_map> #include <list> #include <utility> using namespace std; using namespace stdext; class LRUCache{ public: LRUCache(int capacity) { m_capacity = capacity ; } int get(int key) { int retValue = -1 ; hash_map<int, list<pair<int, int> > :: iterator> ::iterator it = cachesMap.find(key) ; //如果在Cashe中,将记录移动到链表的最前端 if (it != cachesMap.end()) { retValue = it ->second->second ; //移动到最前端 list<pair<int, int> > :: iterator ptrPair = it -> second ; pair<int, int> tmpPair = *ptrPair ; caches.erase(ptrPair) ; caches.push_front(tmpPair) ; //修改map中的值 cachesMap[key] = caches.begin() ; } return retValue ; } void set(int key, int value) { hash_map<int, list<pair<int, int> > :: iterator> ::iterator it = cachesMap.find(key) ; if (it != cachesMap.end()) //已经存在其中 { list<pair<int, int> > :: iterator ptrPait = it ->second ; ptrPait->second = value ; //移动到最前面 pair<int , int > tmpPair = *ptrPait ; caches.erase(ptrPait) ; caches.push_front(tmpPair) ; //更新map cachesMap[key] = caches.begin() ; } else //不存在其中 { pair<int , int > tmpPair = make_pair(key, value) ; if (m_capacity == caches.size()) //已经满 { int delKey = caches.back().first ; caches.pop_back() ; //删除最后一个 //删除在map中的相应项 hash_map<int, list<pair<int, int> > :: iterator> ::iterator delIt = cachesMap.find(delKey) ; cachesMap.erase(delIt) ; } caches.push_front(tmpPair) ; cachesMap[key] = caches.begin() ; //更新map } } private: int m_capacity ; //cashe的大小 list<pair<int, int> > caches ; //用一个双链表存储cashe的内容 hash_map< int, list<pair<int, int> > :: iterator> cachesMap ; //使用map加快查找的速度 };
相关文章推荐
- 【Leetcode】:LRU Cache_缓存淘汰算法LRU的设计与实现
- LeetCode-LRU Cache-LRU缓存-DS
- 【Leetcode-hard-146】LRU Cache
- Java for LeetCode 146 LRU Cache 【HARD】
- LeetCode: LRU Cache 最近最少使用算法 缓存设计
- LeetCode题解: LRU Cache 缓存设计
- Leetcode 146 LRU Cache 模拟操作系统LRU
- LeetCode OJ 之 LRU Cache(LRU缓存)
- LRU缓存设计
- LRU缓存设计-踩坑学习
- LRU Cache 最近最少使用缓存的设计
- 【leetcode】LRU Cache(hard)★
- leetcode || 146、LRU Cache
- leetcode 146: LRU Cache
- LeetCode: LRU Cache [146]
- 8.12 [LeetCode] 146 LRU Cache
- 请用Java设计一个Least Recently Used (LRU) 缓存
- leetcode LRU Cache(高级缓存的最近最少使用算法实现)
- LeetCode146:LRU Cache
- LeetCode 146 LRU Cache