題目描述
運用你所掌握的數據結構,設計和實現一個LRU (最近最少使用) 緩存機制
。它應該支持以下操作: 獲取數據 get
和 寫入數據 put
。
- 獲取數據
get(key)
- 如果密鑰 (key) 存在於緩存中,則獲取密鑰的值(總是正數),否則返回 -1。 - 寫入數據
put(key, value)
- 如果密鑰不存在,則寫入其數據值。當緩存容量達到上限時,它應該在寫入新數據之前刪除最近最少使用的數據值,從而爲新的數據值留出空間。
進階:
你是否可以在 O(1) 時間複雜度內完成這兩種操作?
示例:
LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 該操作會使得密鑰 2 作廢
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 該操作會使得密鑰 1 作廢
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
算法
這題的時間複雜度的好壞比較依賴於所選擇的數據結構。
LRU是操作系統中提出的一種應用於頁置換的算法,這裏不過多介紹,舉個實際例子即可知道本題要求實現的功能需要什麼步驟:
想象有一個隊列的最大允許空間爲3,
依次入隊的順序爲 2,3,2,1,2,4;求LRU算法下隊列的演變過程。
---------------------------------------------------
- 隊列初始爲空,2進入後隊列情況爲:2
- 隊列還有2個剩餘位置,3進入後隊列情況爲:2 3
- 隊列還有1個剩餘位置,這次入隊的數據爲2,它本來就已在隊列中,根據LRU算法,需要將2調到隊列末尾,因此隊列情況爲:3 2
- 隊列還有1個剩餘位置,這次入隊數據爲1,入隊後隊列情況爲:2 3 1
- 隊列已經沒有剩餘位置,但是入隊數據爲2,它本來就在隊列中,根據LRU算法,需要將2調到隊列末尾,因此隊列情況爲:3 1 2
- 隊列已經沒有剩餘位置,新進入的數據爲4,根據LRU需要淘汰最近最少被使用的數據,即隊首的數據3,更新後隊列情況爲:1 2 4
---------------------------------------------------
上面即爲LRU算法的一個例子
選擇hash表與雙向鏈表作爲實現主體功能的兩個數據結構,主要是因爲雙向鏈表便於插入刪除,而hash表可以較快查找到需要返回的value。具體一點,整個LRUCache可能長下面這樣:
代碼
#include <iostream>
#include <list>
#include <unordered_map>
using namespace std;
struct listNode{
int key, value;
listNode *pre, *next;
listNode(int _key, int _value): key(_key), value(_value)
{
pre = next = NULL;
}
};
class LRUCache {
public:
// hash_table末端保存最近剛被使用的節點,前端保存最近最少被使用節點
unordered_map<int, listNode*> hash_table;
listNode *head, *tail;
int cap, size;
LRUCache(int capacity) {
cap = capacity;
size = 0;
head = new listNode(-1, -1);
tail = new listNode(-1, -1);
head->next = tail;
tail->pre = head;
}
int get(int key) {
if (hash_table.find(key) == hash_table.end())
return -1;
else
{
// 記錄該ID指向節點的指針
listNode *tmp = hash_table[key];
/*** 更改節點在表中的順序 ***/
// 1. 刪除hash_table[key]
delNode(tmp);
// 2. 將hash_table[key]插入末尾
pushNodeBack(tmp);
return tmp->value;
}
}
void put(int key, int value) {
// 這個key本身保存在表中
if (hash_table.find(key) != hash_table.end())
{
listNode *tmp = hash_table[key];
// 從鏈表頭部去掉這個點
delNode(tmp);
// 更新表中key對應鏈表節點的value
tmp->value = value;
// 從鏈表尾部插入這個點
pushNodeBack(tmp);
return;
}
// 鏈表的空間已滿
if (cap == size)
{
// 空間不夠,踢出隊列最前端的ID
listNode *tmp = head->next;
// 在表中刪除這個點
hash_table.erase(tmp->key);
// 從鏈表頭部去掉這個點
delNode(tmp);
// 釋放被刪除的點的空間
delete tmp;
}
else
size++;
listNode *node = new listNode(key, value);
hash_table[key] = node;
pushNodeBack(node);
}
void delNode(listNode *node)
{
node->pre->next = node->next;
node->next->pre = node->pre;
}
void pushNodeBack(listNode *node)
{
tail->pre->next = node;
node->pre = tail->pre;
node->next = tail;
tail->pre = node;
}
};
int main()
{
LRUCache *cache = new LRUCache(2);
cache->put(1, 1);
cache->put(2, 2);
cout << cache->get(1) << endl; // 返回 1
cache->put(3, 3); // 該操作會使得密鑰 2 作廢
cout << cache->get(2) << endl; // 返回 -1 (未找到)
cache->put(4, 4); // 該操作會使得密鑰 1 作廢
cout << cache->get(1) << endl; // 返回 -1
cout << cache->get(3) << endl; // 返回 3
cout << cache->get(4) << endl; // 返回 4
}