經典的鏈表應用場景就是 LRU 緩存淘汰算法。算法
數組須要一塊連續的內存空間來存儲,對內存的要求比較高。而鏈表不須要,它經過「指針」將一組零散的內存塊串聯起來使用。數組
三種常見的鏈表結構:單鏈表、雙向鏈表和循環鏈表。緩存
**單鏈表:**頭結點記錄鏈表的基地址,能夠用來遍歷整條鏈表。尾結點的指針指向空地址 NULL,表示最後的結點。每一個結點存儲數據 data 和後繼指針 next,以下:性能
針對鏈表的插入和刪除操做,只須要考慮相鄰結點的指針改變,因此對應的時間複雜度是 O(1)。可是,隨機訪問須要從頭結點開始遍歷,因此時間複雜度是 O(n)。優化
循環鏈表是一種特殊的單鏈表,它的尾結點指針指向鏈表的頭結點,優勢是從鏈尾到鏈頭比較方便。當要處理的數據具備環型結構特色時,就特別適合採用循環鏈表,好比約瑟夫問題。設計
雙向鏈表支持兩個方向,每一個結點有個後繼指針 next 指向後面的結點和一個前驅指針 prev 指向前面的結點。它支持雙向遍歷,帶來了操做的靈活性。雙向鏈表能夠支持 O(1) 時間複雜度的狀況下找到前驅結點,這使得它在某些狀況下的插入、刪除等操做比單鏈表簡單高效。對於一個有序鏈表,雙向鏈表的按值查詢的效率也要比單鏈表高一些。指針
在實際的軟件開發中,雙向鏈表儘管比較費內存,但比單鏈表的應用更加普遍。Java 語言中的 LinkedHashMap 就用到了雙向鏈表,這是用空間換時間的設計思想。orm
時間複雜度 | 數組 | 鏈表 |
---|---|---|
插入、刪除 | O(n) | O(1) |
隨機訪問 | O(1) | O(n) |
數組簡單易用,在實現上使用的是連續的內存空間,能夠藉助 CPU 的緩存機制,預讀數組中的數據,因此訪問效率更高。而鏈表在內存中並非連續存儲,因此對 CPU 緩存不友好,沒辦法有效預讀。(此處是局部性原理)cdn
數組的缺點是大小固定,要佔用整塊連續內存空間。若是數組過大,容易致使 OOM。擴容時須要拷貝數組,很是耗時。鏈表自己沒有大小的限制,自然地支持動態擴容。blog
若是代碼對內存的使用很是苛刻,那數組就是更適合的選擇。鏈表須要額外存儲指針結點,頻繁的增刪操做容易形成內存碎片,若是用 Java 語言,就可能致使頻繁 GC。
如何用鏈表實現 LRU 緩存呢?
維護一個有序單鏈表,靠近尾部的結點是最先訪問的。當有數據被訪問時,從頭開始遍歷鏈表。
基於鏈表的實現思路,緩存訪問的時間複雜度爲 O(n)。考慮一下優化,好比引入散列表老記錄每一個數據的位置,使訪問時間複雜度降到 O(1)。
如何判斷一個字符串是不是迴文字符串?若是字符串是經過單鏈表來存儲的,那該如何來判斷是一個迴文串呢?