在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文讀懂緩存淘汰算法:LFU 算法

算法與數(shù)據(jù)結(jié)構(gòu) ? 來(lái)源:labuladong ? 作者:labuladong ? 2020-08-25 17:37 ? 次閱讀

作者:labuladong

上篇文章算法題就像搭樂(lè)高:手把手帶你拆解 LRU 算法寫(xiě)了 LRU 緩存淘汰算法的實(shí)現(xiàn)方法,本文來(lái)寫(xiě)另一個(gè)著名的緩存淘汰算法:LFU 算法。

從實(shí)現(xiàn)難度上來(lái)說(shuō),LFU 算法的難度大于 LRU 算法,因?yàn)?LRU 算法相當(dāng)于把數(shù)據(jù)按照時(shí)間排序,這個(gè)需求借助鏈表很自然就能實(shí)現(xiàn),你一直從鏈表頭部加入元素的話(huà),越靠近頭部的元素就是新的數(shù)據(jù),越靠近尾部的元素就是舊的數(shù)據(jù),我們進(jìn)行緩存淘汰的時(shí)候只要簡(jiǎn)單地將尾部的元素淘汰掉就行了。

而 LFU 算法相當(dāng)于是淘汰訪問(wèn)頻次最低的數(shù)據(jù),如果訪問(wèn)頻次最低的數(shù)據(jù)有多條,需要淘汰最舊的數(shù)據(jù)。把數(shù)據(jù)按照訪問(wèn)頻次進(jìn)行排序,而且頻次還會(huì)不斷變化,這可不容易實(shí)現(xiàn)。

所以說(shuō) LFU 算法要復(fù)雜很多,labuladong 進(jìn)字節(jié)跳動(dòng)的時(shí)候就被面試官問(wèn)到了 LFU 算法。

話(huà)說(shuō)回來(lái),這種著名的算法的套路都是固定的,關(guān)鍵是由于邏輯較復(fù)雜,不容易寫(xiě)出漂亮且沒(méi)有 bug 的代碼。

那么本文 labuladong 就帶你拆解 LFU 算法,自頂向下,逐步求精。

一、算法描述

要求你寫(xiě)一個(gè)類(lèi),接受一個(gè)capacity參數(shù),實(shí)現(xiàn)get和put方法:

classLFUCache{
//構(gòu)造容量為capacity的緩存
publicLFUCache(intcapacity){}
//在緩存中查詢(xún)key
publicintget(intkey){}
//將key和val存入緩存
publicvoidput(intkey,intval){}
}

get(key)方法會(huì)去緩存中查詢(xún)鍵key,如果key存在,則返回key對(duì)應(yīng)的val,否則返回 -1。

put(key, value)方法插入或修改緩存。如果key已存在,則將它對(duì)應(yīng)的值改為val;如果key不存在,則插入鍵值對(duì)(key, val)。

當(dāng)緩存達(dá)到容量capacity時(shí),則應(yīng)該在插入新的鍵值對(duì)之前,刪除使用頻次(后文用freq表示)最低的鍵值對(duì)。如果freq最低的鍵值對(duì)有多個(gè),則刪除其中最舊的那個(gè)。

//構(gòu)造一個(gè)容量為2的LFU緩存
LFUCachecache=newLFUCache(2);

//插入兩對(duì)(key,val),對(duì)應(yīng)的freq為1
cache.put(1,10);
cache.put(2,20);

//查詢(xún)key為1對(duì)應(yīng)的val
//返回10,同時(shí)鍵1對(duì)應(yīng)的freq變?yōu)?
cache.get(1);

//容量已滿(mǎn),淘汰freq最小的鍵2
//插入鍵值對(duì)(3,30),對(duì)應(yīng)的freq為1
cache.put(3,30);

//鍵2已經(jīng)被淘汰刪除,返回-1
cache.get(2);

二、思路分析

一定先從最簡(jiǎn)單的開(kāi)始,根據(jù) LFU 算法的邏輯,我們先列舉出算法執(zhí)行過(guò)程中的幾個(gè)顯而易見(jiàn)的事實(shí):

1、調(diào)用get(key)方法時(shí),要返回該key對(duì)應(yīng)的val。

2、只要用get或者put方法訪問(wèn)一次某個(gè)key,該key的freq就要加一。

3、如果在容量滿(mǎn)了的時(shí)候進(jìn)行插入,則需要將freq最小的key刪除,如果最小的freq對(duì)應(yīng)多個(gè)key,則刪除其中最舊的那一個(gè)。

好的,我們希望能夠在 O(1) 的時(shí)間內(nèi)解決這些需求,可以使用基本數(shù)據(jù)結(jié)構(gòu)來(lái)逐個(gè)擊破:

