LeetCode146:LRU Cache

mickole發表於2014-02-17

題目:

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.

解題思路:

利用雙向連結串列+hashtable實現

Cache中的儲存空間往往是有限的,當Cache中的儲存塊被用完,而需要把新的資料Load進Cache的時候,我們就需要設計一種良好的演算法來完成資料塊的替換。LRU的思想是基於“最近用到的資料被重用的概率比較早用到的大的多”這個設計規則來實現的。

為了能夠快速刪除最久沒有訪問的資料項和插入最新的資料項,我們雙向連結串列連線Cache中的資料項,並且保證連結串列維持資料項從最近訪問到最舊訪問的順序。每次資料項被查詢到時,都將此資料項移動到連結串列頭部(O(1)的時間複雜度)。這樣,在進行過多次查詢操作後,最近被使用過的內容就向連結串列的頭移動,而沒有被使用的內容就向連結串列的後面移動。當需要替換時,連結串列最後的位置就是最近最少被使用的資料項,我們只需要將最新的資料項放在連結串列頭部,當Cache滿時,淘汰連結串列最後的位置就是了。
    查詢一個連結串列中元素的時間複雜度是O(n),每次命中的時候,我們就需要花費O(n)的時間來進行查詢,怎麼樣才能提高查詢的效率呢?當然是hashtable了,因為它的查詢時間複雜度是O(1)。

實現程式碼:

#include <iostream>
#include <unordered_map>

using namespace std;
/*
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.
*/

//雙向連結串列節點 
struct LRUNode
{
    int key;
    int val;
    LRUNode *pre;
    LRUNode *next;
    LRUNode(int k = 0, int v = 0):key(k), val(v),pre(NULL), next(NULL){
    } 
};
class LRUCache{
public:
    LRUCache(int capacity):cap(capacity),size(0) {
        head = new LRUNode();
        tail = new LRUNode();
        head->next = tail;
        tail->pre = head;
             
    }
    
    int get(int key) {
        LRUNode *t = hashtable[key];
        if(t)//key在hashtable存在,則調整該key在連結串列中對應節點的位置,將其插入到最前面 
        {
            //分離t節點 
            t->pre->next = t->next;
            t->next->pre = t->pre;
            //將t節點插入到頭結點之後,即第一個資料節點 
            t->pre = head;
            t->next = head->next;
            head->next = t;
            t->next->pre = t;
            return t->val;
            
        }
        else
            return -1;
        
    }
    
    void set(int key, int value) {
        LRUNode *t = hashtable[key];
        if(t)//key在hashtable存在,則更新value及調整該key對應節點在連結串列中位置,將其插入到第一個節點 
        {
            t->val = value;
            //分離t節點 
            t->pre->next = t->next;
            t->next->pre = t->pre;
            //將t節點插入到頭結點之後,即第一個資料節點
            t->pre = head;
            t->next = head->next;
            head->next = t;
            t->next->pre = t;
            return ;
            
        }

        if(size == cap)//如果雙向連結串列容量已滿即快取容量已滿,則將最近不使用的節點即連結串列最後一個資料節點刪除 
        {
            LRUNode *tmp = tail->pre;
            tail->pre->pre->next = tail;           
            tail->pre = tmp->pre;
            hashtable.erase(tmp->key);
            delete tmp;
            size--;
        }
        
        //建立key對應的一個新節點,插入到最前面 
        LRUNode *node = new LRUNode(key, value);
        
        node->pre = head;
        node->next = head->next;
        head->next = node;
        node->next->pre = node;
        
        hashtable[key] = node;//在hashtable新增key對應的表項 
        
        size++;//連結串列節點數++ 
        
        
    }
private:
    int cap;//連結串列容量即快取容量 
    int size;//快取當前使用量 
    LRUNode *head;//連結串列頭結點,不存資料, 
    LRUNode *tail;//連結串列尾節點,不存資料 
    unordered_map<int,LRUNode*> hashtable;//hashtable,用作查詢O(1)時間複雜度 
    
};
int main(void)
{
    LRUCache lrucache(3);
    lrucache.set(1,8);
    lrucache.set(2,9);
    lrucache.set(3,10);
    lrucache.set(4,11);
    cout<<lrucache.get(1)<<endl;
    cout<<lrucache.get(2)<<endl;
    cout<<lrucache.get(4)<<endl;

    
    return 0;
}

相關文章