最近在别人文章看到一片关于LFU的题目,个人觉得实现可以不同,他用的c,那我用java写一下,毕竟c已经忘的差不多了。
那个题就是让我们来设计一个数据结构,使其能够按照key-value的方式来实现put和get,而且要满足LFU,而且在空间满了之后,又要满足最近最少使用即LRU,且操作时间复杂度要满足O(1)
首先看看什么是LFU
LFU(Leatest Frequently Used)即访问频次最少被使用。当空间满了时,或者达到了阙值,那么不应该在缓存里面的数据应该是使用时间点最久远的,而且使用次数最少的。
在这里,为了满足一个操作时间复杂度,由get和put很容易就想到java里面有HashMap,这个很醒脑。
那么数据结构该怎么设计。
就这样,直接的数据缓存采用HashMap,所有的直接数据存这里。
但是还有就是关于频次的问题,采用的是一个双向链表+HashSet,但是底层又采用了HashMap。至于到底怎么回事,去看看我的文章看看他们怎么回事👍
为什么这么设计,HashMap不用管,大家都清楚,关键是这个LinkedHashSet怎么搞的。它是按照频次1,2,3,4,5…去存在HashSet里面的,然后每个频次会对应着LinkedHashSet来存储当前频次的所有节点,而且这个LinkedHashSet非常有意思,能够保持插入的顺序和读取的顺序是一样的,也就是说,如果越靠前说明是越早插入的。那么呢如果一个节点被访问了,或者被重置了,那么就将它放到频次高的,然后插入到频次高的后面,这样不仅频次高了,而且还能够满足它是频次高的且最近使用的。
总而言之,我们如果不看频次的Set,只看某一竖下来的双向链表,那么越在上面的是最久远的,要remove也是remove它。
那么来看看版本1的具体实现吧。
首先Node类,每个版本都一样的哦。
Node类:
class Node {
int key;//键
int value;//值
int freq = 1;//频率
//构造方法
public Node() {}
//构造方法
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
版本1
class LFUCache1 {
Map<Integer, Node> cacheMap;//存储缓存的内容
Map<Integer,LinkedHashSet<Node>> freqMap; //存储每个频次对应的双向链表
int size;
int capacity;
int min;//存储最小频次
LFUCache1(int capacity) {
cacheMap = new HashMap<Integer, p7.Node>(capacity);
freqMap = new HashMap<Integer, LinkedHashSet<Node>>();
this.capacity = capacity;
}
public int get(int key) {
Node node = cacheMap.get(key);
if(node == null) {
return -1;
}
freqInc(node);//增加一次频次
return node.value;
}
public void put(int key, int value) {
if(capacity == 0) {
return ;
}
Node node = cacheMap.get(key);
if(node != null) {
//已经存在了就替换掉,且增加一次频次
node.value = value;
freqInc(node);
}else {
if(size == capacity) {
//容量到达极限,移除
Node deadNode = removeNode();
cacheMap.remove(deadNode.key);
size--;
}
Node newnodeNode = new Node(key,value);
cacheMap.put(key, newnodeNode);
//没存在过,说明要增加最开始频率为1
addNode(newnodeNode);
size++;
}
}
void freqInc(Node node) {
//从原来对应的链表里面一处,并更新min;
//获取这个节点的频率
int freq = node.freq;
//获得当前节点频率找到对应频率的HashMap里面的LinkedHashSet
LinkedHashSet<Node> set = freqMap.get(freq);
//移除掉LinkedHashSet里面的这个元素
set.remove(node);
if(freq == min && set.size() == 0) {
min = freq +1;
}
//加入新freq对应的链表
node.freq++;
LinkedHashSet<Node> newSet = freqMap.get(freq+1);
if(newSet == null) {
newSet = new LinkedHashSet<>();
freqMap.put(freq+1, newSet);
}
newSet.add(node);
}
void addNode(Node node) {
LinkedHashSet<Node> set = freqMap.get(1);
if( set == null) {
set = new LinkedHashSet<p7.Node>();
freqMap.put(1, set);
}
set.add(node);
min = 1;
}
Node removeNode() {
LinkedHashSet<Node> set = freqMap.get(min);
Node deadNode = set.iterator().next();
set.remove(deadNode);
return deadNode;
}
}
版本2
Node类
class Node {
int key;
int value;
int freq = 1;
Node preNode;
Node postNode;
public Node() {}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
这个版本没有采用LinkedHashSet,而是自定义了一个双向链表
class SelfLinkedList{
Node headNode;
Node tailNode;
public SelfLinkedList() {
headNode = new Node();
tailNode = new Node();
headNode.postNode = tailNode;
tailNode.preNode = headNode;
}
void removeNode (Node node) {
node.preNode.postNode = node.postNode;
node.postNode.preNode = node.preNode;
}
void addNode (Node node) {
node.postNode = headNode.postNode;
headNode.postNode.preNode = node;
headNode.postNode = node;
node.preNode = headNode;
}
}
class LFUCache2 {
Map<Integer, Node> cacheMap;//存储缓存内容
Map<Integer,SelfLinkedList> freMap;
int size;
int capacity;
int min; //存储当前最小频次
public LFUCache2(int capacity) {
cacheMap = new HashMap<> (capacity);
freMap = new HashMap<>();
this.capacity = capacity;
}
public int get(int key) {
Node node = cacheMap.get(key);
if(node == null) {
return -1;
}
freqInc(node);
return node.value;
}
public void put(int key, int value) {
if(capacity == 0) {
return;
}
Node node = cacheMap.get(key);
if(node != null) {
node.value = value;
freqInc(node);
}else {
if(size == capacity) {
SelfLinkedList minFreqLinkedList = freMap.get(min);
cacheMap.remove(minFreqLinkedList.tail.pre.key);
minFreqLinkedList.removeNode(minFreqLinkedList.tail.pre);
size--;
}
Node newNode = new Node(key,value);
cacheMap.put(key, newNode);
SelfLinkedList linkedList = freMap.get(1);
if(linkedList == null) {
linkedList = new SelfLinkedList();
freMap.put(1,linkedList);
}
linkedList.addNode(newNode);
size++;
min = 1;
}
}
void freqInc(Node node) {
//先从原链表删除,然后加入频次更高的链表里面
int freq = node.freq;
SelfLinkedList linkedList = freMap.get(freq);
linkedList.removeNode(node);
if(freq == min && linkedList.headNode.postNode == linkedList.tailNode) {
min = freq + 1;
}
node.freq++;
linkedList = freMap.get(freq+1);
if(linkedList == null) {
linkedList = new SelfLinkedList();
freMap.put(freq+1, linkedList);
}
linkedList.addNode(node);
}
}