LFU算法会淘汰使用频率最低的元素, 相同频率就淘汰最久未使用的元素
插入和查询时间复杂度都要求O(1),因此使用 链表+HashMap实现
public class LFUCache {
private Map<Integer, DataNode> dataMap;
private Map<Integer, FreqNode> freqMap;
private int capacity;
private int size;
private int minFrequency;
public LFUCache(int capacity) {
this.capacity = capacity;
freqMap = new HashMap<>();
dataMap = new HashMap<>();
}
/**
* 查询数据, 如果不存在, 返回-1
*/
public int get(int key) {
DataNode dataNode = dataMap.get(key);
if (dataNode == null) return -1; // 不存在
markUse(dataNode); // 标记频次+1
return dataNode.val;
}
public void put(int key, int value) {
DataNode dataNode = dataMap.get(key);
if (dataNode == null) {
add(key, value); // 不存在, 新增
} else {
dataNode.val = value; // 存在, 频次+1
markUse(dataNode);
}
}
// 新增节点
private void add(int key, int value) {
DataNode dataNode = new DataNode(key, value);
dataMap.put(key, dataNode);
add0(dataNode);
size++;
eliminate();
minFrequency = 0;
}
private void add0(DataNode dataNode) {
FreqNode freqNode = freqMap.get(dataNode.getFrequency());
if (freqNode == null) {
freqNode = new FreqNode(dataNode.getFrequency());
freqMap.put(freqNode.getFrequency(), freqNode);
}
freqNode.add(dataNode);
}
private void eliminate() {
if (size <= capacity) {
return;
}
size--;
FreqNode freqNode = freqMap.get(minFrequency);
DataNode remove = freqNode.tail.pre;
remove.remove();
dataMap.remove(remove.key);
if (freqNode.isEmpty()) {
freqMap.remove(minFrequency);
}
}
private void markUse(DataNode dataNode) {
int frequency = dataNode.getFrequency();
FreqNode freqNode = freqMap.get(dataNode.getFrequency());
dataNode.markUse(); // 数据节点频次+1
dataNode.remove(); // 数据节点从同频链表上移除, 增加到更惨频次的链表上
if (freqNode.isEmpty()) {
if (frequency == minFrequency) {
// 频率最低的节点没了
minFrequency = dataNode.getFrequency();
}
freqMap.remove(frequency);
}
add0(dataNode);
}
// 存储相同频率的节点
private static class FreqNode {
private DataNode head;
private DataNode tail;
private int frequency;
public FreqNode(int frequency) {
this.frequency = frequency;
this.head = new DataNode();
this.tail = new DataNode();
this.head.setNext(this.tail);
}
public int getFrequency() {
return frequency;
}
private void add(DataNode node) {
head.append(node);
}
public boolean isEmpty() {
return head.next == tail;
}
}
// 存储数据
private static class DataNode {
private int key;
private int val;
private int frequency;
private DataNode pre;
private DataNode next;
public DataNode() {
}
public int getFrequency() {
return frequency;
}
public DataNode(int key, int val) {
this.key = key;
this.val = val;
}
public void markUse() {
frequency++;
}
public void append(DataNode node) {
node.setNext(this.next);
setNext(node);
}
// 从链表上移除, 返回所在的FreqNode
public void remove() {
if (pre != null) {
pre.setNext(next);
pre = null;
}
next = null;
}
public void setNext(DataNode next) {
this.next = next;
if (next != null) {
next.pre = this;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
DataNode cur = this;
while (cur != null) {
sb.append(String.format("%s:%s:%s, ", cur.frequency, cur.key, cur.val));
cur = cur.next;
}
return sb.toString();
}
}
}

本文详细介绍了LFU(Least Frequently Used)缓存淘汰算法的实现,通过链表和HashMap结合的方式,确保了插入和查询操作的时间复杂度为O(1)。LFU算法优先淘汰使用频率最低的元素,当频率相同时,淘汰最久未使用的元素。文章还提供了完整的Java代码实现,包括LFUCache类、FreqNode类和DataNode类,展示了如何维护数据节点和频率节点的双向链表。
393

被折叠的 条评论
为什么被折叠?



