【算法】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)。轉載請註明出處!
限於本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。

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