1、使用一個(gè)HashMap存儲(chǔ)key到val的映射,就可以快速計(jì)算get(key)。

HashMapkeyToVal;

2、使用一個(gè)HashMap存儲(chǔ)key到freq的映射,就可以快速操作key對(duì)應(yīng)的freq。

HashMapkeyToFreq;

3、這個(gè)需求應(yīng)該是 LFU 算法的核心,所以我們分開(kāi)說(shuō)。

3.1首先,肯定是需要freq到key的映射,用來(lái)找到freq最小的key。

3.2、將freq最小的key刪除,那你就得快速得到當(dāng)前所有key最小的freq是多少。想要時(shí)間復(fù)雜度 O(1) 的話(huà),肯定不能遍歷一遍去找,那就用一個(gè)變量minFreq來(lái)記錄當(dāng)前最小的freq吧。

3.3、可能有多個(gè)key擁有相同的freq,所以freq對(duì)key是一對(duì)多的關(guān)系,即一個(gè)freq對(duì)應(yīng)一個(gè)key的列表。

3.4、希望freq對(duì)應(yīng)的key的列表是存在時(shí)序的,便于快速查找并刪除最舊的key。

3.5、希望能夠快速刪除key列表中的任何一個(gè)key,因?yàn)槿绻l次為freq的某個(gè)key被訪問(wèn),那么它的頻次就會(huì)變成freq+1,就應(yīng)該從freq對(duì)應(yīng)的key列表中刪除,加到freq+1對(duì)應(yīng)的key的列表中。

HashMap>freqToKeys;
intminFreq=0;

介紹一下這個(gè)LinkedHashSet,它滿(mǎn)足我們 3.3,3.4,3.5 這幾個(gè)要求。你會(huì)發(fā)現(xiàn)普通的鏈表LinkedList能夠滿(mǎn)足 3.3,3.4 這兩個(gè)要求,但是由于普通鏈表不能快速訪問(wèn)鏈表中的某一個(gè)節(jié)點(diǎn),所以無(wú)法滿(mǎn)足 3.5 的要求。

LinkedHashSet顧名思義,是鏈表和哈希集合的結(jié)合體。鏈表不能快速訪問(wèn)鏈表節(jié)點(diǎn),但是插入元素具有時(shí)序;哈希集合中的元素?zé)o序,但是可以對(duì)元素進(jìn)行快速的訪問(wèn)和刪除。

那么,它倆結(jié)合起來(lái)就兼具了哈希集合和鏈表的特性,既可以在 O(1) 時(shí)間內(nèi)訪問(wèn)或刪除其中的元素,又可以保持插入的時(shí)序,高效實(shí)現(xiàn) 3.5 這個(gè)需求。

綜上,我們可以寫(xiě)出 LFU 算法的基本數(shù)據(jù)結(jié)構(gòu):

classLFUCache{
//key到val的映射,我們后文稱(chēng)為KV表
HashMapkeyToVal;
//key到freq的映射,我們后文稱(chēng)為KF表
HashMapkeyToFreq;
//freq到key列表的映射,我們后文稱(chēng)為FK表
HashMap>freqToKeys;
//記錄最小的頻次
intminFreq;
//記錄LFU緩存的最大容量
intcap;

publicLFUCache(intcapacity){
keyToVal=newHashMap<>();
keyToFreq=newHashMap<>();
freqToKeys=newHashMap<>();
this.cap=capacity;
this.minFreq=0;
}

publicintget(intkey){}

publicvoidput(intkey,intval){}

}

三、代碼框架

LFU 的邏輯不難理解,但是寫(xiě)代碼實(shí)現(xiàn)并不容易,因?yàn)槟憧次覀円S護(hù)KV表,KF表,F(xiàn)K表三個(gè)映射,特別容易出錯(cuò)。對(duì)于這種情況,labuladong 教你三個(gè)技巧:

1、不要企圖上來(lái)就實(shí)現(xiàn)算法的所有細(xì)節(jié),而應(yīng)該自頂向下,逐步求精,先寫(xiě)清楚主函數(shù)的邏輯框架,然后再一步步實(shí)現(xiàn)細(xì)節(jié)。

2、搞清楚映射關(guān)系,如果我們更新了某個(gè)key對(duì)應(yīng)的freq,那么就要同步修改KF表和FK表,這樣才不會(huì)出問(wèn)題。

3、畫(huà)圖,畫(huà)圖,畫(huà)圖,重要的話(huà)說(shuō)三遍,把邏輯比較復(fù)雜的部分用流程圖畫(huà)出來(lái),然后根據(jù)圖來(lái)寫(xiě)代碼,可以極大減少出錯(cuò)的概率。

