LFUCache
LFUCache
LFU是Least Frequently Used的縮寫,即最不經常最少使用算法,也是一種常用的頁面置換算法,選擇訪問計數器最小的頁面,并予以淘汰。
根據LFU的策略,我們每次訪問對象都需要更新訪問計數器,當插入B的時候,發現緩存中有B,所以增加訪問計數器,兵把B移動到訪問計數器從大到小排序的地方。當插入一個元素時,先更新訪問計數器,在移動到它排序以后的位置,如果頁面中已經滿了,就將末尾的元素進行剔除
當插入一個數據時,尾部有多個數據計數器值相同,這個時候插入是從相同計數器頂端開始插入,這樣就能保證每次剔除的都是計數器值相同的比較久的頁面,按照這種數據插入方式,計數器尾部的數值可以保證是比較舊的數據,這一點和LRU有點不同
LFU更新和插入新頁面可以發生在鏈表中的任意位置,刪除頁面都發生在表尾
LFU同樣需要盡量的高效,O(1)內查詢,修改刪除也要O(1)內完成。
type LFUCache struct {nodes map[int]*list.Elementlists map[int]*list.Listcapacity intmin int }type node struct {key intvalue intfrequency int }為了實現O(1)的時間復雜度,依然需要采用map和雙向鏈表的實現方式。但是相比LRU這里需要額外存儲一個訪問次數。
還有一個需要考慮的問題就是按照什么排序,相同順序按照先后順序?如果每次數據都需要排序,那么最少的時間復雜度也是O(nlogn)。仔細看LFU的策略,發現我們只需要保證最小頻次的元素的按照先后順序排序的就可以了,因此我們可以使用一個Min記錄最少的頻次,淘汰時從尾部找到這個最小頻次的值,將其進行刪除就行了
type LFUCache struct {nodes map[int]*list.Element // lists map[int]*list.List // 不同頻次的數據按照不同的鏈表進行存儲capacity intmin int }type node struct {key intvalue intfrequency int }func Constructor(capacity int) LFUCache {return LFUCache{nodes: make(map[int]*list.Element),lists: make(map[int]*list.List),capacity: capacity,min: 0,} }LFUCache的Get操作涉及更新Frequency值和兩個map,在nodes map中通過Key獲取節點信息,lists中的索引存儲的是頻次。當結點存在時,需要將lists中的節點刪除,刪除完之后對frequency進行++.如果新的frequency在Lists中存在,添加到雙向鏈表的表首,如果不存在就重新創建一個雙鏈表并把當前結點添加到表首。在更新雙鏈表結點作為value的map,最后更新min的值,判斷老的frequency對應的雙鏈表中是否已經為空,如果空了min++
func (lfuCache *LFUCache) Get(key int) int {value, ok := lfuCache.nodes[key]if !ok {return -1}currentNode := value.Value.(*node)lfuCache.lists[currentNode.frequency].Remove(value)currentNode.frequency++if _, ok := lfuCache.lists[currentNode.frequency]; !ok {lfuCache.lists[currentNode.frequency] = list.New()}// 按照頻次取出鏈表newList := lfuCache.lists[currentNode.frequency]// 將當前的節點插入到對應鏈表里面newNode := newList.PushFront(currentNode)lfuCache.nodes[key] = newNodeif currentNode.frequency-1 == lfuCache.min && lfuCache.lists[currentNode.frequency-1].Len() == 0 {lfuCache.min++}return currentNode.value }LFU的put操作邏輯稍微復雜一點,現在nodes map節點中查詢key值是否存在,如果存在獲取這個節點,更新它的value值,然后手動調用一次Get操作,以內下面的更新邏輯和Get操作一致。如果map中不存在就執行插入操作,并在插入之后判斷caplity是否裝滿,如果裝滿執行刪除操作,在min對應的鏈表中刪除尾結點,對應的也要刪除nodes map中的鍵值。
func (lfuCache *LFUCache) Put(key int, value int) {if lfuCache.capacity == 0 {return}// 如果存在,更新訪問次數if currentValue, ok := lfuCache.nodes[key]; ok {currentNode := currentValue.Value.(*node)currentNode.value = valuelfuCache.Get(key)return}// 如果不存在,并且緩存已經滿了if lfuCache.capacity == len(lfuCache.nodes) {currentList := lfuCache.lists[lfuCache.min]backNode := currentList.Back()delete(lfuCache.nodes, backNode.Value.(*node).key)currentList.Remove(backNode)}// 新插入元素lfuCache.min = 1currentNode := &node{key: key,value: value,frequency: 1,}if _, ok := lfuCache.lists[1]; !ok {lfuCache.lists[1] = list.New()}newList := lfuCache.lists[1]newNode := newList.PushFront(currentNode)lfuCache.nodes[key] = newNode }總結
- 上一篇: 线段树segment_tree go语言
- 下一篇: 2020小目标