1. 程式人生 > 其它 >【演算法】LRU快取

【演算法】LRU快取

1.概念介紹

假設快取的大小固定,初始狀態為空。每發生一次讀記憶體操作,首先查詢待讀取的資料是否
存在於快取中,如果存在則快取命中,返回資料,並將快取資料放到快取區頭部位置;否則快取未命中,返回提示資訊。
向快取新增資料時,如果快取已滿,則需要刪除訪問時間最早的資料,這種更新快取的方法就叫做LRU(Least Recently Used)。

2. 實際實現LRUCache類

基本要求如下

  • LRUCache(int capacity) 用一個正整數表示的容量大小初始化快取空間。
  • int get(int key) 如果對應的鍵存在於快取中,即命中返回鍵對應的值,未命中返回-1。
  • void put(int key, int value) 如果已存在更新其內容。否則將鍵值對新增到快取中。 如果超過了最大容量,淡出最近最少使用的鍵值對。

同時要求get和put操作保證平均時間複雜度為O(1)。

案例介紹

  • 示例輸入
    ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
    [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]

  • 輸出
    [null, null, null, 1, null, -1, null, -1, 3, 4]

  • 過程解釋
    LRUCache lRUCache = new LRUCache(2);
    LRUCache.put(1, 1); // 快取內容變為 {1=1}
    LRUCache.put(2, 2); // 快取內容變為 {1=1, 2=2}
    LRUCache.get(1); // 返回鍵為1的快取內容對應的值
    LRUCache.put(3, 3); // LRU鍵為2, 新增到快取中前先移除鍵為2的快取內容然後新增新內容, 快取內容變為 {1=1, 3=3}
    LRUCache.get(2); // 快取中不存在就返回-1
    LRUCache.put(4, 4); // LRU鍵為1, 先移除鍵為1的快取內容然後新增新內容, 快取內容變為 {4=4, 3=3}
    LRUCache.get(1); // 快取中不存在就返回-1
    LRUCache.get(3); // 返回3
    LRUCache.get(4); // 返回4

3.C++參考實現

class LRUCache {
public:
	struct ListNode {
		int key;
		int val;
		ListNode *prev;
		ListNode *next;
		ListNode(): key(0), val(0), prev(nullptr), next(nullptr) {}
		ListNode(int _key, int _val): key(_key), val(_val),
			prev(nullptr), next(nullptr) {}
	};
public:
	LRUCache(const int capacity): cap(capacity) {
		head = new ListNode(-1, 0);
		tail = new ListNode(-1, 0);
		head->next = tail;
		tail->prev = head;
	}

	~LRUCache(void) {
		if (cap>0)
		{
			ListNode *curNode = head->next;
			// 釋放連結串列中除首尾指示結點外所有結點佔用的資源
			while (curNode != tail)
			{
				ListNode *tmpNode = curNode;
				curNode = curNode->next;
				delete tmpNode;
				tmpNode = nullptr;
			}
			// 清空雜湊表
			mp.clear();
			// 重置快取容量
			cap = 0;
		}
		// 釋放首尾指標資源並將其設定為空指標
		delete head;
		delete tail;
		head = nullptr;
		tail = nullptr;
	}

	int get(int key) {
		if (mp.find(key)==mp.end())
			return -1;

		ListNode *curNode = mp[key];
		// 如果已存在於當前快取中,將其提前到連結串列首部位置
		moveToHead(curNode);		

		return curNode->val;
	}

	void put(int key, int val) {
		if (mp.find(key)!=mp.end())
		{
			ListNode *curNode = mp[key];
			// 如果已存在於當前快取中,將其提前到連結串列首部位置
			moveToHead(curNode);
			curNode->val = val;
		} else {
			if (cap>0 && mp.size()==cap)
			{
				// 刪除最後一個結點,因為時間最久沒被訪問
				deleteNode(tail->prev);
			}

			// 在連結串列頭部新增新結點
			ListNode *tmpNode = new ListNode(key, val);
			mp[key] = tmpNode;
			addNode(tmpNode);
		}
	}

private:
	void moveToHead(ListNode *pnode)
	{
		// 斷開當前結點與前後結點的連線
		curNode->prev->next = curNode->next;
		curNode->next->prev = curNode->prev;

		// 將結點連到連結串列頭部位置
		curNode->next = head->next;
		head->next->prev = curNode;
		head->next = curNode;
		curNode->prev = head;
	}

	void deleteNode(ListNode *pnode)
	{
		pnode->prev->next = pnode->next;
		pnode->next->prev = pnode->prev;
		delete pnode;
		pnode = nullptr;
	}

	void addNode(ListNode *pnode)
	{
		pnode->next = head->next;
		head->next->prev = pnode;
		head->next = pnode;
		pnode->prev = head;
	}

private:
	int cap;
	std::unordered_map<int, ListNode*> mp;
	ListNode *head;
	ListNode *tail;
};

本文作者 :phillee
發表日期 :2022年03月07日
本文連結https://www.cnblogs.com/phillee/p/15975167.html
版權宣告 :自由轉載-非商用-非衍生-保持署名(創意共享3.0許可協議/CC BY-NC-SA 3.0)。轉載請註明出處!
限於本人水平,如果文章和程式碼有表述不當之處,還請不吝賜教。