🎯 什麼情境該想到我

分工:這張是「選哪個結構」的決策卡。每個結構的內部機制、複雜度推導、Go 實作細節 → reference/演算法導論/(雜湊表、二元堆積、紅黑樹、堆疊與佇列、鏈結串列、併查集)。

當你要決定「一堆資料該用什麼結構存」,或發現查找/插入很慢時。選對結構,效能天差地別。

⚙️ 怎麼用(依「主要操作」選)

  • 常隨機存取、少增刪 → 陣列(O(1) 索引)。
  • 常在中間插入/刪除 → 鏈結串列。
  • 常用鍵查值 / 去重 → 雜湊表(平均 O(1))。
  • 需要保持有序 + 範圍查詢 → 平衡樹 / 有序結構(O(log n))。
  • 常取最大/最小 → 堆積(Heap / 優先佇列)。
  • 關係/連通/最短路 → 圖。

先問:這份資料最常做哪個操作? 那個操作要快,就選對應結構。

★ Go 裡的實際對照表

Go 標準函式庫沒有內建的有序 map、樹、集合型別。實際可用的選項:

需求Go 的做法備註
有序、按索引存取[]T預設選擇。連續記憶體,快取友善
用鍵查值map[K]V走訪順序刻意隨機,不要依賴
集合(去重)map[T]struct{}struct{} 不佔空間
有序 + 範圍查詢排序好的 slice + slices.BinarySearch讀多寫少時最實用;插入是 O(n) 的搬移
有序 + 頻繁插入刪除第三方 B-tree(如 github.com/google/btree)標準函式庫沒有
取最小/最大container/heap(實作 5 個方法的介面)優先佇列、Top-K、Dijkstra
FIFO 佇列slice(q = q[1:])或 channel(跨 goroutine 時)q[1:] 不會釋放前面的記憶體,長期運作的佇列要定期重新配置或用環形緩衝區
雙向鏈結串列container/list幾乎永遠不該用:每個節點獨立配置、快取極不友善(見 鏈結串列)

TS 對應:Array、Map(保留插入順序)、Set;沒有內建堆積,需要自己寫或用套件。用 Map 而不是一般物件當字典——物件會繼承原型上的鍵,也只能用字串當鍵。

★ 小資料量時,Big-O 會說謊

Big-O 只描述「n 很大時」的趨勢。n 很小時,常數與記憶體存取模式才是主角。實測(Apple M4、Go 1.26.4,字串鍵、查詢都命中,2026-09-25):

nslice 線性搜尋排序 slice + 二分搜尋map
44.6 ns8.7 ns6.1 ns
87.9 ns11.4 ns7.5 ns
1614.0 ns14.9 ns5.3 ns
6453.0 ns18.9 ns5.6 ns
1024627 ns30.9 ns5.4 ns

讀法:

  • n ≤ 8 左右,線性搜尋和 map 打平甚至更快——不用為了 5 個元素建 map。
  • map 的查詢時間幾乎不隨 n 變(O(1) 的實際樣子);線性搜尋隨 n 線性上升(O(n));二分搜尋緩慢上升(O(log n))。
  • 交叉點會隨鍵的型別、CPU、快取狀態移動,所以要自己量,不要背數字。

★ 選擇的決策順序

  1. 最常做的操作是什麼? 查、插、刪、排序、取極值、範圍查詢。
  2. 資料量級? 小於幾十個 → 幾乎用什麼都行,選最簡單的(通常是 slice)。
  3. 讀寫比例? 讀遠多於寫 → 可以接受寫入慢的結構(排序 slice),換取查詢快又省記憶體。
  4. 需要有序嗎? Go 的 map 不保證順序;需要穩定輸出(例如產生 JSON、測試比對)時,先把鍵取出來排序:slices.Sorted(maps.Keys(m))。
  5. 會被多個 goroutine 同時存取嗎? 內建 map 不是併發安全的,同時讀寫會直接讓程式 fatal error。加 sync.RWMutex,或在「寫一次、讀很多次」的情況用 sync.Map。

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 選擇是「操作頻率」的取捨,沒有全能結構;查得快的常插得慢,反之亦然。

  • 記憶體也是成本:map 每個條目有額外的桶與中繼資料開銷;存一億個小整數時,排序 slice 可能只用 map 的幾分之一記憶體。選結構時要同時看時間與空間(見 工具-時間複雜度分析)。

🔗 相關工具