緩存算法(頁面置換算法)-FIFO、LFU、LRU

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;
}


 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章