陣列與鏈結串列的差異:讀取、插入與刪除、記憶體排列、快取與時間複雜度比較

陣列是把相同型別的元素在記憶體上毫無間隙地連續排列,能從元素編號(索引)計算出位置,以固定的時間讀寫任一元素的資料結構。鏈結串列是每個元素都有值與指向下一個元素位置的指標,沿著指標串接起來的資料結構,元素可以放在記憶體中彼此分離的位置。陣列讀取快,但在中間插入或刪除時,必須移動後面的元素。鏈結串列只要知道位置,只需改接指標就能插入或刪除,但要讀第i個元素,必須從開頭依序走訪。常以編號讀取時用陣列,常在已知位置插入或刪除時用鏈結串列,是基本的使用原則。

項目陣列鏈結串列
記憶體上的排列方式元素在連續的區域中毫無間隙地排列元素放在分散的位置,以指標串接
讀取第i個元素O(1)。可用「開頭位址 + i × 元素大小」計算位置O(n)。從開頭沿指標走i次
搜尋值未排序時為O(n)的線性搜尋。已排序時以二分搜尋為O(log n)即使已排序也是O(n)。無法一次移到中間的元素,因此不能使用二分搜尋
在中間插入、刪除O(n)。把後面的元素逐一移動知道插入位置(前一個元素)時為O(1)。只需改接指標
在尾端新增有空位時為O(1)。可變長度陣列以每次加倍容量的方式時,偶爾需要複製全部,但平均為O(1)(攤銷)若保有指向最後一個元素的指標,則為O(1)
改變大小固定長度的陣列在建立時就決定大小。要擴大時,需配置新的區域並複製每個元素各自配置空間,因此只需增加需要的量
使用的記憶體只需元素本身的空間(可變長度陣列會保留預備的容量)每個元素都需要額外的指標空間(雙向串列為2個)
快取是否容易生效容易。元素連續排列,依序讀取時快取與預取都能良好運作不容易。元素容易分散,即使同樣是O(n)的走訪,也可能比陣列慢很多
主要用途以索引讀寫的表、矩陣與影像、排序或二分搜尋的對象、堆疊與佇列的實作插入與刪除頻繁的序列、雜湊表的鏈結法、可用空間的管理

數值為具代表性的大致參考值,會因世代或產品而異。

適用於什麼情況

陣列
常以編號讀寫元素,或從開頭依序讀取時。由於快取容易生效,在現今的電腦上,許多情況下陣列(包含可變長度陣列)都比較快。
鏈結串列
在中間插入或刪除的次數多,而且已經以指標掌握該位置時。由於能不移動元素就改接,也用於元素位址不能改變的情況。

常見問題

在時間複雜度上,鏈結串列的插入是O(1),為什麼實際上有時陣列比較快?

原因有兩個。一是若要從開頭找出插入位置,搜尋就需要O(n),即使插入本身是O(1),整體也與陣列同樣是O(n)。二是陣列移動元素的處理是連續記憶體的複製,快取容易生效而非常快;而走訪鏈結串列要逐一讀取分散的位址,不容易命中快取。時間複雜度表示的是資料變多時的增加方式,實際的速度要實際測量確認。

單向串列與雙向串列有什麼不同?

單向串列的每個元素只有指向下一個元素的指標,只能從前往後走訪。雙向串列還有指向前一個元素的指標,因此也能從後往前走訪,只要有指向某元素的指標,就能以O(1)刪除該元素。相對地,每個元素多一個指標,需要改接的指標也會增加。

什麼是可變長度陣列(動態陣列)?

當元素增加而容量不足時,配置更大的區域並複製全部內容,使大小可以改變的陣列。若每次把容量加倍,複製的總數只約等於元素數,因此在尾端新增平均為O(1)。以編號讀取與一般陣列相同,為O(1)。

理解度檢測

與陣列相比,鏈結串列(各元素持有指向下一個元素位置之指標的串列)的特徵,下列何者正確?

  1. A若已知插入位置的前一個元素,只要改接指標就能插入,不必移動其他元素;但要讀取第 i 個元素,必須從開頭依序走訪
  2. B可以計算出第 i 個元素的位置,因此任何元素都能以固定時間 O(1) 讀取
  3. C元素在記憶體中緊密連續排列,因此依序讀取時快取與預取容易發揮效果
查看答案與解析

正確答案:A. 若已知插入位置的前一個元素,只要改接指標就能插入,不必移動其他元素;但要讀取第 i 個元素,必須從開頭依序走訪

