數據結構和算法-LRU

LRU, 內存管理的一種頁面置換算法,對於在內存中但又不用的數據塊(內存塊)叫作LRU,操做系統會根據哪些數據屬於LRU而將其移出內存而騰出空間來加載另外的數據python

緩存使用策略有三種算法

  • FIFO(先進先出隊列)
  • LFU - Least frequently Used (最少使用)
    若是一段數據在最近一段時間被訪問的次數不多, 那麼在未來被訪問的次數也不多
  • LRU - Least Recently Used(最近最少使用)
    若是一段數據在最近的一段時間沒有被訪問到, 那麼以後被訪問到的機率很小

方案一: O(n)

維護一個有序單鏈表. 尾部是最近使用的, 頭部是最先使用的. 當有一個新數據被訪問時, 遍歷該鏈表, 有如下狀況數組

  1. 若是已經被緩存在鏈表中, 獲得這個節點, 刪除後移動到最尾部
  2. 若是沒有被緩存
    • 若是鏈表未滿, 直接加入尾部
    • 若是鏈表滿了, 刪除頭部, 把新節點加入尾部

若是直接使用單鏈表實現的話, 查找操做時間複雜度是O(n), 因此一般還會再借助散列表來提升查找速度. 可是在空間已滿的時候須要刪除最先使用的數據, 因此還須要保證使用順序緩存

方案二: O(1)

實現有序的散列表: 藉助散列表和雙向鏈表實現, 散列表用來快速定位, 雙向鏈表用來存儲數據數據結構

實現

使用OrderDict實現

# coding:utf-8

from collections import OrderedDict


class LRUCache(object):
    """
    藉助OrderedDict的有序性實現, 內部使用了雙向鏈表
    """

    def __init__(self, max_length: int = 5):
        self.max_length = max_length
        self.o_dict = OrderedDict()

    def get(self, key):
        """
        若是找到的話移動到尾部
        :param key:
        :return:
        """
        value = self.o_dict.get(key)
        if value:
            self.o_dict.move_to_end(key)
        return value

    def put(self, key, value):
        if key in self.o_dict:
            self.o_dict.move_to_end(key)
        else:
            if len(self.o_dict) >= self.max_length:
                # 彈出最早插入的元素
                self.o_dict.popitem(last=False)
        self.o_dict[key] = value


if __name__ == "__main__":
    lru = LRUCache(max_length=3)
    lru.put(1, "a")
    lru.put(2, "b")
    lru.put(3, "c")
    assert lru.o_dict == OrderedDict([(1, 'a'), (2, 'b'), (3, 'c')])
    lru.get(2)
    assert lru.o_dict == OrderedDict([(1, 'a'), (3, 'c'), (2, 'b')])
    lru.put(4, "d")
    assert lru.o_dict == OrderedDict([(3, 'c'), (2, 'b'), (4, "d")])

使用dict和list實現

# coding:utf-8

from collections import deque


class LRUCache(object):
    def __init__(self, max_length: int = 5):
        self.max_length = max_length
        self.cache = dict()
        self.keys = deque()

    def get(self, key):
        if key in self.cache:
            value = self.cache[key]
            self.keys.remove(key)
            self.keys.append(key)
        else:
            value = None
        return value

    def put(self, key, value):
        if key in self.cache:
            self.keys.remove(key)
            self.keys.append(key)
        else:
            if len(self.keys) >= self.max_length:
                self.keys.popleft()
                self.keys.append(key)
            else:
                self.keys.append(key)
        self.cache[key] = value


if __name__ == "__main__":
    lru = LRUCache(max_length=3)
    lru.put(1, "a")
    lru.put(2, "b")
    lru.put(3, "c")
    assert lru.keys == deque([1, 2, 3])
    lru.get(2)
    assert lru.keys == deque([1, 3, 2])
    lru.put(4, "d")
    assert lru.keys == deque([3, 2, 4])

lru_cache

可使用python3.7中自帶的lru緩存模塊app

from functools import lru_cache


@lru_cache(maxsize=32)
def fibs(n: int):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fibs(n-1) + fibs(n-2)


if __name__ == '__main__':
    print(fibs(10))

應用

  • Redis的LRU策略
  • Java的LinkedHashMap
    使用散列表和雙向鏈表實現

總結

  • 數組利用索引能夠快速定位, 可是缺陷是須要內存連續
  • 鏈表優點是內存能夠不連續, 可是查找慢
  • 散列表和鏈表/跳錶混合使用是爲告終合數組和鏈表的優點

資料

  • 數據結構和算法之美-王爭

相關文章
相關標籤/搜索