您的位置:首页 > 科技 > IT业 > 珠海市公共资源交易中心_黄冈网站设计推广哪家好_短视频seo公司_大地seo视频

珠海市公共资源交易中心_黄冈网站设计推广哪家好_短视频seo公司_大地seo视频

2024/12/23 9:40:24 来源:https://blog.csdn.net/weixin_74261199/article/details/143962926  浏览:    关键词:珠海市公共资源交易中心_黄冈网站设计推广哪家好_短视频seo公司_大地seo视频
珠海市公共资源交易中心_黄冈网站设计推广哪家好_短视频seo公司_大地seo视频

什么是LRU缓存?

    LRU(Least Recently Used)是最近最少使用算法,是操作系统中用于分页置换的算法,如果要向内存中添加分页,并且内存分页已满的情况下,就选出最近一段时间最不常用的分页进行置换(例如将最不常用的分页暂时放到磁盘,这时内存就有一个空闲分页,将新增分页放过来即可)。

题目

链接:leetcode.146. LRU 缓存

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)

    正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key)

    如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value)

    如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 O(1) 的平均时间复杂度运行。

分析

一、 容器内容的设计

我们可以用一个容器对数据进行缓存,第一个元素表示最近最不经常使用,最后一个元素表示最近最经常使用,从前往后依次过渡

二、 数据结构的选型

① 使用数组

假设将内容全部用数组进行缓存

现在使用元素2,那么应该将2移到尾部,3、4、5全部向前移动。这样查询的时间复杂度为O(1),移动的时间复杂度为O(n)。

那么题目要求的get时间复杂度就为O(n),put时间复杂度为O(n)。显然不能使用数组。

② 使用链表

如果使用元素2,那么需要找到元素2,然后将元素2放到尾部即可。这样查询的时间复杂度为O(n),移动的时间复杂度为O(1),

显然对于查询操作我们可以使用哈希去优化,使得查询的时间复杂度为O(1)

③ 哈希+ 链表

 

这样查询2只需要通过key映射,查询效率为O(1),将2移动到链表尾部,1指向3即可,移动效率为O(1),完美!

但是这里有个问题,我们通过key定位到元素2时,如何获取2前面的元素?

聪明的小伙伴已经想出来了,答案就是将单链表变为双向链表

④ 哈希 + 双向链表

 这样不管是查询还是移动,复杂度都是O(1)

解答

① 设计双向链表节点结构

class ListNode {//	节点前驱ListNode pre;//	节点后继ListNode next;//	哈希中的keyint key;//	数据域,记录缓存内容int val;//	构造函数public ListNode() {}public ListNode(int key, int val) {this.key = key;this.val = val;}
}

这里节点中为什么要设计key呢?因为如果添加元素时缓存区已满,就需要删除链表中第一个元素(最近最不常使用的元素),同时还要在哈希中移除,所以需要记录这个数据的key

② 使用带假节点的双向链表

为什么使用假节点?原因很简单,操作方便。对于一个单链表,添加元素时要判断头指针是否为空,删除元素时需要判断是不是头部元素,如果是头部元素,就需要将头指针向后移,但是使用假节点之后便不再需要进行这些操作。结构如下:

 可以看出,对于带有假节点的链表,head后邻节点是有效的第一个节点(在不是rear的情况下),rear前邻节点是最后一个有效节点(在不是head的情况下)

③ 初始化LRU容器

//	头部指针
private ListNode head;
//	尾部指针
private ListNode rear;
//	哈希表
private HashMap<Integer, ListNode> hash = new HashMap<>();
//	缓存容量
private final int capacity;public LRUCache(int capacity) {//	缓存容量this.capacity = capacity;//  初始化双向链表//	头部假节点this.head = new ListNode();//	尾部假节点this.rear = new ListNode();//	首尾相连head.next = rear;rear.pre = head;
}

目前为止,双向链表还是一个空链表

④ 获取缓存元素