下面我們先來(lái)實(shí)現(xiàn)get(key)方法,邏輯很簡(jiǎn)單,返回key對(duì)應(yīng)的val,然后增加key對(duì)應(yīng)的freq:

publicintget(intkey){
if(!keyToVal.containsKey(key)){
return-1;
}
//增加key對(duì)應(yīng)的freq
increaseFreq(key);
returnkeyToVal.get(key);
}

增加key對(duì)應(yīng)的freq是 LFU 算法的核心,所以我們干脆直接抽象成一個(gè)函數(shù)increaseFreq,這樣get方法看起來(lái)就簡(jiǎn)潔清晰了對(duì)吧。

下面來(lái)實(shí)現(xiàn)put(key, val)方法,邏輯略微復(fù)雜,我們直接畫(huà)個(gè)圖來(lái)看:

一文讀懂緩存淘汰算法:LFU 算法

這圖就是隨手畫(huà)的,不是什么正規(guī)的程序流程圖,但是算法邏輯一目了然,看圖可以直接寫(xiě)出put方法的邏輯:

publicvoidput(intkey,intval){
if(this.cap<=?0)?return;

????/*?若?key?已存在,修改對(duì)應(yīng)的?val?即可?*/
????if?(keyToVal.containsKey(key))?{
????????keyToVal.put(key,?val);
????????//?key?對(duì)應(yīng)的?freq?加一
????????increaseFreq(key);
????????return;
????}

????/*?key?不存在,需要插入?*/
????/*?容量已滿(mǎn)的話(huà)需要淘汰一個(gè)?freq?最小的?key?*/
????if?(this.cap?<=?keyToVal.size())?{
????????removeMinFreqKey();
????}

????/*?插入?key?和?val,對(duì)應(yīng)的?freq?為?1?*/
????//?插入?KV?表
????keyToVal.put(key,?val);
????//?插入?KF?表
????keyToFreq.put(key,?1);
????//?插入?FK?表
????freqToKeys.putIfAbsent(1,?new?LinkedHashSet<>());
freqToKeys.get(1).add(key);
//插入新key后最小的freq肯定是1
this.minFreq=1;
}

increaseFreq和removeMinFreqKey方法是 LFU 算法的核心,我們下面來(lái)看看怎么借助KV表,KF表,F(xiàn)K表這三個(gè)映射巧妙完成這兩個(gè)函數(shù)。

四、LFU 核心邏輯

首先來(lái)實(shí)現(xiàn)removeMinFreqKey函數(shù):

privatevoidremoveMinFreqKey(){
//freq最小的key列表
LinkedHashSetkeyList=freqToKeys.get(this.minFreq);
//其中最先被插入的那個(gè)key就是該被淘汰的key
intdeletedKey=keyList.iterator().next();
/*更新FK表*/
keyList.remove(deletedKey);
if(keyList.isEmpty()){
freqToKeys.remove(this.minFreq);
//問(wèn):這里需要更新 minFreq 的值嗎?
}
/*更新KV表*/
keyToVal.remove(deletedKey);
/*更新KF表*/
keyToFreq.remove(deletedKey);
}

刪除某個(gè)鍵key肯定是要同時(shí)修改三個(gè)映射表的,借助minFreq參數(shù)可以從FK表中找到freq最小的keyList,根據(jù)時(shí)序,其中第一個(gè)元素就是要被淘汰的deletedKey,操作三個(gè)映射表刪除這個(gè)key即可。

但是有個(gè)細(xì)節(jié)問(wèn)題,如果keyList中只有一個(gè)元素,那么刪除之后minFreq對(duì)應(yīng)的key列表就為空了,也就是minFreq變量需要被更新。如何計(jì)算當(dāng)前的minFreq是多少呢?

實(shí)際上沒(méi)辦法快速計(jì)算minFreq,只能線(xiàn)性遍歷FK表或者KF表來(lái)計(jì)算,這樣肯定不能保證 O(1) 的時(shí)間復(fù)雜度。

但是,其實(shí)這里沒(méi)必要更新minFreq變量,因?yàn)槟阆胂雛emoveMinFreqKey這個(gè)函數(shù)是在什么時(shí)候調(diào)用?在put方法中插入新key時(shí)可能調(diào)用。而你回頭看put的代碼,插入新key時(shí)一定會(huì)把minFreq更新成 1,所以說(shuō)即便這里minFreq變了,我們也不需要管它。

下面來(lái)實(shí)現(xiàn)increaseFreq函數(shù):

