鏈表——最基本的數據結構之一 | 經典鏈表應用場景:LRU 緩存淘汰算法


轉自:

http://www.chinacion.cn/article/4419.html

 

和數組相同,鏈表也是一種線性表結構。作為非常基礎、非常常用的兩種數據結構,數組和鏈表經常被拿來比較。

鏈表定義

  1. 鏈表是一種線性表數據結構;
  2. 從底層存儲結構上看,鏈表不需要一整塊連續的存儲空間,而是通過“指針”將一組零散的內存塊串聯起來使用;
  3. 鏈表中的每個內存塊被稱為鏈表的“結點”,每個結點除了要存儲數據外,還需要記錄上(下)一個結點的地址。

鏈表特點

  1. 插入、刪除數據效率高,只需要考慮相鄰結點的指針改變,不需要搬移數據,時間復雜度是 O(1)。
  2. 隨機查找效率低,需要根據指針一個結點一個結點的遍歷查找,時間復雜度為O(n)。
  3. 與內存相比,鏈表的空間消耗大,因為每個結點除了要存儲數據本身,還要儲存上(下)結點的地址。

常用的鏈表類型

鏈表結構五花八門,常用的有三種:單鏈表、循環鏈表、雙向鏈表和雙向循環列表。

單鏈表

單鏈表結構示意圖
如圖所示:

  1. 單鏈表的每個節點只包含一個后繼指針;
  2. 單鏈表的頭結點尾結點比較特殊,頭結點用來記錄鏈表的基地址,是鏈表遍歷的起點,尾結點的后繼指針不指向任何結點,而是指向一個空地址NULL
  3. 單鏈表的插入、刪除操作時間復雜度為O(1),隨機查找時間復雜度為O(n)。

單鏈表操作示意圖

循環鏈表

循環列表是一種特殊的單鏈表,它跟單鏈表唯一的區別就在於它的尾結點又指回了鏈表的頭結點,首尾相連,形成了一個環,所以叫做循環鏈表。
循環鏈表結構示意圖
與單鏈表相比,循環鏈表的優點是從鏈尾到鏈首比較方便,適用於處理具有環形結構的數據問題,比如著名的約瑟夫問題。

雙向鏈表

雙向鏈表中的每個結點具有兩個方向指針,后繼指針(next)指向后面的結點,前驅指針(prev)指向前面的結點。
雙向鏈表也有兩個特殊結點,首節點的前驅指針和尾結點的后繼指針均指向空地址NULL
雙向鏈表結構示意圖
與單鏈表相比,儲存同樣的數據,雙向鏈表會占用更多的內存空間。雖然多占用了空間,但是雙向鏈表在處理根據已知結點查找上一節點、有序鏈表查找等問題上,都表現的更靈活高效。

雙向循環鏈表

雙向循環鏈表結構示意圖

鏈表&數組對比

鏈表&數組性能對比

數組缺點

  1. 數組必須占用整塊、連續的內存空間,如果聲明數組過大,可能回導致“內存不足”。
  2. 數組不夠靈活,一旦需要擴容,會重新申請連續整塊空間,並需要把原數組的數據全部拷貝到新申請的空間。

鏈表缺點

  1. 內存空間消耗更大,用於儲存結點指針信息。
  2. 對鏈表進行頻繁的插入、刪除操作會導致頻繁的內存申請、釋放,容易造成內存碎片,如果是JAVA語言,還有可能會導致頻繁的GC(Garbage Collection,垃圾回收)。

經典的鏈表應用場景: LRU 緩存淘汰算法

緩存是一種提高數據讀取性能的技術,在硬件設計、軟件開發中都有着非常廣泛的應用,比如常見的CPU緩存、數據庫緩存、瀏覽器緩存等等。
緩存空間的大小有限,當緩存空間被用滿時,哪些數據應該被清理出去,哪些數據應該被保留?這就需要緩存淘汰策略來決定。常見的緩存清理策略有三種:先進先出策略FIFO(First In, First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略LRU(Least Recently Used)。
如何用鏈表來實現LRU緩存淘汰策略呢?
思路:維護一個有序單鏈表,越靠近鏈表尾部的結點是越早之前訪問的。當有一個新的數據被訪問時,我們從鏈表頭部開始順序遍歷鏈表。

  1. 如果此數據之前已經被緩存在鏈表中了,我們遍歷得到這個數據的對應結點,並將其從原來的位置刪除,並插入到鏈表頭部。
  2. 如果此數據沒在緩存鏈表中,又可以分為兩種情況處理:

如果此時緩存未滿,可直接在鏈表頭部插入新節點存儲此數據;
如果此時緩存已滿,則刪除鏈表尾部節點,再在鏈表頭部插入新節點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM