一、是什麼
連結串列(Linked List)是一種物理儲存單元上非連續、非順序的儲存結構,資料元素的邏輯順序是透過連結串列中的指標連結次序實現的,由一系列結點(連結串列中每一個元素稱為結點)組成
每個結點包括兩個部分:一個是儲存資料元素的資料域,另一個是儲存下一個結點地址的指標域
節點用程式碼表示,則如下:
class Node { constructor(val) { this.val = val; this.next = null; } }
- data 表示節點存放的資料
- next 表示下一個節點指向的記憶體空間
相比於線性表順序結構,操作複雜。由於不必須按順序儲存,連結串列在插入的時候可以達到O(1)
的複雜度,比另一種線性表順序錶快得多,但是查詢一個節點或者訪問特定編號的節點則需要O(n)的時間,而線性表和順序表相應的時間複雜度分別是O(logn)
和O(1)
連結串列的結構也十分多,常見的有四種形式:
- 單連結串列:除了頭節點和尾節點,其他節點只包含一個後繼指標
- 迴圈連結串列:跟單連結串列唯一的區別就在於它的尾結點又指回了連結串列的頭結點,首尾相連,形成了一個環
- 雙向連結串列:每個結點具有兩個方向指標,後繼指標(next)指向後面的結點,前驅指標(prev)指向前面的結點,其中節點的前驅指標和尾結點的後繼指標均指向空地址NULL
- 雙向迴圈連結串列:跟雙向連結串列基本一致,不過頭節點前驅指標指向尾跡誒單和尾節點的後繼指標指向頭節點
二、操作
關於連結串列的操作可以主要分成如下:
- 遍歷
- 插入
- 刪除
遍歷
遍歷很好理解,就是根據next
指標遍歷下去,直到為null
,如下:
let current = head while(current){ console.log(current.val) current = current.next }
插入
向連結串列中間插入一個元素,可以如下圖所示:
可以看到,插入節點可以分成如下步驟:
-
儲存插入位置的前一個節點
-
儲存插入位置的後一個節點
-
將插入位置的前一個節點的 next 指向插入節點
-
將插入節點的 next 指向前面儲存的 next 節點
相關程式碼如下所示:
let current = head while (current < position){ pervious = current; current = current.next; } pervious.next = node; node.next = current;
如果在頭節點進行插入操作的時候,會實現previousNode
節點為undefined
,不適合上述方式
解放方式可以是在頭節點前面新增一個虛擬頭節點,保證插入行為一致
刪除
向連結串列任意位置刪除節點,如下圖操作:
從上圖可以看到刪除節點的步驟為如下:
- 獲取刪除節點的前一個節點
- 獲取刪除節點的後一個節點
- 將前一個節點的 next 指向後一個節點
- 向刪除節點的 next 指向為null
如果想要刪除制定的節點,示意程式碼如下:
while (current != node){ pervious = current; current = current.next; nextNode = current.next; } pervious.next = nextNode
同樣如何希望刪除節點處理行為一致,可以在頭節點前面新增一個虛擬頭節點
三、應用場景
快取是一種提高資料讀取效能的技術,在硬體設計、軟體開發中都有著非常廣泛的應用,比如常見的CPU
快取、資料庫快取、瀏覽器快取等等
當快取空間被用滿時,我們可能會使用LRU
最近最好使用策略去清楚,而實現LRU
演算法的資料結構是連結串列,思路如下:
維護一個有序單連結串列,越靠近連結串列尾部的結點是越早之前訪問的。當有一個新的資料被訪問時,我們從連結串列頭部開始順序遍歷連結串列
- 如果此資料之前已經被快取在連結串列中了,我們遍歷得到這個資料的對應結點,並將其從原來的位置刪除,並插入到連結串列頭部
- 如果此資料沒在快取連結串列中
- 如果此時快取未滿,可直接在連結串列頭部插入新節點儲存此資料
- 如果此時快取已滿,則刪除連結串列尾部節點,再在連結串列頭部插入新節點
由於連結串列插入刪除效率極高,達到O(1)。對於不需要搜尋但變動頻繁且無法預知數量上限的資料的情況的時候,都可以使用連結串列
參考文獻
- https://zh.wikipedia.org/zh-hans/%E9%93%BE%E8%A1%A8
- https://mah93.github.io/2019/07/19/js-linked/
如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。