【每日一题】LFU 缓存

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

void set(int key,int value):加入或者修改 key 对应的 value

int get(int key):查询 key 对应的 value 值

但是缓存最多放 K 条记录,如果新的 K + 1 条记录需要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。

这个策略为:在缓存结构的 K 条记录中,哪一个 key 从进入缓存结构的时刻开始,被调用 set 或者 get 次数最少,就删掉这个key 的记录;如果调用次数最少的 key 有多个,上次调用发送最早的 key 被删除。

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

解法一:哈希表 + 有序表

缓存中的数据是 k,v 对,所以用 map 存储数据。由于缓存数据需要根据:使用次数和使用时间进行淘汰,如果遍历数据查找需要淘汰的数据,耗时比较高。因此需要维护一个有序结构,方便查找要淘汰的数据。

  • 有序数组:
    • 查找要淘汰数据的耗时为:O(1),删除后需要移动数据,耗时为:O(K)
    • 每次操作都需要更新 count 和 time 并维护数组的有序,此时也需要查找并移动数据,耗时为为:O(K)
  • 有序链表:
    • 查找要淘汰数据的耗时为:O(1),(比如头结点或者尾结点)
    • 更新操作时,查找对应节点的耗时为:O(K)
  • 有序表:
    • 查找并移除要淘汰的数据的耗时为:O(log K)
    • 更新操作的耗时时为:O(log K)
  • 小顶堆:
    • 查找并移除要淘汰的数据的耗时为:O(1),移除堆顶后需要堆化的耗时为:O(log K)。
    • 更新数据后,也需要堆化,耗时为:O(log K)。

时间复杂度:O(log K)

空间复杂度:O(K)

import java.util.*;

public class LFU1 {
   
    public static class Node implements Comparable<Node> {
   
        public int key;
        public int value;
        // 这个节点发生get或者set的次数总和
        public int count;
        // 最后一次操作的时间
        public int time;
        
        public Node(int key, int value, int count, int time) {
   
            this.key = key;
            this.value = value;
            this.count = count;
            this.time = time;
        }

        // 缓存淘汰优先级
        // 最少使用(count 越小越容易被淘汰)
        // count 相同,时间越早越容易被淘汰(time 越小越容易被淘汰)
        @Override
        public int compareTo(Node o) {
   
            return count == o.count ? Integer.compare(time, o.time) : Integer.compare(count, o.count);
        }

        @Override
        public String toString() {
   
            return "Node{" + "key=" + key + ", value=" + value + ", count=" + count + ", time=" + time + '}';
        }
    }

    public static class LFUCache {
   
        // 缓存过期优先级
        TreeSet<Node> set = new TreeSet<>();
        Map<Integer, Node> map = new HashMap<>();
        int capacity;
        int time = 0; // 用来计算缓存时间

        public LFUCache(int capacity) {
   
            this.capacity = Math.max(capacity, 0);
        }

        public Integer get(int key) {
   
            if (!map.containsKey(key)) {
   
                return null;
            }
            Node node = map.get(key);
            set(key, node.value);
            return node.value;
        }

        public void set(int key, int value) {
   
            this.time += 1;
            // 更新
            if (map.containsKey(key)) {
   
                Node node = map.get(key);
                // 删除再插入(node 的count 和 time 变化了,TreeSet 认为是不同的数据)
                set.remove(node);
                node.time = this.time;
                node.count++;
                node.value = value;
                set.add(node);
                map.put(key, node);
                return;
            }

            // 新增
            // 如果内存满了,淘汰一条旧数据
            if (this.capacity == this.map.size()) {
   
                remove();
            }
            Node node = new Node(key, value, 1, this.time);
            map.put(key, node);
            set.add(node);
        }

        public void remove() {
   
            if (map.size() == 0) {
   
                return;
            }
            Node node = set.first();
            map.remove(node.key);
            set.remove(node);
        }
    }
}

解法二:哈希表 + 小顶堆

将有序表更换为小顶堆。

删除数据时,heap.pop()

更新数据后,堆化:heap.heapify(node)。更新数据使得 time 和 count 变大,因此只需要从 node 节点开始向下堆化

时间复杂度:O(log K)

空间复杂度:O(K)

import java.util.*;

public class LFU3 {
   
    public static class Node {
   
        public int key;
        public int value;
        // 这个节点发生get或者set的次数总和
        public int count;
        // 最后一次操作的时间
        public int time;

        public Node(int key, int value, int count, int time
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值