<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          最近最少使用的緩存機(jī)制,完整實(shí)現(xiàn)

          共 9532字,需瀏覽 20分鐘

           ·

          2021-06-20 00:24

          你好,我是zhenguo

          今天結(jié)合一道leetcode有意思的題目,設(shè)計(jì)和實(shí)現(xiàn)一個(gè)  LRU (最近最少使用) 緩存機(jī)制,順便和讀者們加強(qiáng)下雙向鏈表、字典這些數(shù)據(jù)結(jié)構(gòu)的應(yīng)用能力。鏈表增刪操作時(shí)間復(fù)雜度都是O(1),這是它最強(qiáng)的地方,尤其追求卓越性能的算法場(chǎng)景,應(yīng)用廣泛。同時(shí),在面試中也經(jīng)常會(huì)考察到。但是,鏈表比較容易出錯(cuò),如果在項(xiàng)目中應(yīng)用,務(wù)必要多多測(cè)試。

          1 問(wèn)題

          運(yùn)用你所掌握的數(shù)據(jù)結(jié)構(gòu),設(shè)計(jì)和實(shí)現(xiàn)一個(gè)  LRU (最近最少使用) 緩存機(jī)制 。實(shí)現(xiàn) LRUCache 類(lèi):

          LRUCache(int capacity) 以正整數(shù)作為容量 capacity 初始化 LRU 緩存 

          int get(int key)  如果關(guān)鍵字 key 存在于緩存中,則返回關(guān)鍵字的值,否則返回 -1 。

          void put(int key, int value) 如果關(guān)鍵字已經(jīng)存在,則變更其數(shù)據(jù)值;如果關(guān)鍵字不存在,則插入該組「關(guān)鍵字-值」。


          當(dāng)緩存容量達(dá)到上限時(shí),它應(yīng)該在寫(xiě)入新數(shù)據(jù)之前刪除最久未使用的數(shù)據(jù)值,從而為新的數(shù)據(jù)值留出空間。

          你是否可以在 O(1) 時(shí)間復(fù)雜度內(nèi)完成這兩種操作?

          鏈接:https://leetcode-cn.com/problems/lru-cache

          2 鏈表

          這道題最高效的O(1)實(shí)現(xiàn)需要基于鏈表結(jié)構(gòu),因此再溫習(xí)一下鏈表的基本操作。

          鏈表是一種節(jié)點(diǎn)傳遞結(jié)構(gòu),所謂的"傳遞"是靠next變量,以此建立指向下一個(gè)節(jié)點(diǎn)的關(guān)系,可以理解為一條邊,僅此而已。

          如果想令j節(jié)點(diǎn)指向i節(jié)點(diǎn),需要如何做?如果對(duì)鏈表不熟悉,可能想當(dāng)然的這么操作:

          node_j.next = node_i

          上面操作后實(shí)現(xiàn)效果如下:

          這是不對(duì)的,節(jié)點(diǎn)i指向節(jié)點(diǎn)j的邊還存在,這不是我們想要的結(jié)果!因此,我們需要摘除這條邊,那么如何摘除?實(shí)際這才是鏈表的靈活之處,所謂的摘除只不過(guò)是一個(gè)None賦值操作:

          node_i.next = None

          上面賦值實(shí)現(xiàn)的效果如下:

          你看到嗎?剪斷后,節(jié)點(diǎn)i和節(jié)點(diǎn)j之間不再有鏈接關(guān)系。所以j節(jié)點(diǎn)指向i節(jié)點(diǎn)的完整代碼如下:

          node_i.next = None
          node_j.next = node_i

          3 實(shí)現(xiàn)思路

          下面我們?cè)倩仡^問(wèn)題,要想在O(1)時(shí)間復(fù)雜度內(nèi)求解,需要借助字典和雙向鏈表,問(wèn)題涉及的主要操作包括:

          (1). put操作 加入鍵值對(duì)分兩種情況討論:

          • (1).1 鍵不存在
          • (1).2 鍵存在

          (2).get操作,get操作除了具備dict.get的功能外,此處需要定制一個(gè)新的功能,即最近使用的節(jié)點(diǎn)需要移動(dòng)到熱點(diǎn)區(qū)域。而我們知道鏈表的增刪時(shí)間復(fù)雜度都是O(1),所以根據(jù)這個(gè)定制化需求,使用鏈表是再自然不過(guò)的了!

          4 完整代碼

          class Node(object):
              """
              定義雙向鏈表
              """

              def __init__(self, val, pre, next):
                  self.val = val
                  self.pre = pre
                  self.next = next

          LRUCache緩存類(lèi):

          class LRUCache:
              def __init__(self, capacity: int):
                  self.cache_dict = {}
                  self.linked_dict = {}
                  self.capacity = capacity
                  self.head, self.tail = NoneNone

          put操作對(duì)應(yīng)代碼,主要解決三種情況,這和題目是完整一一對(duì)應(yīng)的,分別為:

          • 如果關(guān)鍵字已經(jīng)存在,則變更其數(shù)據(jù)值
          • 如果關(guān)鍵字不存在,則插入該組「關(guān)鍵字-值」
          • 當(dāng)緩存容量達(dá)到上限時(shí),它應(yīng)該在寫(xiě)入新數(shù)據(jù)之前刪除最久未使用的數(shù)據(jù)值,從而為新的數(shù)據(jù)值留出空間。

              def put(self, key: int, value: int) -> None:
                  if key in self.cache_dict:  # 如果關(guān)鍵字已經(jīng)存在,則變更其數(shù)據(jù)值
                      self.cache_dict[key] = value
                      self._move_to_tail(key)
                  elif len(self.cache_dict) < self.capacity:  # 如果關(guān)鍵字不存在,則插入該組「關(guān)鍵字-值」
                      self.cache_dict[key] = value
                      self.linked_dict[key] = Node(key, NoneNone)
                      if not self.head:
                          self.head = self.linked_dict[key]
                      if self.tail:
                          self.tail.next = self.linked_dict[key]

                      tmp = self.tail
                      self.tail = self.linked_dict[key]
                      if tmp:
                          self.linked_dict[key].pre = tmp
                          tmp.next = self.linked_dict[key]

                  else:  # 當(dāng)緩存容量達(dá)到上限時(shí),
                      # 它應(yīng)該在寫(xiě)入新數(shù)據(jù)之前刪除最久未使用的數(shù)據(jù)值,從而為新的數(shù)據(jù)值留出空間。
                      self.cache_dict[key] = value
                      self.linked_dict[key] = Node(key, NoneNone)
                      del_key = self.head.val
                      self.cache_dict.pop(del_key)
                      pre, next = self.linked_dict[del_key].pre, self.linked_dict[del_key].next
                      if not pre and not next:  # 刪除節(jié)點(diǎn)無(wú)頭無(wú)尾
                          self.head = self.linked_dict[key]
                      elif not pre:  # 刪除元素是頭節(jié)點(diǎn)
                          next.pre = None
                          self.head = next
                      elif not next:  # 刪除元素是尾節(jié)點(diǎn)
                          pre.next = None
                          self.tail = pre
                      else:
                          pre.next = next
                          next.pre = pre

                      # 然后在尾部連接上key節(jié)點(diǎn)
                      self.tail.next = self.linked_dict[key]
                      self.linked_dict[key].pre = self.tail
                      self.linked_dict[key].next = None
                      self.tail = self.linked_dict[key]
                      # 最后再?gòu)膌inked_dict中移除key
                      self.linked_dict.pop(del_key)

          get操作,

              def get(self, key: int) -> int:
                  if key in self.linked_dict and self.tail != self.linked_dict[key]:
                      self._move_to_tail(key)
                  return self.cache_dict.get(key, -1)

          簡(jiǎn)單重構(gòu)一下,抽離出一個(gè)移動(dòng)節(jié)點(diǎn)i到tail處的方法_move_to_tail

          def _move_to_tail(self, key):
                  """
                  移動(dòng)key節(jié)點(diǎn)到tail
                  :param key:
                  :return:
                  """

                  pre, next = self.linked_dict[key].pre, self.linked_dict[key].next
                  # 從鏈表中隔斷key節(jié)點(diǎn)
                  if pre and next:
                      pre.next = next
                      next.pre = pre
                  elif next:
                      next.pre = None
                      self.head = next
                  # 如果key節(jié)點(diǎn)不在尾部,則移動(dòng)key節(jié)點(diǎn)到尾部
                  if self.tail != self.linked_dict[key]:
                      self.linked_dict[key].pre = self.tail
                      tmp = self.tail
                      self.tail = self.linked_dict[key]
                      if tmp:
                          tmp.next = self.tail
                      self.linked_dict[key].next = None

          5 后記

          鏈表操作比較容易出錯(cuò),平時(shí)需要多加練習(xí),才能在實(shí)際項(xiàng)目和面試中,使用得心應(yīng)手。

          其實(shí)鏈表的應(yīng)用極為廣泛,包括我們熟知的版本管理軟件git,里面的多分支和某個(gè)分支的管理,都是基于鏈表操作的。牢固的掌握鏈表才算是深度掌握算法和數(shù)據(jù)結(jié)構(gòu)的第一步。

          瀏覽 22
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲AV永久无码国产精品国产 | 亚洲无码免费电影 | 欧美性爱黑人大屌 | 国产一区二区三区四区五区六区久久 | 91av在线麻豆 |