力扣关于LRU的一种实现哈希表+双向链表

目录

关于LRU缓存:

LRU原理

设计思路:

代码实现:

仿真结果:

146题:

运用你所掌握的数据结构,设计和实现一个  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缓存:

LRU原理

操作系统中关于LRU:假设内存只能容纳3个页大小,按照 7 0 1 2 0 3 0 4 的次序访问页。假设内存按照栈的方式来描述访问时间,在上面的,是最近访问的,在下面的是,最远时间访问的,LRU就是这样工作的。保证栈顶的数据最热,栈底的数据最冷;

设计思路:

使用 HashMap 存储 key,这样可以做到 save 和 get key的时间都是 O(1),而 HashMap 的 Value 指向双向链表实现的 LRU 的 Node 节点,利用空间hash_map的空间换来快速如图所示的访问;

LRU 存储是基于双向链表实现的。其中 head 代表双向链表的表头,tail 代表尾部。首先预先设置 LRU 的容量,如果存储满了,可以通过 O(1) 的时间淘汰掉双向链表的尾部,每次新增和访问数据,都可以通过 O(1)的效率把新的节点增加到对头,或者把已经存在的节点移动到队头。

  1. put(key, value),首先在 HashMap 找到 Key 对应的节点,如果节点存在,更新节点的值,并把这个节点移动队头。如果不存在,需要构造新的节点,并且尝试把节点塞到队头,如果LRU空间不足,则通过 tail 淘汰掉队尾的节点,同时在 HashMap 中移除 Key。
  2. get(key),通过 HashMap 找到 LRU 链表节点,因为根据LRU 原理,这个节点是最新访问的,所以要把节点插入到队头,然后返回缓存的值

代码实现:



struct DlinkdNode {
	int  key;
	int val;
	DlinkdNode* pre;
	DlinkdNode* next;

};





class LRUCache {
private:
	unordered_map<int, DlinkdNode*>cache;
	int capacity;
	int size;
	DlinkdNode*head;
	DlinkdNode*tail;

    //链表中添加节点
	void addNode(DlinkdNode* node)
	{
		node->pre = head;
		node->next = head->next;
		head->next->pre = node;
		head->next = node;
	}
    //链表中删除节点
	void remove(DlinkdNode*node)
	{
		node->pre->next = node->next;
		node->next->pre = node->pre;
		//delete node;此处不能删除,节点的删除交给hash_map进行,否则后序hash_map无法访问此节点
	}
//链表中将节点调制头结点,数据变为最热的数据
	void moveTohead(DlinkdNode*node)
	{
		this->remove(node);
		this->addNode(node);
	}

	//删除链表尾节点
	DlinkdNode* popTail()
	{
		DlinkdNode*res = tail->pre;
		this->remove(res);
		return res;
	}


public:
	LRUCache(int capacity) {
		size = 0;
		this->capacity = capacity;

		head = new DlinkdNode();
		head->pre = nullptr;

		tail = new DlinkdNode();
		tail->next = nullptr;

		head->next = tail;
		tail->pre = head;
	}

	int get(int key) {
		unordered_map<int, DlinkdNode*>::iterator it = cache.find(key);
		if (it == cache.end())
			return -1;
		else {
			moveTohead(it->second);
			return it->second->val;
		}
	}


	void put(int key, int value) {
		unordered_map<int, DlinkdNode*>::iterator it = cache.find(key);
		if (it == cache.end()) {
			DlinkdNode*Node = new DlinkdNode();
			Node->key = key;
			Node->val = value;
			this->cache.insert({ key,Node });
			addNode(Node);

			++size;
			if (size > capacity)//超出容量将尾部最冷数据删除
			{
				DlinkdNode* TailNode = popTail();
				cache.erase(TailNode->key);
				--size;
			}
		}
		else {
			it->second->val = value;//更新缓存key对应的val,并移动到链表头,最热
			moveTohead(it->second);
	}

}
};

参考:

https://zhuanlan.zhihu.com/p/34133067?utm_source=wechat_session&utm_medium=social&s_r=0

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值