手写LFU算法(Least Frequently Used)
分析:
LFU算法相当于把数据按照访问频次进行排序,而且还有一种情况,如果多个数据拥有相同的访问词频,就应该删除最早插入的那个数据,也就是LFU算法是淘汰访问频次最低的数据,如果访问频次最低的数据有多条,需要淘汰最旧的数据
思路:
- 先从简单的开始分析,列举算法执行过程中的几个显而易见的事实
- 调用get(key)方法时,需要返回对应的val
- 只要用get或者put方法访问一次某个key,该key的freq就要加一
- 如果在容量满的时候进行插入,则需要将freq最小的key删除,如果是最小的freq对应多个key,则删除其中最旧的那个
一定要搞清楚映射关系,如果我们更新某个key对应的freq,那么就需要同步修改kf 和 fk表,这样才不会出问题
- 增加应该算是LFU算法的核心了,添加 val 和key 值,如果key已经存在,修改key对应的val 增加对应的freq 如果key不存在,需要新插入key,判断容量是否满了,满了就需要淘汰掉最小的freq,如果容量为满,插入key和val key对应的freq为1
- 删除某个键key肯定是需要同时修改三个映射表的,借助minFreq参数可以从FK表中找到freq最小的keyLIst。然后根据时序进行获取就可以了。
- 理清楚增加的操作,根据ke值获取到他对应的频率,然后再keyToVal里面进行重新的复制,这里会利用到map的特性就是key是可以相同的,然后将freqtokeys中的对应频率的表LinkedHashSet表进行修改,freq+1,如果freqToKey中有着个值对应的数据,我们只需要把当前的键继续添加进去就好了,但是还没有结束,如果freq对应的列表空了,我们就需要在表中移除这个对应的freq的键,如果这个freq == minFreq 则最小的次频++
public class LFUCache {
//key到val的映射,
HashMap<Integer, Integer> keyToVal;
//key到Freq的映射
HashMap<Integer, Integer> keyToFreq;
//Freq到key的映射,方便我们查询到次频相同数字是进行改动
HashMap<Integer, LinkedHashSet<Integer>> freqToKey;
int minFreq;
int cap;
public LFUCache(int capacity) {
keyToVal = new HashMap<>();
keyToFreq = new HashMap<>();
freqToKey = new HashMap<>();
this.cap = capacity;
this.minFreq = 0;
}
public int get(int key) {
if (!keyToFreq.containsKey(key)) {
return -1;
}
//增加key对应的freq
increaseFreq(key);
return keyToFreq.get(key);
}
public void put(int key, int val) {
if (this.cap <= 0) {
return;
}
/*若对应的key已经存在,修改对应的val即可*/
if (keyToVal.containsKey(key)) {
keyToVal.put(key,val);
increaseFreq(key);
return;
}
/*key不存在,则需要插入*/
if (this.cap <= keyToVal.size()) {
removeMinFreqKey();
}
/*插入对应的key和val 对应的freq为1*/
keyToVal.put(key,val);
keyToFreq.put(key,1);
freqToKey.putIfAbsent(1,new LinkedHashSet<>());
freqToKey.get(1).add(key);
//插入新key最小的freq肯定是1
this.minFreq = 1;
}
private void increaseFreq(int key) {
int freq = keyToFreq.get(key);
keyToFreq.put(key,freq + 1);
freqToKey.get(freq).remove(key);
freqToKey.putIfAbsent(freq + 1,new LinkedHashSet<>());
freqToKey.get(freq + 1).add(key);
//如果freq对应的列表空了,就移除这个freq
if (freqToKey.get(freq).isEmpty()) {
freqToKey.remove(freq);
//如果这个freq恰好是minFreq,就可以更新minFreq
if (freq == this.minFreq){
this.minFreq++;
}
}
}
private void removeMinFreqKey() {
LinkedHashSet<Integer> keyList = freqToKey.get(this.minFreq);
int delteKey = keyList.iterator().next();
//进行一系列数据的更新
keyList.remove(delteKey);
if (keyList.isEmpty()) {
freqToKey.remove(this.minFreq);
//这里的min需要进行更新么?
}
keyToVal.remove(delteKey);
keyToFreq.remove(delteKey);
}
}