轉自:
http://www.chinacion.cn/article/4419.html
和數組相同,鏈表也是一種線性表結構。作為非常基礎、非常常用的兩種數據結構,數組和鏈表經常被拿來比較。
鏈表定義
- 鏈表是一種線性表數據結構;
- 從底層存儲結構上看,鏈表不需要一整塊連續的存儲空間,而是通過“指針”將一組零散的內存塊串聯起來使用;
- 鏈表中的每個內存塊被稱為鏈表的“結點”,每個結點除了要存儲數據外,還需要記錄上(下)一個結點的地址。
鏈表特點
- 插入、刪除數據效率高,只需要考慮相鄰結點的指針改變,不需要搬移數據,時間復雜度是 O(1)。
- 隨機查找效率低,需要根據指針一個結點一個結點的遍歷查找,時間復雜度為O(n)。
- 與內存相比,鏈表的空間消耗大,因為每個結點除了要存儲數據本身,還要儲存上(下)結點的地址。
常用的鏈表類型
鏈表結構五花八門,常用的有三種:單鏈表、循環鏈表、雙向鏈表和雙向循環列表。
單鏈表
如圖所示:
- 單鏈表的每個節點只包含一個后繼指針;
- 單鏈表的頭結點和尾結點比較特殊,頭結點用來記錄鏈表的基地址,是鏈表遍歷的起點,尾結點的后繼指針不指向任何結點,而是指向一個空地址NULL。
- 單鏈表的插入、刪除操作時間復雜度為O(1),隨機查找時間復雜度為O(n)。
循環鏈表
循環列表是一種特殊的單鏈表,它跟單鏈表唯一的區別就在於它的尾結點又指回了鏈表的頭結點,首尾相連,形成了一個環,所以叫做循環鏈表。
與單鏈表相比,循環鏈表的優點是從鏈尾到鏈首比較方便,適用於處理具有環形結構的數據問題,比如著名的約瑟夫問題。
雙向鏈表
雙向鏈表中的每個結點具有兩個方向指針,后繼指針(next)指向后面的結點,前驅指針(prev)指向前面的結點。
雙向鏈表也有兩個特殊結點,首節點的前驅指針和尾結點的后繼指針均指向空地址NULL。
與單鏈表相比,儲存同樣的數據,雙向鏈表會占用更多的內存空間。雖然多占用了空間,但是雙向鏈表在處理根據已知結點查找上一節點、有序鏈表查找等問題上,都表現的更靈活高效。
雙向循環鏈表
鏈表&數組對比
數組缺點
- 數組必須占用整塊、連續的內存空間,如果聲明數組過大,可能回導致“內存不足”。
- 數組不夠靈活,一旦需要擴容,會重新申請連續整塊空間,並需要把原數組的數據全部拷貝到新申請的空間。
鏈表缺點
- 內存空間消耗更大,用於儲存結點指針信息。
- 對鏈表進行頻繁的插入、刪除操作會導致頻繁的內存申請、釋放,容易造成內存碎片,如果是JAVA語言,還有可能會導致頻繁的GC(Garbage Collection,垃圾回收)。
經典的鏈表應用場景: LRU 緩存淘汰算法
緩存是一種提高數據讀取性能的技術,在硬件設計、軟件開發中都有着非常廣泛的應用,比如常見的CPU緩存、數據庫緩存、瀏覽器緩存等等。
緩存空間的大小有限,當緩存空間被用滿時,哪些數據應該被清理出去,哪些數據應該被保留?這就需要緩存淘汰策略來決定。常見的緩存清理策略有三種:先進先出策略FIFO(First In, First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略LRU(Least Recently Used)。
如何用鏈表來實現LRU緩存淘汰策略呢?
思路:維護一個有序單鏈表,越靠近鏈表尾部的結點是越早之前訪問的。當有一個新的數據被訪問時,我們從鏈表頭部開始順序遍歷鏈表。
- 如果此數據之前已經被緩存在鏈表中了,我們遍歷得到這個數據的對應結點,並將其從原來的位置刪除,並插入到鏈表頭部。
- 如果此數據沒在緩存鏈表中,又可以分為兩種情況處理:
如果此時緩存未滿,可直接在鏈表頭部插入新節點存儲此數據;
如果此時緩存已滿,則刪除鏈表尾部節點,再在鏈表頭部插入新節點。