在鏈結串列中,只要知道插入或刪除的位置,改寫前後的指標即可,只需 O(1)。另一方面,要讀取第 i 個元素必須從開頭追蹤 i 次指標,需要 O(n)。陣列可由起始位址與元素大小算出第 i 個元素的位址,因此能以 O(1) 讀取,但在中間插入時需要把後面的元素往後挪,要花 O(n)。此外,陣列的元素連續排列,快取與預取容易發揮效果;鏈結串列的節點容易散落在記憶體各處,即使同樣是 O(n) 的走訪,也可能慢上許多。

某個可變長度陣列在容量已滿時加入元素,會重新配置2倍容量的區域並複製所有元素。對容量為1的空陣列逐一加入1,000個元素時,被複製的元素總數為多少?

  1. A10
  2. B1,023
  3. C499,500
查看答案與解析

正確答案:B. 1,023

容量每次滿了就以1、2、4、…、512的方式變為2倍,每次都要複製當時的所有元素,因此複製總數為 1 + 2 + 4 + … + 512 = 1,023。對1,000次加入而言,複製約為元素數的1倍,因此每次加入的工作量平均為固定值,也就是攤銷 O(1)。若容量每次只增加1,複製為 1 + 2 + … + 999 = 499,500,是 O(n²)。10是重新配置區域的次數,不是被複製的元素數。

比較處理雜湊表碰撞的鏈結法(以鏈結串列串接相同位置的元素)與開放定址法(在表中尋找其他空位置),下列說明何者正確?負載因子為存放的元素數除以表的大小所得的值。

  1. A鏈結法在負載因子超過1時也能存放,但開放定址法無法存放超過表大小的元素,且負載因子接近1時搜尋次數會急遽增加
  2. B開放定址法不會發生碰撞,因此無論負載因子為何,搜尋都只需1次
  3. C鏈結法無論負載因子多高,搜尋的工作量都不變
查看答案與解析

正確答案:A. 鏈結法在負載因子超過1時也能存放,但開放定址法無法存放超過表大小的元素,且負載因子接近1時搜尋次數會急遽增加

鏈結法把相同位置的元素串成串列,因此元素數超過表的大小也能存放,但串列的平均長度與負載因子成正比增加,搜尋的工作量也隨之增加。開放定址法只存放在表中,元素數最多只能到表的大小,負載因子接近1時尋找空位的次數會急遽增加。無論哪種方法,負載因子超過設定值(例如約0.5~0.75)時,通常都會放大表格並重新放入所有元素(重新雜湊)。開放定址法也會發生碰撞,是靠尋找空位置來處理。

  1. 在元素很多、負載因子維持在0.5左右的雜湊表中,使用線性探測的開放定址法往往比鏈結法(以鏈結串列串接的方法)快,最適當的原因為下列何者?
  2. 與陣列中央的元素比較、每次將搜尋範圍縮小一半的二分搜尋,其使用的前提條件為下列何者?

前往資料結構與演算法的練習題

相關名詞的解說

陣列
將相同型別的值排列儲存,並以元素編號(索引)指定各元素的資料結構。元素在記憶體中連續配置,因此可由元素編號計算位置,以固定的時間讀寫任何元素。有元素編號從0開始的語言,也有從1開始的寫法。
鏈結串列
每個元素持有值與指向下一個元素位置的指標,並沿著指標串接起來的資料結構。已知位置時,插入與刪除只需改接指標,為 O(1);但要讀取第 i 個元素必須從開頭走訪,需要 O(n)。元素容易散落在記憶體各處,快取效果不如陣列。
指標(pointer)
值為變數、陣列元素、函式等所在記憶體位址的變數。C語言以 int *p 的方式宣告,以 &x 取出 x 的位址,以 *p 讀寫 p 所指向的值。指標加上整數時,會以所指向型別的大小為單位前進。陣列名稱在多數運算式中會轉換成指向第一個元素的指標,a[i] 與 *(a + i) 意義相同。表示不指向任何地方的值稱為 NULL 指標。
時間複雜度(大O符號)
表示演算法所需的工作量(時間複雜度)或記憶空間(空間複雜度),隨資料數 n 如何增加的指標。以 O(n)、O(n log n)、O(n²) 這類大O符號表示,省略係數與低次項。
二分搜尋
在已排序的資料中,與中央元素比較,每次把搜尋範圍縮小一半的搜尋方法。從 n 個資料中最多以 ⌊log₂ n⌋ + 1 次比較找到,時間複雜度為 O(log n)。前提是資料已排序。
雜湊表
以雜湊函數由鍵計算存放位置,平均能以 O(1) 進行搜尋、插入與刪除的資料結構。不同的鍵落在相同位置的碰撞,以鏈結法或開放定址法處理。
空間局部性
存取某個位址後,其附近的位址也容易在不久後被存取的性質。例如依序讀取陣列的處理,是以快取行為單位一併載入的原因。

相關的比較