思路如下:

  1. 元素不存在,直接返回-1

  2. 元素存在

    1. 将元素移到链表尾部

    2. 返回元素

代码实现:

public int get(int key) {//  从哈希表中查找节点ListNode node = hash.get(key);//  节点不存在,返回-1if (node == null) {return -1;}//  节点存在,将节点移动到链表尾部moveToLast(node);//  返回缓存数据return node.val;
}

⑤ 放置缓存元素

思路如下:

  1. 元素不存在

    1. 如果缓存已满,删除链表第一个元素

    2. 添加新节点到链表尾部

  2. 元素存在

    1. 将元素移动到链表尾部

    2. 更节点数据

代码实现:

public void put(int key, int value) {//  判断key是否存在ListNode valueNode = hash.get(key);//  元素不存在if (valueNode == null) {//  判断缓存是否到达上限if (hash.size() == capacity) {//  删除最不经常使用的节点ListNode firstNode = head.next;removeNode(firstNode);}//  添加新节点到链表尾部addNodeToBack(new ListNode(key, value));} else {//  节点存在,将节点移到尾部,并修改节点值moveToLast(valueNode);valueNode.val = value;}
}

⑥ 操作节点函数的封装

将节点放置到尾部

private void moveToLast(ListNode node) {//  如果节点在链表中,那么更新他的前驱与后继的指针指向if (node.pre != null && node.next != null) {ListNode pre = node.pre;ListNode next = node.next;pre.next = next;next.pre = pre;}//  将节点插入到尾部ListNode rearPre = rear.pre;rearPre.next = node;node.pre = rearPre;node.next = rear;rear.pre = node;
}

添加节点到尾部并放入哈希表

private void addNodeToBack(ListNode newNode) {//	将节点移动到尾部moveToLast(newNode);//	将节点映射信息放入哈希表hash.put(newNode.key, newNode);
}

删除节点

private void removeNode(ListNode node) {//	移除哈希表中存储的信息hash.remove(node.key);//	移除链表中的节点ListNode pre = node.pre;ListNode next = node.next;pre.next = next;next.pre = pre;
}

代码实现

class LRUCache {private static class ListNode {public ListNode pre;public ListNode next;public int key;public int val;public ListNode() {}public ListNode(int key, int val) {this.key = key;this.val = val;}}private ListNode head;private ListNode rear;private HashMap<Integer, ListNode> hash = new HashMap<>();private final int capacity;public LRUCache(int capacity) {this.capacity = capacity;//  初始化双向链表this.head = new ListNode();this.rear = new ListNode();head.next = rear;rear.pre = head;}public int get(int key) {//  从哈希表中查找节点ListNode node = hash.get(key);//  节点不存在,返回-1if (node == null) {return -1;}//  节点存在,将节点移动到链表尾部moveToLast(node);//  返回缓存数据return node.val;}public void put(int key, int value) {//  判断key是否存在ListNode valueNode = hash.get(key);//  元素不存在if (valueNode == null) {//  判断缓存是否到达上限if (hash.size() == capacity) {//  删除最不经常使用的节点ListNode firstNode = head.next;removeNode(firstNode);}//  添加新节点到链表尾部addNodeToBack(new ListNode(key, value));} else {//  节点存在,将节点移到尾部,并修改节点值moveToLast(valueNode);valueNode.val = value;}}private void addNodeToBack(ListNode newNode) {moveToLast(newNode);hash.put(newNode.key, newNode);}private void moveToLast(ListNode node) {//  如果节点在链表中,那么更新他的前驱与后继的指针指向if (node.pre != null && node.next != null) {ListNode pre = node.pre;ListNode next = node.next;pre.next = next;next.pre = pre;}//  将节点插入到尾部ListNode rearPre = rear.pre;rearPre.next = node;node.pre = rearPre;node.next = rear;rear.pre = node;}private void removeNode(ListNode node) {hash.remove(node.key);ListNode pre = node.pre;ListNode next = node.next;pre.next = next;next.pre = pre;}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com