1. FIFO -- 先進先出
如果一個數據最先進入緩存中,則應該最早淘汰掉。也就是說,當緩存滿的時候,應當把最先進入緩存的數據給淘汰掉。
實現:
利用一個雙向鏈表保存數據,當來了新的數據之後便添加到鏈表末尾,如果Cache存滿數據,則把鏈表頭部數據刪除,然後把新的數據添加到鏈表末尾。在訪問數據的時候,如果在Cache中存在該數據的話,則返回對應的value值;否則返回-1。如果想提高訪問效率,可以利用hashmap來保存每個key在鏈表中對應的位置。
2. LFU -- 最近最少使用
基於“如果一個數據在最近一段時間內使用次數很少,那麼在將來一段時間內被使用的可能性也很小”的思路。
LFU是基於訪問次數的。
實現:
爲了能夠淘汰最少使用的數據,LFU算法最簡單的一種設計思路就是利用一個數組存儲數據項,用hashmap存儲每個數據項在數組中對應的位置,然後爲每個數據項設計一個訪問頻次,當數據項被命中時,訪問頻次自增,在淘汰的時候淘汰訪問頻次最少的數據。這樣一來的話,在插入數據和訪問數據的時候都能達到O(1)的時間複雜度,在淘汰數據的時候,通過選擇算法得到應該淘汰的數據項在數組中的索引,並將該索引位置的內容替換爲新來的數據內容即可,這樣的話,淘汰數據的操作時間複雜度爲O(n)。
另外還有一種實現思路就是利用小頂堆+hashmap,小頂堆插入、刪除操作都能達到O(logn)時間複雜度,因此效率相比第一種實現方法更加高效。
3. LRU -- 最近最久未使用
如果一個數據在最近一段時間沒有被訪問到,那麼在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。
實現:
(1)用一個數組來存儲數據,給每一個數據項標記一個訪問時間戳,每次插入新數據項的時候,先把數組中存在的數據項的時間戳自增,並將新數據項的時間戳置爲0並插入到數組中。每次訪問數組中的數據項的時候,將被訪問的數據項的時間戳置爲0。當數組空間已滿時,將時間戳最大的數據項淘汰。
思路簡單,但是需要不停地維護數據項的訪問時間戳,另外,在插入數據、刪除數據以及訪問數據時,時間複雜度都是O(n)。
(2)利用鏈表和hashmap。當需要插入新的數據項的時候,如果新數據項在鏈表中存在(一般稱爲命中),則把該節點移到鏈表頭部;如果不存在,則新建一個節點,放到鏈表頭部。若緩存滿了,則把鏈表最後一個節點刪除即可。在訪問數據的時候,如果數據項在鏈表中存在,則把該節點移到鏈表頭部,否則返回-1。這樣一來在鏈表尾部的節點就是最近最久未訪問的數據項。
在已知要刪除的節點的情況下,如何在O(1)時間複雜度內刪除節點?
假如要刪除的節點是cur,通過cur可以知道cur節點的後繼節點curNext,如果交換cur節點和curNext節點的數據域,然後刪除curNext節點(curNext節點是很好刪除地),此時便在O(1)時間複雜度內完成了cur節點的刪除。
如何使得刪除末尾節點的複雜度也在O(1)?
利用雙向鏈表,並提供head指針和tail指針,這樣一來,所有的操作都是O(1)時間複雜度。
參考實現:
(1)
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;
struct Node
{
int key;
int value;
Node *pre;
Node *next;
};
class LRUCache{
private:
int count;
int size ;
map<int,Node *> mp;
Node *cacheHead;
Node *cacheTail;
public:
LRUCache(int capacity) {
size = capacity;
cacheHead = NULL;
cacheTail = NULL;
count = 0;
}
int get(int key) {
if(cacheHead==NULL)
return -1;
map<int,Node *>::iterator it=mp.find(key);
if(it==mp.end()) //如果在Cache中不存在該key, 則返回-1
{
return -1;
}
else
{
Node *p = it->second;
pushFront(p); //將節點p置於鏈表頭部
}
return cacheHead->value;
}
void set(int key, int value) {
if(cacheHead==NULL) //如果鏈表爲空,直接放在鏈表頭部
{
cacheHead = (Node *)malloc(sizeof(Node));
cacheHead->key = key;
cacheHead->value = value;
cacheHead->pre = NULL;
cacheHead->next = NULL;
mp[key] = cacheHead;
cacheTail = cacheHead;
count++;
}
else //否則,在map中查找
{
map<int,Node *>::iterator it=mp.find(key);
if(it==mp.end()) //沒有命中
{
if(count == size) //cache滿了
{
if(cacheHead==cacheTail&&cacheHead!=NULL) //只有一個節點
{
mp.erase(cacheHead->key);
cacheHead->key = key;
cacheHead->value = value;
mp[key] = cacheHead;
}
else
{
Node * p =cacheTail;
cacheTail->pre->next = cacheTail->next;
cacheTail = cacheTail->pre;
mp.erase(p->key);
p->key= key;
p->value = value;
p->next = cacheHead;
p->pre = cacheHead->pre;
cacheHead->pre = p;
cacheHead = p;
mp[cacheHead->key] = cacheHead;
}
}
else
{
Node * p = (Node *)malloc(sizeof(Node));
p->key = key;
p->value = value;
p->next = cacheHead;
p->pre = NULL;
cacheHead->pre = p;
cacheHead = p;
mp[cacheHead->key] = cacheHead;
count++;
}
}
else
{
Node *p = it->second;
p->value = value;
pushFront(p);
}
}
}
void pushFront(Node *cur) //雙向鏈表刪除節點,並將節點移動鏈表頭部,O(1)
{
if(count==1)
return;
if(cur==cacheHead)
return;
if(cur==cacheTail)
{
cacheTail = cur->pre;
}
cur->pre->next = cur->next; //刪除節點
if(cur->next!=NULL)
cur->next->pre = cur->pre;
cur->next = cacheHead;
cur->pre = NULL;
cacheHead->pre = cur;
cacheHead = cur;
}
void printCache(){
Node *p = cacheHead;
while(p!=NULL)
{
cout<<p->key<<" ";
p=p->next;
}
cout<<endl;
}
};
int main(void)
{
LRUCache cache(3);
cache.set(1,1);
//cache.printCache();
cache.set(2,2);
//cache.printCache();
cache.set(3,3);
cache.printCache();
cache.set(4,4);
cache.printCache();
cout<<cache.get(4)<<endl;
cache.printCache();
cout<<cache.get(3)<<endl;
cache.printCache();
cout<<cache.get(2)<<endl;
cache.printCache();
cout<<cache.get(1)<<endl;
cache.printCache();
cache.set(5,5);
cache.printCache();
cout<<cache.get(1)<<endl;
cout<<cache.get(2)<<endl;
cout<<cache.get(3)<<endl;
cout<<cache.get(4)<<endl;
cout<<cache.get(5)<<endl;
return 0;
}
(2)用stl的list實現雙向鏈表
#include <iostream>
#include <map>
#include <algorithm>
#include <list>
using namespace std;
struct Node
{
int key;
int value;
};
class LRUCache{
private:
int maxSize ;
list<Node> cacheList;
map<int, list<Node>::iterator > mp;
public:
LRUCache(int capacity) {
maxSize = capacity;
}
int get(int key) {
map<int, list<Node>::iterator >::iterator it = mp.find(key);
if(it==mp.end()) //沒有命中
{
return -1;
}
else //在cache中命中了
{
list<Node>::iterator listIt = mp[key];
Node newNode;
newNode.key = key;
newNode.value = listIt->value;
cacheList.erase(listIt); //先刪除命中的節點
cacheList.push_front(newNode); //將命中的節點放到鏈表頭部
mp[key] = cacheList.begin();
}
return cacheList.begin()->value;
}
void set(int key, int value) {
map<int, list<Node>::iterator >::iterator it = mp.find(key);
if(it==mp.end()) //沒有命中
{
if(cacheList.size()==maxSize) //cache滿了
{
mp.erase(cacheList.back().key);
cacheList.pop_back();
}
Node newNode;
newNode.key = key;
newNode.value = value;
cacheList.push_front(newNode);
mp[key] = cacheList.begin();
}
else //命中
{
list<Node>::iterator listIt = mp[key];
cacheList.erase(listIt); //先刪除命中的節點
Node newNode;
newNode.key = key;
newNode.value = value;
cacheList.push_front(newNode); //將命中的節點放到鏈表頭部
mp[key] = cacheList.begin();
}
}
};
int main(void)
{
LRUCache cache(3);
cache.set(1,1);
cache.set(2,2);
cache.set(3,3);
cache.set(4,4);
cout<<cache.get(4)<<endl;
cout<<cache.get(3)<<endl;
cout<<cache.get(2)<<endl;
cout<<cache.get(1)<<endl;
cache.set(5,5);
cout<<cache.get(1)<<endl;
cout<<cache.get(2)<<endl;
cout<<cache.get(3)<<endl;
cout<<cache.get(4)<<endl;
cout<<cache.get(5)<<endl;
return 0;
}