鏈表(上)—— LRU 緩存淘汰算法的實現

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

1. 鏈表結構

數組須要一塊連續的內存空間來存儲,對內存的要求比較高。而鏈表不須要,它經過「指針」將一組零散的內存塊串聯起來使用。數組

三種常見的鏈表結構:單鏈表雙向鏈表循環鏈表緩存

**單鏈表:**頭結點記錄鏈表的基地址,能夠用來遍歷整條鏈表。尾結點的指針指向空地址 NULL,表示最後的結點。每一個結點存儲數據 data 和後繼指針 next,以下:性能

針對鏈表的插入和刪除操做,只須要考慮相鄰結點的指針改變,因此對應的時間複雜度是 O(1)。可是,隨機訪問須要從頭結點開始遍歷,因此時間複雜度是 O(n)。優化

循環鏈表是一種特殊的單鏈表,它的尾結點指針指向鏈表的頭結點,優勢是從鏈尾到鏈頭比較方便。當要處理的數據具備環型結構特色時,就特別適合採用循環鏈表,好比約瑟夫問題。設計

雙向鏈表支持兩個方向,每一個結點有個後繼指針 next 指向後面的結點和一個前驅指針 prev 指向前面的結點。它支持雙向遍歷,帶來了操做的靈活性。雙向鏈表能夠支持 O(1) 時間複雜度的狀況下找到前驅結點,這使得它在某些狀況下的插入、刪除等操做比單鏈表簡單高效。對於一個有序鏈表,雙向鏈表的按值查詢的效率也要比單鏈表高一些。指針

在實際的軟件開發中,雙向鏈表儘管比較費內存,但比單鏈表的應用更加普遍。Java 語言中的 LinkedHashMap 就用到了雙向鏈表,這是用空間換時間的設計思想。orm

2. 鏈表、數組性能比較

時間複雜度 數組 鏈表
插入、刪除 O(n) O(1)
隨機訪問 O(1) O(n)

數組簡單易用,在實現上使用的是連續的內存空間,能夠藉助 CPU 的緩存機制,預讀數組中的數據,因此訪問效率更高。而鏈表在內存中並非連續存儲,因此對 CPU 緩存不友好,沒辦法有效預讀。(此處是局部性原理)cdn

數組的缺點是大小固定,要佔用整塊連續內存空間。若是數組過大,容易致使 OOM。擴容時須要拷貝數組,很是耗時。鏈表自己沒有大小的限制,自然地支持動態擴容。blog

若是代碼對內存的使用很是苛刻,那數組就是更適合的選擇。鏈表須要額外存儲指針結點,頻繁的增刪操做容易形成內存碎片,若是用 Java 語言,就可能致使頻繁 GC。

如何用鏈表實現 LRU 緩存呢?

維護一個有序單鏈表,靠近尾部的結點是最先訪問的。當有數據被訪問時,從頭開始遍歷鏈表。

  1. 若是數據被緩存過,遍歷獲得對應的結點,把它從原來位置刪除,插入到鏈表頭部。
  2. 若是沒有緩存過,那麼分兩種狀況:
    • 若是緩存沒滿,那麼直接把新數據插入鏈表尾部;
    • 若是緩存已滿,那麼把尾結點刪除,新數據插入鏈表頭部。

基於鏈表的實現思路,緩存訪問的時間複雜度爲 O(n)。考慮一下優化,好比引入散列表老記錄每一個數據的位置,使訪問時間複雜度降到 O(1)。

思考題:

如何判斷一個字符串是不是迴文字符串?若是字符串是經過單鏈表來存儲的,那該如何來判斷是一個迴文串呢?

相關文章
相關標籤/搜索