在上一篇文章中,我们介绍LRU和LFU的概念、共同点及其主要差别,并且详细介绍了LRU的C++实现。本文将继续对LFU进行分析与详解,并给出C++实现代码。
与LRU纯粹比较数据使用时间与当前时间的远近不同,LFU的核心思想如果数据在最近一段时间内访问次数越少,那么将来被访问的几率也越低。因此在设计LFU时,我们需要引进变量:频率frequency,并且需要根据频率值来决定节点的删除操作。这里需要注意的是:当几个节点的频率都相同时,采用LRU的思想,优先删除的是最近最少使用的节点。
根据以上的分析,首先我们需要在节点上增加一个变量freq记录节点的频率。其次,除了LRU中key与Node的map之外,对于每一个频率值,我们还需要一个list 来记录在这一频率值下的节点。因此我们增加一个频率与list的map:freqMap。另外,为了迅速找到并删除节点,我们在Node结构中增加iterator,记录frequency list的头部。
节点的定义和LFU的结构如下:
struct Node{
int val;
int freq;
list<int>::iterator it;
Node():freq(1){}
Node(int value):val(value),freq(1){}
};
class LFUCache {
public:
LFUCache(int capacity) {
this->capacity = capacity;
}
private:
int capacity;
int minFreq;
unordered_map<int,Node*> cacheMap;
unordered_map<int,list<int>> freqMap;
};
get(int key)
函数的设计与LRU中get函数类似。需要注意的是这里更新节点不再是将节点移到到链表尾部,而是更新节点的freq,将原节点从以前的freq list中移除,添加到新的freq list前端,最后更新节点的iterator。
int get(int key) {
//if not exist
if(cacheMap.find(key) == cacheMap.end()) return -1;
Node* node = cacheMap[key];
//update freq
freqMap[node->freq].erase(node->it);
node->freq += 1;
freqMap[node->freq].push_front(key);
node->it = freqMap[node->freq].begin();
if(freqMap[minFreq].size()==0)
minFreq = node->freq;
return node->val;
}
put(int key, int value)
的设计同样需要注意对freq list的处理。
void put(int key, int value) {
if(capacity <= 0) return;
if(get(key) == -1)
{
if(cacheMap.size()==capacity)
{
//remove
int popkey = freqMap[minFreq].back();
cacheMap.erase(popkey);
freqMap[minFreq].pop_back();
}
//insert new node
Node* node = new Node(value);
minFreq = 1;
freqMap[1].push_front(key);
node->it = freqMap[1].begin();
cacheMap[key] = node;
}
else
{
cacheMap[key]->val = value;
}
}
代码清单: https://github.com/ShulinLiu/DataStructure-Algorithm/blob/master/LeetCode/LFUCache.hpp
以上就是LRU和LFU详解的全部内容。感谢关注。