日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

高级数据结构与算法 | LFU缓存机制(Least Frequently Used)

發布時間:2024/4/11 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高级数据结构与算法 | LFU缓存机制(Least Frequently Used) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • LFUCache
  • 結構設計
  • LFUCache的實現


在之前我寫過一篇LRU的博客,如果不了解的建議先看看這篇
高級數據結構與算法 | LRU緩存機制(Least Recently Used)

LFUCache

LFU(Least Frequently Used),即最不經常使用頁置換算法。

如果一個數據在最近一段時間很少被訪問到,那么可以認為在將來它被訪問的可能性也很小。因此,當空間滿時,最小頻率訪問的數據最先被淘汰,這就是LFU的核心思想。

如果我們想要設計一個LFU緩存算法,就需要滿足以下功能

  • 初始化時設置緩存大小,當緩存已滿后插入元素時需要淘汰現有元素來置換新元素
  • get(key):如果緩存中存在key,則獲取value,并更新其使用次數
  • put(key, value) :如果key已經存在,則更新key所對應的value。如果不存在則插入鍵值對,但是插入時如果緩存已滿,則需要將最不常用的鍵值對淘汰,置換新數據。如果存在多個使用頻率相同的鍵值對,應當選取最久沒有使用的。
  • 保證get和put的操作時間復雜度都為O(1)
class LFUCache { public:LFUCache(int capacity) {}int get(int key) {}void put(int key, int value) {} };

根據以上條件,我們在上面給出了一個算法的大致框架,下面就結合框架以及條件,來設計我們具體的一個結構


結構設計

由于LFU需要根據使用次數來進行緩存淘汰的判斷,因此我們每一項中不僅需要存儲key,value,還需要存儲使用次數freq

每一項的節點設計如下

class LFUNode { public:LFUNode(int key, int value, int freq): _key(key), _value(value), _freq(freq){}int _key; int _value;int _freq; //訪問次數 };

在上面列的幾個條件中,其中最難處理的地方其實就是O(1)的get和put,因為沒有任何一個獨立的數據結構能夠滿足這個條件,因此我們需要組合使用多個結構。

如果要保證put中的插入和刪除操作達到O(1),那么我們只能選擇使用鏈表來記錄我們的緩存信息,但是這次與之前的LRU并不相同,我們在保證使用次數最少的同時,還需要保證淘汰的時同次數中最久沒有使用的,這就意味這我們需要根據不同的使用次數,來維護多個鏈表,并且每個鏈表中按照使用時間進行排序。

上面的設計依舊存在著一些問題,因為我們在對一個元素進行操作后,它的使用次數就會發生變化,那么我們就需要將其從原先的鏈表中移動到下一個鏈表,那么我們如何能夠保證O(1)的查找到它,并對他進行操作呢?同時,我們如何能夠快速的在多個鏈表中,查找到它所在的那個鏈表,以及它即將要去的鏈表呢?因此,我們還需要結合其他的數據結構來完成這個問題

要保證O(1)的查找鏈表,這時候就需要用到哈希結構了,我們可以將使用次數freq與每個保存該freq節點的鏈表建立起映射,保證O(1)的獲取鏈表。

如果要保證O(1)的從指定鏈表中找到元素,并對其進行操作,我們可以將key與鏈表中對應元素的迭代器建立起映射,當我們通過key找到迭代器的時候,就可以利用迭代器對鏈表進行操作。

因此,最終LFUCache的結構設計如下

class LFUCache { public:LFUCache(int capacity) : _capacity(capacity), _minFreq(0){}int get(int key) {}void put(int key, int value) {}private:unordered_map<int, list<LFUNode>::iterator> _keyTable; //建立key與節點迭代器的映射unordered_map<int, list<LFUNode>> _freqTable; //建立freq與該值鏈表的映射int _minFreq; //保存當前最小的freq,便于淘汰操作int _capacity; //緩存最大容量 };

LFUCache的實現

接下來就該實現get和put功能了,我先列出這兩個功能實現的核心思路

get

  • 根據keyTable查找到節點的迭代器,不存在則直接返回
  • 更新使用次數,通過迭代器將節點從原鏈表中刪除,并通過freqTable獲取下一個鏈表,將其頭插進鏈表中,保證最后使用的在最前面,使得鏈表按照使用時間倒序排序
  • 將新鏈表中該節點的迭代器更新到keyTable中

put

  • 根據keyTable查找到節點的迭代器,存在則說明是更新,不存在則說明是淘汰
  • 如果是更新,那么操作的內容和上面的get大體相同
  • 如果是插入,需要判斷當前緩存是否已滿,是否需要淘汰,如果未滿則直接插入freq=1的鏈表中,并將迭代器插入至keyTable中
  • 如果緩存已滿,則需要根據minFreq在freqTable中找到對應的最小freq鏈表,并刪除隊尾,即最久沒有使用的元素,同時在keyTable中淘汰該元素

這里我就將每一步的思路寫在下面代碼的注釋中

完整的實現代碼如下

class LFUNode { public:LFUNode(int key, int value, int freq): _key(key), _value(value), _freq(freq){}int _key; int _value;int _freq; //訪問次數 };class LFUCache { public:LFUCache(int capacity) : _capacity(capacity), _minFreq(0){}int get(int key) {if(_capacity == 0){return -1;}auto it = _keyTable.find(key); //查找到對應的節點if(it != _keyTable.end()){list<LFUNode>::iterator node = it->second; //記錄節點的value以及freqint value = node->_value, freq = node->_freq;_freqTable[freq].erase(node); //從freq表中的對應鏈表刪除當前節點if(_freqTable[freq].empty() && _minFreq == freq) //如果刪除后鏈表為空,則判斷是否需要更新最小freq{_minFreq++;}_freqTable[freq + 1].push_front(LFUNode(key, value, freq + 1)); //將更新后的節點頭插入下一個freq的鏈表中,保證按照插入時間排序it->second = _freqTable[freq + 1].begin(); //更新迭代器return value;}//查找不到直接返回-1else{return -1;}}void put(int key, int value) {if(_capacity == 0){return;}auto it = _keyTable.find(key); //查找key是否存在,判斷是更新還是插入if(it != _keyTable.end()) //如果存在,則是更新{list<LFUNode>::iterator node = it->second;int freq = node->_freq;_freqTable[freq].erase(node);if(_freqTable[freq].empty() && _minFreq == freq){_minFreq++;}_freqTable[freq + 1].push_front(LFUNode(key, value, freq + 1));it->second = _freqTable[freq + 1].begin();}else //不存在則是插入{//如果緩存滿了,則要先刪除后才能插入if(_keyTable.size() == _capacity){//查詢freq表,獲取freq最少且最久沒有使用的節點,即freq鏈表的尾部節點LFUNode node = _freqTable[_minFreq].back();_keyTable.erase(node._key); //刪除key表中的節點_freqTable[node._freq].pop_back(); //刪除freq表中的節點}_freqTable[1].push_front(LFUNode(key, value, 1)); //在freq表中插入新節點_keyTable.insert(make_pair(key, _freqTable[1].begin()));//在key表中插入新節點_minFreq = 1; //插入新元素,它為最小的}} private:unordered_map<int, list<LFUNode>::iterator> _keyTable; //建立key與節點迭代器的映射unordered_map<int, list<LFUNode>> _freqTable; //建立freq與該值鏈表的映射int _minFreq; //保存當前最小的freq,便于淘汰int _capacity; //緩存最大容量 };

總結

以上是生活随笔為你收集整理的高级数据结构与算法 | LFU缓存机制(Least Frequently Used)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。