含义
这个缓存算法使用一个计数器来记录条目被访问的频率,通过使用LFU缓存算法,最低访问数的条目首先被移除若存在多个最低访问数的条目,那么移除加入缓存时间最久的条目。
这个缓存算法有缺陷:它无法对一个最初拥有极高访问率而后长时间不访问的条目负责。
拥有方法
🍟构造函数
public LFUCache(int capacity) {
// 初始化
}
🎃获取
获取方法时间复杂度为O(1),若对应键不存在返回-1
public int get(int key) {
}
🎈添加
添加方法时间复杂度为O(1)
若对应键不存在则加入缓存
如果容量已满则删除使用频率最低的条目,若存在多个使用频率最低的条目则删除最长时间未使用的条目
若存在则更新值
public void put(int key, int value) {
}
📄空类
class LFUCache {
public LFUCache(int capacity) {
}
public int get(int key) {
}
public void put(int key, int value) {
}
}
实现思路
双向链表+双哈希表+最低使用频率
一个哈希表存储键与节点,另一个存储频率与相同频率的链表
链表从越靠左代表越先加入。对于获取方法我们拿到最低使用频率的链表并返回首个节点即可
每一次的get操作都对频率+1
put操作
- 原本存在。修改值并且频率+1
- 原本不存在。
- 判断是否达到容量阈值,达到则删除一个最不常用的条目
- 添加新元素到频率为1的链表中
节点Node
class Node {
private int key, val, freq;
private Node pre, next;
public Node() {
this(-1, -1, 0);
}
public Node(int key, int val, int freq) {
this.key = key;
this.val = val;
this.freq = freq;
}
}
字段解释:
- key:缓存的键
- val:缓存的值
- freq:缓存的使用频率
- pre:链表中上一个节点
- next:链表中下一个节点
链表DoublyLinkedList
class DoublyLinkedList {
private final Node head, tail;
private int size;
public DoublyLinkedList() {
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
size = 0;
}
/**
* 链表是否为空
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 获取链表的首个节点
*/
public Node getFirst() {
return head.next;
}
/**
* 在链表尾部新增节点
*/
public void add(Node node) {
node.pre = tail.pre;
node.next = tail;
node.pre.next = node;
tail.pre = node;
size++;
}
/**
* 删除某一个节点
*/
public void remove(Node node) {
Node pre = node.pre, next = node.next;
next.pre = pre;
pre.next = next;
size--;
}
}
字段解释
- head:辅助头结点
- tail:辅助尾节点
头尾两个辅助节点是方便不判空,也就是每一个节点都有前后两个节点。
对于链表的顺序,从左到右排列是加入的时间先后。对于一条链表中最先加入的条目就为首个条目。
附上几张图帮助理解
链表结构
我们看到除了辅助头尾节点之外每一个节点都连接着它的上一个与下一个节点
链表添加
将原末尾节点断开连接至新节点,然后将新节点连接至辅助尾节点即可
链表删除
完整代码
class LFUCache {
/**
* 缓存容量
*/
private final int capacity;
/**
* 最小频率
*/
private int minFreq;
/**
* 频率哈希表。结构:key=freq,val=list
*/
private final Map<Integer, DoublyLinkedList> freqMap;
/**
* 键哈希表。结构:key=条目key,val=node
*/
private final Map<Integer, Node> keyMap;
public LFUCache(int capacity) {
// init
this.capacity = capacity;
minFreq = 0;
freqMap = new HashMap<>();
keyMap = new HashMap<>();
}
public int get(int key) {
// 若容量为0,或者不存在直接返回-1
if (capacity == 0 || !keyMap.containsKey(key)) {
return -1;
}
Node node = keyMap.get(key);
// 从原链表中取出
DoublyLinkedList doublyLinkedList = freqMap.get(node.freq);
doublyLinkedList.remove(node);
if (doublyLinkedList.isEmpty()) {
// 当前频率无节点,删除
freqMap.remove(node.freq);
// 当前最小频率与删除的相同那么最小频率需改变
if (minFreq == node.freq) {
minFreq++;
}
}
// 更新节点频率
node.freq++;
// 加入新链表
DoublyLinkedList defaultList = freqMap.getOrDefault(node.freq, new DoublyLinkedList());
defaultList.add(node);
freqMap.putIfAbsent(node.freq, defaultList);
return node.val;
}
public void put(int key, int value) {
// 容量为0,不做操作
if (capacity == 0) {
return;
}
// 原本存在
if (keyMap.containsKey(key)) {
Node node = keyMap.get(key);
// 除需要更新值以外其他与get操作无异
int oldVal = get(key);
node.val = value;
return;
}
// 删除节点
if (keyMap.size() == capacity) {
DoublyLinkedList doublyLinkedList = freqMap.get(minFreq);
Node first = doublyLinkedList.getFirst();
doublyLinkedList.remove(first);
if (doublyLinkedList.isEmpty()) {
// 当前频率无节点,删除
freqMap.remove(minFreq);
}
// 删除节点
keyMap.remove(first.key);
}
// 新增节点
Node node = new Node(key, value, 1);
keyMap.put(key, node);
minFreq = 1;
DoublyLinkedList doublyLinkedList = freqMap.getOrDefault(minFreq, new DoublyLinkedList());
doublyLinkedList.add(node);
freqMap.putIfAbsent(minFreq, doublyLinkedList);
}
private static class Node {
private int key, val, freq;
private Node pre, next;
public Node() {
this(-1, -1, 0);
}
public Node(int key, int val, int freq) {
this.key = key;
this.val = val;
this.freq = freq;
}
}
private static class DoublyLinkedList {
private final Node head, tail;
private int size;
public DoublyLinkedList() {
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public Node getFirst() {
return head.next;
}
public void add(Node node) {
node.pre = tail.pre;
node.next = tail;
node.pre.next = node;
tail.pre = node;
size++;
}
public void remove(Node node) {
Node pre = node.pre, next = node.next;
next.pre = pre;
pre.next = next;
size--;
}
}
}
🎉恭喜学会了LFU
此处只讲了双哈希表+双向链表的做法,还可以用哈希表+平衡二叉树的方式来实现
对于Java平衡二叉树直接使用TreeSet即可
平衡二叉树使用核心:排序的首要条件是频率次要条件是加入时间
对于排序方法,建议实现接口Comparator<Integer>
。亦或者可以在TreeSet的构造方法中写
本文完。
如有不对的地方还望指教。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/195212.html