LFU即最不经常使用。LFU算法的思想是一定时期内被访问次数最少的节点,在将来被访问到的几率也是最小的。当缓存达到上限,再插入新的数据,需要将访问频次最小的数据删除。LFU强调的是访问次数,而LRU强调的是访问时间

LFU优点:

  相比LRU,LFU的缓存命中率高

缺点:

  第一,LFU要记录每个key的频次,会浪费空间;第二,对时间不敏感,某个数据曾经访问频次很高,但是过了时间点,该数据不被访问,由于当初的访问频次高,该数据会在相当长的一段时间内常驻内存。

双哈希表实现LFU

第一个哈希表是 freq_table,它的key是访问的频次,它的value是一个双向链表,双向链表的每一个节点包含三个元素:key,value,freq。

第二个哈希表是 key_table,它的key是双向链表中存储的key,value是对应节点的迭代器。

如图所示,假设缓存的内存是3,初始时连续插入三个键值对,(1,1)(2,2)(3,3),他们的频次都是1,记录最小频次minfreq=1;

 

当我们访问了某个键值对,就要将对应的的频次+1,将结点移动到修改后的频次所对应的链表上。例如get(1)后,key1所对应的频次为2,将(1,1,2)结点挂在freq_table上频次为2的链表上。记录最小频次minfreq=1;

 当再插入(4,4,1)时,容量已经溢出,要删除频次最少,插入时间最晚的,也就是(3,3,1),记录最小频次minfreq=1;

 代码定义链表结点类型

 

struct Node
{
    int key,val,freq;
    Node(int k,int v,int f):key(k),val(v),freq(f){}
};
class Solution {
public:
  
    int minfreq=0;
    unordered_map<int, list<Node>::iterator>key_table;
    unordered_map<int, list<Node>>freq_table;
    void set(int key,int val,int cap)//插入元素
    {
        if(cap==0) return;
        auto it=key_table.find(key);
       case1:缓存中没有待插入的数据
        if(it==key_table.end())
        {
            缓存已经满了
            if(key_table.size()==cap)
            {//找到频次最小的链表中的最后一个数据
                auto it=freq_table[minfreq].back(); 
                //删除key_table中该数据key
                key_table.erase(it.key);
                freq_table[minfreq].pop_back();//删除结点
                //如果该频次所对应的链表为空,删除键值对
                if(freq_table[minfreq].size()==0)
                    freq_table.erase(minfreq);
            }
            //新插入的数据频次都是1
            freq_table[1].push_front(Node(key,val,1));
            //修改迭代器的指向
            key_table[key]=freq_table[1].begin();
            minfreq=1; //修改最小频次
        }else//缓存中有插入数据,修改value和freq即可
        {
            auto it=key_table[key];
            int freq=it->freq;
            freq_table[freq].erase(it);
            if(freq_table[freq].size()==0)
            {
                freq_table.erase(key);
                if(minfreq==freq)
                    minfreq++;
            }
            freq_table[freq+1].push_front(Node(key,val,freq+1));
            key_table[key]=freq_table[freq+1].begin();
        }
    }
   //找到返回对应的value,修改频次,将它挂在正确的freq_table后面,
 //修改key_table的value。没有找到返回-1
    int get(int key)
    {
        auto it=key_table.find(key);
        if(it==key_table.end()) return -1;
        list<Node>::iterator node=it->second;
        int value=node->val;
        int freq=node->freq;
        freq_table[freq].erase(node);
        if(freq_table[freq].size()==0)
        { 
            freq_table.erase(freq);
            if(minfreq==freq)
                minfreq+=1;
        }
        freq_table[freq+1].push_front(Node(key,value,freq+1));
        key_table[key]=freq_table[freq+1].begin();
        return value;
    }