LFU算法(Leetcode)

LFU:
採用LFU算法的最簡單方法是爲每個加載到緩存的快分配一個計數器.每次引用該塊時,計數器將增加1.當緩存達到容量並且有一個新塊等待插入時,系統將搜索計數器最低的塊並將其從緩存中刪除

實現: LFU的每個數據塊都有一個引用計數,所有數據塊按照引用計數排序,具有相同引用計數的數據塊則按照時間排序

  1. 新加入數據插入到隊列尾部(因爲引用計數爲1)
  2. 隊列中的數據被訪問後,引用計數增加,隊列重新排序
  3. 當需要淘汰數據時,將已經排序的列表最後的數據塊刪除

除了LRU,LFU,還有對於LFU算法的改進,比如Window-LFU,LFU-Aging等.
關於LRU的實現請參考我的博文
golang實現LRU
python實現LRU


哈希表和平衡二叉樹實現

import bisect


class LFUCache:
    def __init__(self, capacity: int):
        # 容量capacity和計時time
        self.cap, self.time = capacity, 0
        # 元素形式爲: (頻率,時間,鍵)(freq, time, key)
        self.his = []
        # 使用字典保存雙關鍵字-鍵值對形式爲key: [val, freq, time]
        self.dic = {}

    def get(self, key: int) -> int:
        # key不存在,返回-1
        if key not in self.dic:
            return -1
        # 更新該緩存的時間
        self.time += 1
        # 取出值,頻率和時間
        val, freq, time = self.dic[key]
        # 更新該緩存的使用頻率
        self.dic[key][1] += 1  # 將頻率+1
        self.dic[key][2] = self.time
        # 找到history裏的記錄並移除原來緩存
        self.his.pop(bisect.bisect_left(self.his, (freq, time, key)))
        # 將更新後的記錄二分插入
        bisect.insort(self.his, (freq+1, self.time, key))
        return val

    def put(self, key: int, value: int) -> None:
        if not self.cap:
            return
        self.time += 1
        # 查看哈希表中是否有對應鍵值
        if key in self.dic:
            # 取出頻率和時間
            _, freq, time = self.dic[key]
            self.dic[key][:] = value, freq + 1, self.time
            # 找到history裏的記錄並移除
            self.his.pop(bisect.bisect_left(self.his, (freq, time, key)))
            # 將更新後的記錄二分插入
            bisect.insort(self.his, (freq + 1, self.time, key))
        else:
            # 無該記錄
            self.dic[key] = [value, 1, self.time]
            # history容量是否已經滿了,滿了需要緩存淘汰
            if len(self.his) == self.cap:
                # 刪除最近最少使用緩存,因爲有序,移除history首個元素即對應的鍵值對
                del self.dic[self.his.pop(0)[2]]
            # 將新紀錄插入history
            bisect.insort(self.his, (1, self.time, key))


if __name__ == '__main__':
    capacity = 2
    cache = LFUCache(capacity)
    print(cache.put(2, 2))
    print(cache.get(1))
    print(cache.put(3, 3))
    print(cache.get(2))
    print(cache.get(3))
    print(cache.put(4, 4))
    print(cache.get(1))
    print(cache.get(3))
    print(cache.get(4))


"""
None
-1
None
2
3
None
-1
3
4
"""

雙哈希表實現

import collections


class Node:
    def __init__(self, key, val, pre=None, nex=None, freq=0):
        self.pre = pre
        self.nex = nex
        self.freq = freq  # 當前節點使用頻率
        self.val = val
        self.key = key

    # 插入節點
    # self-> nex-> self.nex
    def insert(self, nex):
        nex.pre = self
        nex.nex = self.nex
        self.nex.pre = nex
        self.nex = nex


# 創建雙向鏈表,包含值爲0的head,tail
def create_linked_list():
    head = Node(0, 0)
    tail = Node(0, 0)
    head.nex = tail
    tail.pre = head
    return (head, tail)


class LFUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.size = 0  # 鍵值對總數
        self.minFreq = 0  # 記錄最小的頻率,每次容量滿了,刪這個頻率的head.nex
        self.freqMap = collections.defaultdict(
            create_linked_list)  # key是頻率,值是一條雙向鏈表的head, tail,最近操作的節點插入tail前面,則head.nex是最小使用頻率的節點,刪除時刪head.nex
        self.keyMap = {}  # 存儲鍵值對,值是node 類型

    # 雙向鏈表中刪除指定節點
    def delete(self, node):
        if node.pre:  # 不是第一個節點,就需要刪除,
            node.pre.nex = node.nex  # 前後前接起來
            node.nex.pre = node.pre
            if node.pre is self.freqMap[node.freq][0] and node.nex is self.freqMap[node.freq][
                -1]:  # 新的頻率中已存在這個節點,且只有這個節點,那就直接把這個新頻率刪掉,方便後面插入最新數據
                self.freqMap.pop(node.freq)
        return node.key

    # 增加
    def increase(self, node):
        node.freq += 1  # 當前節點頻率+1
        self.delete(node)  # 舊頻率中,刪除此節點
        self.freqMap[node.freq][-1].pre.insert(node)  # 新頻率中,tail節點前插入當前節點
        if node.freq == 1:  # 出現頻率爲1的了,記錄一下,下次容量滿了先從這裏刪
            self.minFreq = 1
        elif self.minFreq == node.freq - 1:  # 操作最小頻率的節點時,從舊頻率到新頻率時需要檢查下舊頻率,只有head,tail就不可能從這裏刪數據了,那就需要把minFreq更新爲新頻率,下次從這裏刪
            head, tail = self.freqMap[node.freq - 1]
            if head.nex is tail:  # 這個頻率裏沒有實際節點,只有head,tail
                self.minFreq = node.freq  # 最小頻率更新爲節點當前頻率

    def get(self, key: int) -> int:
        if key in self.keyMap:
            self.increase(self.keyMap[key])
            return self.keyMap[key].val
        return -1

    def put(self, key: int, value: int) -> None:
        if self.capacity != 0:
            if key in self.keyMap:  # 有,更新value
                node = self.keyMap[key]
                node.val = value
            else:
                node = Node(key, value)  # 沒有,新建一個node
                self.keyMap[key] = node
                self.size += 1
            if self.size > self.capacity:  # 大於容量
                self.size -= 1
                deleted = self.delete(self.freqMap[self.minFreq][0].nex)  # 刪除head.nex
                self.keyMap.pop(deleted)
            self.increase(node)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章