【算法】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;
				deletenode(tmpNode);
			}
			// 清空哈希表
			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)。转载请注明出处!
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

posted @ 2022-03-07 11:29  coffee_tea_or_me  阅读(76)  评论(0编辑  收藏  举报