当前位置:   article > 正文

【每日一题】LFU 缓存_lfu缓存

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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

解法二:哈希表 + 小顶堆

将有序表更换为小顶堆。

删除数据时,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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/950830
推荐阅读
相关标签
  

闽ICP备14008679号