设计LRU/LFU缓存结构

目录

一.设计LRU(最近最少未使用)缓存结构

1.要求

2.思路步骤

3.代码

二.设计LFU缓存结构

1.要求

2.思路图解

3.代码


一.设计LRU(最近最少未使用)缓存结构

1.要求

设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 capacity ,操作次数是 n ,并有如下功能:
1. Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
2. get(key):如果关键字 key 存在于缓存中,则返回key对应的value值,否则返回 -1 。
3. set(key, value):将记录(key, value)插入该结构,如果关键字 key 已经存在,则变更其数据值 value,如果不存在,则向缓存中插入该组 key-value ,如果key-value的数量超过capacity,弹出最久未使用的key-value。

这里set和get要求的时间复杂度为O(1)。

 

2.思路步骤

(1)首先自己维护一个双向链表,其中包含自定义的Node类,Node中包含该节点的value,和前后指针,还有key(方便之后将结点存储在map中get的时间复杂度为O(1)).

(2)初始化一个头结点和尾结点,将两个结点连接起来,之后set或get时对该双向链表维护的结点进行操作

(3)对于get操作时,直接将map中维护的结点返回,然后将该结点设置为最近使用。

(4)对于set操作时,首先判断容量是否足够,如果够,就添加到map和链表中;否则就删除掉链表和map中最久未使用的元素,再将该元素设置为最近使用,并添加到map中。

(5)接下来是用双向链表来维护一个最近最久未使用的规则。首先尾结点存放的是最久未使用的结点,头结点存放最新使用的结点。对于最久未使用结点进行删除转化为删除尾结点和map中存放的结点;对于新结点直接插入到头结点,然后再将结点保存到map中即可;对于老节点的访问,首先需要删除链表中该节点,然后再将该节点插入到链表头(相当于是将结点移动到最新访问的位置)。

 

 

 

3.代码

import java.util.*;

public class Solution {

    private static class Node{
        int val;
        int key;
        Node pre;
        Node next;
        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
    int capacity = 0;
    //设置一个头结点
    Node head = new Node(-1,-1);
    //设置一个尾结点
    Node tail = new Node(-1,-1);
    Map<Integer, Node> map = new HashMap<>();
    public Solution(int capacity) {
         // write code here
        this.capacity = capacity;
        //初始化链表,将头尾连接起来
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
         // write code here
        int res = -1;
        if(map.containsKey(key)) {
            Node node = map.get(key);
            res = node.val;
            moveHead(node);
        }
        return res;
    }

    public void set(int key, int value) {
         // write code here
        if(!map.containsKey(key)) {
            //说明当前元素部存在,添加并判断是否超过容量
            Node node = new Node(key, value);
            map.put(key, node);
            if(map.size()>capacity) {
                //超过删除
                removeTail();
            }
            addHead(node);
        }else {
            //说明已经存在,覆盖并移动到表头
            map.get(key).val = value;
            moveHead(map.get(key));
        }
    }
    //将节点移动到表头
    public void moveHead(Node node){
        //如果当前结点已经是表头,结束,否则就删除并添加到表头
        if(node.pre==head) return;
        node.pre.next = node.next;
        node.next.pre = node.pre;
        addHead(node);
    }
    //向头结点插入元素
    public void addHead(Node node){
        node.next = head.next;
        node.pre = head;
        head.next.pre = node;
        head.next = node;
    }
    //删除尾结点
    public void removeTail(){
        //删除hash表中的值
        map.remove(tail.pre.key);
        //删除结点
        tail.pre.pre.next = tail;
        tail.pre = tail.pre.pre;
    }
}

 

二.设计LFU缓存结构

1.要求

一个缓存结构需要实现如下功能。

  • set(key, value):将记录(key, value)插入该结构
  • get(key):返回key对应的value值

但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;

如果调用次数最少的key有多个,上次调用发生最早的key被删除

这就是LFU缓存替换算法。实现这个结构,K作为参数给出

若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1

示例:

输入:[[1,1,1],[1,2,2],[1,3,2],[1,2,4],[1,3,5],[2,2],[1,4,4],[2,1]],3

返回值:[4,-1]

说明:在执行"1 4 4"后,"1 1 1"被删除。因此第二次询问的答案为-1

2.思路图解

(1)首先通过map来保存每一个元素的key和value,方便之后进行查找

(2)自定义一个节点类,其中包含key,value,和访问次数和使用时间(用变化的数值代替),之后对该类设置比较规则,如果访问次数不同,默认以访问次数;如果访问次数相同,就以使用时间的早晚进行排序。

(3)使用一个优先级队列,默认以小堆的方式对结点进行排序,堆顶存放的是能够删除的节点。

(4)对于get操作,需要先将原来的元素访问次数和时间进行修改,然后再在小堆中进行重新排序(以删除添加来实现)。

对于set操作,首先判断key是否已经存在,如果存在,就修改存在元素的值,访问次数和时间,然后再放入到小堆中重新排序;如果不存在,且容量已经满了,需要删除符合规则的元素,删除堆顶,然后设置新节点的访问次数和时间并插入到堆和map中;如果容量没满,设置节点后插入到堆和map中即可。

 

3.代码

import java.util.*;


public class Solution {
    //通过小堆的形式,堆顶存放的是需要删除的结点
   Queue<Node> q = new PriorityQueue<>();
    Map<Integer, Node> map = new HashMap<>();
    int time;//模拟当前时间
    //这里k代表容量大小,超过k就需要删除符合规则的key
    public int[] LFU (int[][] operators, int k) {
        // write code here
        ArrayList<Integer> list = new ArrayList<>();
        for(int i=0; i<operators.length; i++) {
            int opt = operators[i][0];
            //判断是set还是get操作
            if(opt==1) {
                int key = operators[i][1];
                int val = operators[i][2];
                set(key, val, k);
            }else if(opt==2) {
                int key = operators[i][1];
                list.add(get(key));
            }
        }
        int[] res = new int[list.size()];
        for(int i=0; i<res.length; i++) res[i] = list.get(i);
        return res;
    }
    public void set(int key, int val,int k){
        //判断该元素是否存在
        Node cur = map.get(key);
        if(cur==null) {
            if(q.size()==k) {
                //说明需要删除符合规则的元素,还要删除掉map中该元素
                map.remove(q.poll().key);
            }
            //再将新元素存放到小堆和map中
            Node node = new Node(key, val,time++);
            q.offer(node);
            map.put(key, node);   
            }else {
            //说明该元素已经存在,直接修改元素状态即可
            cur.time = time++;
            cur.fre++;
            cur.val = val;
            map.put(key,cur);
            q.remove(cur);
            q.offer(cur);
        }
    }
    public int get(int key){
        Node node = map.get(key);
        if(node!=null) {
            //需要修改访问时间
            node.fre++;
            node.time=time++;
            //说明元素存在,先删除堆中该元素,然后再插入让其重新排序
            q.remove(node);
            q.offer(node);
            return node.val;
        }
        //元素不存在
        return -1;
    }
    public class Node implements Comparable<Node>{
        int fre;//表示操作次数
        int time;//结点创建的时间
        int key;
        int val;
        public Node(int key, int val, int time){
            this.key = key;
            this.fre++;//第一次创建节点,访问次数为1
            this.val = val;
            this.time = time;
        }
        //设置结点的比较规则
        public int compareTo(Node node) {
            if(this.fre!=node.fre) {
                return this.fre-node.fre;
            }
            return this.time-node.time;
        }
    }
}


版权声明:本文为weixin_47651920原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。