JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

如何選對資料結構

你已經認識了四種線性結構。真正的本事不在於知道它們,而在於快速選對那一個。這是一份實用的決策指南:先問你的資料需要*做什麼*,結構便會自己浮現。

先問資料必須做什麼

別從"該用哪種結構?"出發。要從"我需要哪些操作快?"出發。按位置讀取?在某一端添加?按名字查找?每種結構天生就是某些操作快、某些操作慢。把結構匹配到你*最高頻*的那個操作上。

再問幾個問題也有幫助:大小是固定的,還是會增長?我在意元素到達的順序,還是只關心某個東西在不在?在這裡多想兩分鐘,能省下你日後把一個慢的 O(n) 迴圈改寫成快迴圈的功夫。把你的問題對照下面這張清單走一遍。

  1. 需要按索引讀寫(第 5 個元素)?-> 陣列(或 std::vector)。
  2. 需要後進先出(復原、呼叫堆疊、回溯)?-> 堆疊。
  3. 需要先進先出(公平排程、BFS)?-> 佇列。
  4. 需要按鍵快速查找/插入/刪除(名字 -> 值)?-> 雜湊表。
  5. 既要有序、又要快速查找?-> 樹(下文預告)。

代價速查表

整整一階的內容,濃縮成一張卡片。"平均"對雜湊表尤為關鍵——它最壞是 O(n),但只要雜湊得當,你很少碰到。記住這張表的*形狀*,而非每個格子的精確值;形狀才是指引你選擇的東西。

structure      access     search     insert     delete
-----------    -------    -------    --------   --------
array          O(1)       O(n)       O(n)*      O(n)*
linked list    O(n)       O(n)       O(1)**     O(1)**
stack          O(1) top   --         O(1) push  O(1) pop
queue          O(1) ends  --         O(1) enq   O(1) deq
hash table     --         O(1) avg   O(1) avg   O(1) avg

*  array insert/delete is O(1) only at the very end
** linked list O(1) only once you HOLD a pointer to the spot
四種線性結構(加上陣列)的操作代價。記住它的形狀。

幾個真實的取捨

把這些問題套到真實任務上。"統計一本書裡每個單字出現的次數。"你需要查一個單字並給它的計數加一——按鍵快速查找——所以雜湊表(`std::unordered_map<std::string,int>`)勝出。

"按提交順序處理列印任務。"到達順序、從隊首服務——這是佇列"實作復原。"最近的操作最先被回退——堆疊"儲存 256 個固定像素值,會不斷按索引讀取。"純按索引存取——陣列

那麼"一個總是在中間插入和刪除、從不按索引存取的清單"呢?這正是鏈結串列難得的用武之地——一旦握有指向那個位置的指標,修改就是 O(1)。

當線性結構不夠用:樹的預告

有一種組合是這四者都搞不定的:既保持元素有序、又能快速查找。雜湊表快但無序;有序陣列保持順序,但插入是 O(n)。答案是一種有分支、而非單一直線的結構——,尤其是二元搜尋樹,它能提供 O(log n) 的有序查找和插入。

         (8)
        /   \
      (3)    (10)
      / \       \
    (1) (6)     (14)

ordered + fast: each step halves the search -> O(log n)
二元搜尋樹會分叉,因此每次比較都能丟掉剩下一半的元素。

那是下一階的內容。眼下的收穫是一個習慣:動手寫程式碼前,先把你需要的操作一一點名,再挑出代價與之匹配的結構。結構是一個*決策*,而不是一個*預設值*。