privatevoidincreaseFreq(intkey){
intfreq=keyToFreq.get(key);
/*更新KF表*/
keyToFreq.put(key,freq+1);
/*更新FK表*/
//將key從freq對(duì)應(yīng)的列表中刪除
freqToKeys.get(freq).remove(key);
//將key加入freq+1對(duì)應(yīng)的列表中
freqToKeys.putIfAbsent(freq+1,newLinkedHashSet<>());
freqToKeys.get(freq+1).add(key);
//如果freq對(duì)應(yīng)的列表空了,移除這個(gè)freq
if(freqToKeys.get(freq).isEmpty()){
freqToKeys.remove(freq);
//如果這個(gè)freq恰好是minFreq,更新minFreq
if(freq==this.minFreq){
this.minFreq++;
}
}
}

更新某個(gè)key的freq肯定會(huì)涉及FK表和KF表,所以我們分別更新這兩個(gè)表就行了。

和之前類(lèi)似,當(dāng)FK表中freq對(duì)應(yīng)的列表被刪空后,需要?jiǎng)h除FK表中freq這個(gè)映射。如果這個(gè)freq恰好是minFreq,說(shuō)明minFreq變量需要更新。

能不能快速找到當(dāng)前的minFreq呢?這里是可以的,因?yàn)槲覀儎偛虐裬ey的freq加了 1 嘛,所以minFreq也加 1 就行了。

至此,經(jīng)過(guò)層層拆解,LFU 算法就完成了。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4702

    瀏覽量

    94941
  • 數(shù)據(jù)結(jié)構(gòu)

    關(guān)注

    3

    文章

    573

    瀏覽量

    40642

原文標(biāo)題:算法題就像搭樂(lè)高:手把手帶你拆解 LFU 算法

