lfu java_LFU - 猫熊小才天 - 博客园

date: 2020-12-08 10:49:21

updated: 2020-12-08 14:57:09

LFU

LFU(Least Frequently Used ,最近最少使用算法)

算法描述:

class LFUCache {

// 构造容量为 capacity 的缓存

public LFUCache(int capacity) {}

// 在缓存中查询 key

public int get(int key) {}

// 将 key 和 val 存入缓存

public void put(int key, int val) {}

}

get(key)方法会去缓存中查询键key,如果key存在,则返回key对应的val,否则返回 -1。

put(key, value)方法插入或修改缓存。如果key已存在,则将它对应的值改为val;如果key不存在,则插入键值对(key, val)。

当缓存达到容量capacity时,则应该在插入新的键值对之前,删除使用频次(后文用freq表示)最低的键值对。如果freq最低的键值对有多个,则删除其中最旧的那个。

思路:

需要有一个HashMap来保存key到val的映射,用来计算 get(key)

HashMap keyToVal;

需要有一个HashMap来保存key到freq的映射,用来计算每一个key的频次

HashMap keyToFreq;

算法的核心需求(思路)

需要freq到key的映射,用来找到最小freq对应的key

如果要满足上一条,快速找到最小freq是多少,可以通过一个变量minFreq来记录当前最小freq,避免遍历

可能会有多个key拥有相同的freq,所以freq和key是一对多的关系,那么就需要维护一个freq到key对应的映射关系

为了保证快速查找并删除最旧的key,就需要保证freq对应的key的映射列表应该是有顺序的

需要能够快速删除key列表中的任何一个key。如果频次为freq的key被访问了,它的频次应该变成freq+1,此时应该从freq对应的key列表中删除,并把key加入到freq+1对应的key列表中 => 就是需要提高它的频次

HashMap> freqToKeys;

int minFreq;

如果用 LinkedList 可以满足第3、4条,但是链表不能快速访问某一个节点,所以不能满足第5条快速删除

综上,LFUCache 类代码如下:

class LFUCache {

// key 到 val 的映射,我们后文称为 KV 表

HashMap keyToVal;

// key 到 freq 的映射,我们后文称为 KF 表

HashMap keyToFreq;

// freq 到 key 列表的映射,我们后文称为 FK 表

HashMap> freqToKeys;

// 记录最小的频次

int minFreq;

// 记录 LFU 缓存的最大容量

int cap;

public LFUCache(int capacity) {

keyToVal = new HashMap<>();

keyToFreq = new HashMap<>();

freqToKeys = new HashMap<>();

this.cap = capacity;

this.minFreq = 0;

}

public int get(int key) {

if (!keyToVal.containsKey(key))

return -1;

// 增加key对应的频次

increaseFreq(key);

return keyToVal.get(key);

}

public void put(int key, int val) {

// 避免初始化的时候capacity参数异常

if (this.cap <= 0)

return;

// 如果 KV 表已经存在这个key

// 修改对应的val,频次+1

if (keyToVal.containsKey(key)) {

keyToVal.put(key, val);

increaseFreq(key);

return;

}

// 如果 KV 表不存在这个key

// 并且当前容量已满,就需要删除最小频次的key

if (keyToVal.size() >= this.cap) {

removeMinFreqKey();

}

// 当前容量未满,插入key和val,并且key对应的freq置为1

keyToVal.put(key, val);

keyToFreq.put(key, 1);

// putIfAbsent 如果有这个key的话就不进行任何操作,此处相当于对1这个key进行初始化

freqToKeys.putIfAbsent(1, new LinkedHashSet<>());

freqToKeys.get(1).add(key);

// 插入最新的key之后,最小的freq肯定是1

this.minFreq = 1;

}

private void increaseFreq(int key) {

// 首先,能进入到这个函数的时候,keyToFreq 和 freqToKeys 一定保存过这个key了

// 更新 KF 表中key对应的频次+1

int freq = keyToFreq.get(key);

keyToFreq.put(key, freq + 1);

// Fk 表中删除freq对应的列表中的key

freqToKeys.get(freq).remove(key);

freqToKeys.putIfAbsent(freq + 1, new LinkedHashSet<>());

freqToKeys.get(freq + 1).add(key);

// 如果 freq 对应的列表空了,就移除这个freq

if (freqToKeys.get(freq).size() == 0) {

freqToKeys.remove(freq);

// 如果这个 freq 又恰好是 minFreq,更新 minFreq

// 这个地方容易忘记更新

if (freq == this.minFreq) {

this.minFreq++;

}

}

}

private void removeMinFreqKey() {

// FK 表中最小freq对应的列表中 最先被插入的那个 key 就是该被淘汰的 key

LinkedHashSet keyList = freqToKeys.get(this.minFreq);

int deleteKey = keyList.iterator().next();

keyList.remove(deleteKey);

// 如果 freq 对应的列表空了,就移除这个freq

if(keyList.size() == 0){

freqToKeys.remove(this.minFreq);

// 这里不需要更新 minFreq,因为后面会紧跟一个put操作,minFreq 会被置为1

}

// 更新 KV 表

keyToVal.remove(deleteKey);

// 更新 KF 表

keyToFreq.remove(deleteKey);

}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值