文章出處:【微信號(hào):TheAlgorithm,微信公眾號(hào):算法與數(shù)據(jù)結(jié)構(gòu)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    《CDN 之我見(jiàn)》系列二:原理篇(緩存、安全)

    Hash 運(yùn)算都得到同個(gè)余數(shù)),則性能與單鏈表無(wú)異,查找時(shí)間復(fù)雜度是 O(n)。如果磁盤(pán)空間不夠了怎么辦?使用基于訪問(wèn)熱度的內(nèi)容淘汰算法,例如 FIFO、LRU、LFU、SLRU、
    發(fā)表于 06-12 16:59

    深度學(xué)習(xí)RCNN算法

    目標(biāo)檢測(cè)算法圖解:看懂RCNN系列算法
    發(fā)表于 08-29 09:50

    讀懂接口模塊的組合應(yīng)用有哪些?

    讀懂接口模塊的組合應(yīng)用有哪些?
    發(fā)表于 05-17 07:15

    讀懂什么是NEC協(xié)議

    讀懂什么是NEC協(xié)議?
    發(fā)表于 10-15 09:22

    星上交換系統(tǒng)輸入緩存調(diào)度算法

    為改善星上交換系統(tǒng)的性能,該文提出了種新的輸入緩存調(diào)度算法。該算法基于Crossbar 交換結(jié)構(gòu),采用了串行調(diào)度思想,在兼顧每個(gè)端口公平性的基礎(chǔ)上調(diào)整了輸出端口的仲裁策
    發(fā)表于 11-17 13:52 ?10次下載

    HSDPA系統(tǒng)中種感知用戶(hù)終端緩存狀態(tài)的QoE保障調(diào)度算法

    針對(duì)HSDPA系統(tǒng)中現(xiàn)有調(diào)度算法無(wú)法滿(mǎn)足實(shí)時(shí)業(yè)務(wù)QoE的缺點(diǎn),提出種保障實(shí)時(shí)業(yè)務(wù)QoE的調(diào)度算法。該算法根據(jù)用戶(hù)反饋的信道質(zhì)量信息和在基站獲取到的用戶(hù)終端
    發(fā)表于 01-08 15:24 ?0次下載

    基于BCH算法的高速緩存糾檢錯(cuò)方案研究

    基于BCH算法的高速緩存糾檢錯(cuò)方案研究
    發(fā)表于 01-07 20:32 ?0次下載

    讀懂數(shù)據(jù)結(jié)構(gòu)中的算法

    在進(jìn)步學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法前,我們應(yīng)該先掌握算法分析的般方法。算法分析主要包括對(duì)算法的時(shí)空復(fù)雜
    的頭像 發(fā)表于 11-15 15:19 ?3600次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>數(shù)據(jù)結(jié)構(gòu)中的<b class='flag-5'>算法</b>

    基于DASH的混合控制碼率算法

    針對(duì)平滑流(SF)算法在帶寬預(yù)測(cè)時(shí)存在的毛刺現(xiàn)象以及僅依靠帶寬預(yù)測(cè)而沒(méi)有緩存區(qū)控制所導(dǎo)致的頻繁播放停滯的問(wèn)題,提出種動(dòng)態(tài)自適應(yīng)混合控制碼率算法。首先,通過(guò)使用標(biāo)準(zhǔn)差來(lái)代替原SF
    發(fā)表于 11-24 16:54 ?0次下載
    基于DASH的混合控制碼率<b class='flag-5'>算法</b>

    基于密策略屬性基加密系統(tǒng)訪問(wèn)機(jī)制的緩存替換策略

    為提高基于密策略屬性基加密( CP-ABE)系統(tǒng)的數(shù)據(jù)緩存性能,針對(duì)CP-ABE加密的數(shù)據(jù),提出種有效的緩存替換算法最小屬性?xún)r(jià)值(MAV
    發(fā)表于 11-25 11:13 ?0次下載

    讀懂幾種常用的安全算法

    摘要算法 ? 對(duì)稱(chēng)加密算法 ? 非對(duì)稱(chēng)加密算法 ? 數(shù)字簽名 ? 數(shù)字證書(shū) 數(shù)字摘要 實(shí)現(xiàn) ? 將任意長(zhǎng)度的明文通過(guò)單向hash函數(shù)摘要成固定長(zhǎng)度的串。 Hash(明文)--固定長(zhǎng)度的摘要 特點(diǎn)
    發(fā)表于 05-30 01:59 ?1932次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>幾種常用的安全<b class='flag-5'>算法</b>

    基于哈希算法和近鄰算法緩存數(shù)據(jù)選擇策略

    針對(duì)終端用戶(hù)產(chǎn)生大量相同或相似計(jì)算請(qǐng)求的情況,可以通過(guò)近似匹配在邊緣服務(wù)器緩存空間中查找相似數(shù)據(jù),選取可復(fù)用的計(jì)算結(jié)果?,F(xiàn)有算法大多未考慮數(shù)據(jù)分布不均的問(wèn)題,導(dǎo)致計(jì)算量和時(shí)間開(kāi)銷(xiāo)較大,對(duì)此
    發(fā)表于 04-19 15:11 ?3次下載
    基于哈希<b class='flag-5'>算法</b>和近鄰<b class='flag-5'>算法</b>的<b class='flag-5'>緩存</b>數(shù)據(jù)選擇策略

    緩存敏感的多屬性不等值連接操作算法

    緩存敏感的多屬性不等值連接操作算法
    發(fā)表于 06-25 16:16 ?5次下載

    讀懂經(jīng)典雙目稠密匹配算法SGM

    最近來(lái)看看些雙目稠密匹配的算法。說(shuō)來(lái)慚愧,SGM在航測(cè)領(lǐng)域是很重要的算法(當(dāng)然也是最好的雙目稠密匹配算法),自己卻沒(méi)有認(rèn)真讀過(guò),只是大
    的頭像 發(fā)表于 12-15 15:12 ?1899次閱讀

    讀懂,什么是BLE?

    讀懂,什么是BLE?
    的頭像 發(fā)表于 11-27 17:11 ?3339次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>,什么是BLE?
    主站蜘蛛池模板: 免费爱爱网 | 五月天丁香婷 | 亚洲伊人久久大香线蕉综合图片 | 国产1区2区三区不卡 | 色香蕉在线观看 | 日本欧美一区二区三区不卡视频 | 午夜小片 | 美女视频黄视大全视频免费网址 | 成 人色 网 站999 | 成年人毛片网站 | 欧美影院一区二区 | 老司机午夜网站 | 人人揉人人爽五月天视频 | 国产女主播精品大秀系列在线 | 欧美一级做一a做片性视频 欧美一级做一级做片性十三 | 天天操夜夜摸 | 婷婷月| 未满十八18周岁禁止免费国产 | 亚洲综合成人网在线观看 | 一级片a级片 | 日韩免费一区 | 五月婷婷在线播放 | 真实的国产乱xxxx在线 | 久久精品免费在线观看 | 五月婷婷视频在线观看 | 国内精品免费视频精选在线观看 | 三级网址在线 | 性做久久久久 | 黄色特级毛片 | 亚洲色图欧美激情 | 69re在线观看| 婷婷激情小说网 | 日本爱爱片 | 国产看色免费 | 一级特黄a大片免费 | 奇米77 | 加勒比在线免费视频 | 色成人综合| 亚洲激情视频网 | 久久99热精品免费观看无卡顿 | 欧美日韩亚洲国内综合网俺 |