漸進符號
當輸入規模 n 夠大時,函數的低階項與常數係數對成長趨勢的影響可忽略,只有最高階項決定整體行為。
以 T(n)=3n2+100n+5000 為例,n=10000 時:
- 3n2=3×108
- 100n=106(僅佔 n2 項的 0.3%)
- 5000(可忽略)
因此 T(n)=O(n2),n2 項完全主導增長。
化簡規則:
- 捨棄常數係數:O(5n)=O(n)
- 捨棄低階項:O(n2+n)=O(n2)
- 不同輸入各自獨立:O(n+m) 不能化簡成 O(n),因為 m 可能比 n 大
O、Ω、Θ 的差異
- O(Big-O):上界,演算法不會比這更慢
- Ω(Big-Omega):下界,演算法不會比這更快
- Θ(Big-Theta):緊界,上界與下界同階
Θ 成立的條件:存在 n0 和正常數 c,c′,使得對所有 n≥n0:
c′⋅g(n)≤f(n)≤c⋅g(n)
以線性搜尋為例:
- Worst case = O(n):目標在最後一個位置,要掃完整個陣列
- Best case = Ω(1):目標在第一個位置,看一次就找到了
- Average case = Θ(n):期望上要跑一半,仍然是線性成長
考試考點:嚴格區分 O、Ω、Θ。
- 「Quick Sort 是 O(nlogn)」不精確(worst case 是 O(n2))
- 「Quick Sort average case 是 Θ(nlogn)」才正確
- Big O 只保證上界,看到題目問「緊界(tight bound)」必須寫 Θ
常見複雜度等級
常數時間 O(1)
執行時間與輸入規模無關。典型範例:陣列 index 存取、hash table 無碰撞時的查找。
對數時間 O(log n)
每步將問題規模砍半(或更大比例)。典型範例:二分搜尋——從 n 個元素開始,每次排除一半,共 log2n 次。對數成長極緩慢:n 從 100 萬增至 10 億,log2n 僅從 20 增到 30。
線性時間 O(n)
與輸入規模成正比,需遍歷每個元素至少一次。典型範例:線性搜尋、找最大值、計算陣列總和。許多問題的天然下界就是 Ω(n)(至少要讀過每個輸入一次)。
線性對數時間 O(n log n)
常見於「對每個元素做 logn 代價的事」或「分而治之、每層合併需要線性代價」的結構。Merge Sort、Heap Sort 屬此等級。比較排序(comparison sort)的理論下界為 Ω(nlogn)——任何只靠比較來排序的演算法,最壞情況不可能更快。
多項式時間 O(nᵏ)
n 的某個固定次方。最常見的是 O(n2) 和 O(n3)。
O(n2) 通常來自兩層巢狀迴圈,對每個元素都要跑一遍所有其他元素——Bubble Sort、Insertion Sort、Selection Sort 都是。n=1000 時大約 106 次操作,還可以接受;n=105 時就是 1010 次,在現代硬體上大概要幾十秒,很慢了。
O(n3) 常見於三層巢狀迴圈,標準的矩陣乘法就是 O(n3):兩個 n×n 的矩陣相乘,每個輸出元素要做 n 次乘加,共 n2 個輸出元素。n=1000 的矩陣乘法需要 109 次操作,需要幾秒鐘。
只要 k 是固定常數,多項式時間演算法在理論上都被認為是「可接受的」——這也是 P 類問題的定義(詳見最後一節)。
指數時間 O(2ⁿ)
通常來自列舉所有子集合(n 個元素的集合有 2n 個子集)。暴力解背包問題、子集合加總問題屬此等級。n=30 時已達 109 次,n=50 完全不可行。
階乘時間 O(n!)
列舉所有排列的代價(n 個元素有 n! 種排列)。暴力解旅行推銷員問題即為此等級。n=12 時約 4.8×108;n=15 已超過 1012。
例題:用算式推導迴圈複雜度
直覺「兩層迴圈就是 O(n2)」常常對,但考試要的是算式。內層迴圈的次數會隨外層變化時,必須用 Σ 展開、套求和公式,才算完整推導。
(a) 成長率排序
把下列 8 個複雜度由慢到快排序:
O(n7), O(n2), O(loglogn), O(logn), O(n), O(n!), O(2n), O(1)
只要握住三大群組 常數 / 對數 / 多項式 / 指數 / 階乘,再比群內:
O(1)⊂O(loglogn)⊂O(logn)⊂O(n)⊂O(n2)⊂O(n7)⊂O(2n)⊂O(n!)
- loglogn 比 logn 更慢(對 logn 再取一次對數)
- 多項式之間比次方:n<n2<n7
- 指數勝過所有多項式:對任意固定 k,nk/2n→0
- 階乘又勝過指數:2n/n!→0(由 Stirling 近似 n!∼2πn(n/e)n 可知)
(b) 巢狀迴圈的算式推導
下列片段其實是選擇排序的 1-indexed 寫法:
for i := 1 to n-1 do
{
j := i;
for k := i+1 to n do
if (a[k] < a[j]) then j := k;
swap(a[i], a[j]);
}
以比較次數(內層 if 的執行次數)作為代價單位,逐層拆解:
- 外層 i:從 1 到 n−1,共 n−1 輪
- 內層 k:對每個固定的 i,k 從 i+1 掃到 n,共 n−i 次比較
- 外層每輪末尾的 j:=i 與
swap 都是 O(1),合計 O(n),不影響最高階
總比較次數:
T(n)=∑i=1n−1(n−i)
做變數代換 ℓ=n−i(當 i=1 時 ℓ=n−1,當 i=n−1 時 ℓ=1):
T(n)=∑ℓ=1n−1ℓ=1+2+⋯+(n−1)=2(n−1)n
展開並加上外層 O(n):
T(n)=2n2−n+O(n)=21n2−21n+O(n)
取最高階項、捨棄常數係數:
T(n)=O(n2)
這個推導是選擇排序 O(n2) 的來源:不論輸入是否有序,內層的 n−i 次比較無法提前中止,每一輪都要掃完未排序區,所以 best / average / worst 全是 Θ(n2)。對比之下,泡泡排序在已排序輸入上可以用 flag 提前退出,best case 退化成 O(n)。
常見錯誤是寫「外層 n 次、內層 n 次,所以 n×n=n2」就交卷。這在內層範圍隨外層變化時不嚴謹——老師會扣分,因為 n2 的常數係數是 21、不是 1。正確做法必須把 Σ 展開、套等差求和公式。
最壞、最佳、平均情況
同一演算法面對不同輸入,執行時間可能差距很大:
- Worst case:輸入對演算法最不利的情況。這是 O 最常對應的場景。
- Best case:輸入對演算法最有利的情況。這是 Ω 最常對應的場景。
- Average case:對所有可能輸入取平均(需要假設輸入分佈)。
Quick Sort 是最具代表性的例子:選一個 pivot 將陣列分成兩部分,遞迴排序。
- Best / Average case:O(nlogn):每次 pivot 大概把陣列切成一半,遞迴深度是 logn,每層合計掃 n 個元素,共 nlogn。
- Worst case:O(n2):每次 pivot 都選到最大或最小值,陣列被切成 1 和 n-1,退化成一層一層剝,遞迴深度變 n,共 n2。這在已排序的輸入上很容易發生(取決於 pivot 選法)。
| 演算法 | Best | Average | Worst |
|---|
| Binary Search | O(1) | O(logn) | O(logn) |
| Linear Search | O(1) | O(n) | O(n) |
| Bubble Sort | O(n) | O(n2) | O(n2) |
| Merge Sort | O(nlogn) | O(nlogn) | O(nlogn) |
| Quick Sort | O(nlogn) | O(nlogn) | O(n2) |
Merge Sort 的三種情況全都是 Θ(nlogn),這讓它在輸入分佈未知時是更穩定的選擇。Quick Sort 在實作上通常比 Merge Sort 快(cache 友好、原地排序),但需要好的 pivot 策略(如隨機選 pivot 或 median-of-three)來避免 worst case。Merge Sort 的代價是需要 O(n) 的額外空間來合併。
主定理(Master Theorem)
分治法將問題拆成若干較小子問題,遞迴解決後合併結果。這類演算法的時間複雜度通常為:
T(n)=aT(bn)+f(n),a≥1,b>1
- a:每次遞迴拆成幾個子問題
- b:每個子問題的規模縮小比例(變成 n/b)
- f(n):每次遞迴的合併/拆分代價
主定理提供直接判定 T(n) 成長速度的方法。
關鍵值 c=logba
nc 可以想成遞迴樹總共有多少個葉節點的代價。比較 f(n)(合併代價)與 nc(遞迴天然代價),誰大誰主導。
套用流程(四步)
- 識別 a、b、f(n)
- 計算 c=logba
- 比較 f(n) 與 nc:誰成長快?
- 根據結果對應到 Case 1 / 2 / 3,寫出答案
Case 1:遞迴主導 → T(n)=Θ(nc)
條件:f(n) 比 nc 慢一個多項式因子,即 f(n)=O(nc−ε),某個 ε>0。
手把手:T(n)=8T(n/2)+O(n2)
| 步驟 | 計算 |
|---|
| 1. 識別 | a=8, b=2, f(n)=n2 |
| 2. 求 c | c=log28=3,所以 nc=n3 |
| 3. 比較 | f(n)=n2,nc=n3,f 比 nc 慢一個多項式(n−1) |
| 4. 判定 | Case 1,T(n)=Θ(n3) |
Case 2:勢均力敵 → T(n)=Θ(nclogn)
條件:f(n)=Θ(nc),兩邊同階。每層遞迴都貢獻類似代價,共 logn 層,所以多一個 log。
手把手(Merge Sort):T(n)=2T(n/2)+O(n)
| 步驟 | 計算 |
|---|
| 1. 識別 | a=2, b=2, f(n)=n |
| 2. 求 c | c=log22=1,所以 nc=n |
| 3. 比較 | f(n)=n 與 nc=n 同階 |
| 4. 判定 | Case 2,T(n)=Θ(nlogn) |
Case 3:合併主導 → T(n)=Θ(f(n))
條件:f(n) 比 nc 快一個多項式因子,即 f(n)=Ω(nc+ε),另需滿足正則條件(實務上大多成立,不詳述)。
手把手:T(n)=T(n/2)+O(n)
| 步驟 | 計算 |
|---|
| 1. 識別 | a=1, b=2, f(n)=n |
| 2. 求 c | c=log21=0,所以 nc=1 |
| 3. 比較 | f(n)=n,nc=1,f 比 nc 快一個多項式 |
| 4. 判定 | Case 3,T(n)=Θ(n) |
更多範例對照表
| 遞迴式 | a | b | c=logba | f(n) | Case | T(n) |
|---|
| Binary search: T(n/2)+O(1) | 1 | 2 | 0 | 1 | 2 | Θ(logn) |
| Merge sort: 2T(n/2)+O(n) | 2 | 2 | 1 | n | 2 | Θ(nlogn) |
| Strassen: 7T(n/2)+O(n2) | 7 | 2 | log27≈2.81 | n2 | 1 | Θ(nlog27) |
| T(n/2)+O(n) | 1 | 2 | 0 | n | 3 | Θ(n) |
| 4T(n/2)+O(n2) | 4 | 2 | 2 | n2 | 2 | Θ(n2logn) |
| 3T(n/2)+O(n2) | 3 | 2 | log23≈1.58 | n2 | 3 | Θ(n2) |
口訣:比較 f(n) 和 nc。f 慢 → Case 1,答案就是 nc;同階 → Case 2,多一個 log;f 快 → Case 3,答案就是 f(n)。
主定理不是萬能的,有些遞迴式不在它的適用範圍內。例如 T(n)=2T(n/2)+nlogn——f(n)=nlogn 比 nc=n 大,但差的只是一個 logn 因子(不是多項式因子),所以不滿足 Case 3 也不屬於 Case 2。這類情況要改用遞迴樹法或代入法,結果為 Θ(nlog2n)。
例題:用展開法(不用主定理)推導 T(n)=3T(n/3)+O(n)
為什麼要會展開法:主定理是一個「查表規則」,考試如果老師尚未教到、或題目明說「不得使用主定理」,直接寫結果會被扣分。展開法(又稱 iteration method、substitution by unfolding)是把遞迴式一層一層代入,靠 Σ 求和算出精確階數,屬於第一性原理的推導。
設 T(1)=1,且
T(n)=3T(n/3)+cn
其中 c 為常數,代表合併步驟的線性代價。
第 1 層代入
T(n)=3T(n/3)+cn
第 2 層代入:把 T(n/3)=3T(n/9)+c(n/3) 帶回
T(n)=3[3T(n/9)+c3n]+cn=9T(n/9)+cn+cn=9T(n/9)+2cn
第 3 層代入:把 T(n/9)=3T(n/27)+c(n/9) 帶回
T(n)=9[3T(n/27)+c9n]+2cn=27T(n/27)+cn+2cn=27T(n/27)+3cn
觀察規律:展開到第 k 層
T(n)=3kT(3kn)+k⋅cn
合併項 kcn 是關鍵——每展開一層,都會再添加一整層的 cn 合併代價(因為 3 個子問題每個帶回 c(n/3),加起來剛好又是 cn)。
觸底條件:展開到 T(1) 時停下,即 n/3k=1,解得
3k=n⟹k=log3n
代回
T(n)=3log3n⋅T(1)+cnlog3n=n⋅1+cnlog3n
T(n)=n+cnlog3n=Θ(nlogn)
最後一步用到 log3n=log3logn,log3 是常數可吸收到大 O 裡。
| 層 k | 子問題大小 | 子問題個數 | 該層合併代價 |
|---|
| 0 | n | 1 | cn |
| 1 | n/3 | 3 | 3⋅c(n/3)=cn |
| 2 | n/9 | 9 | 9⋅c(n/9)=cn |
| ⋮ | ⋮ | ⋮ | ⋮ |
| k | n/3k | 3k | 3k⋅c(n/3k)=cn |
| 葉層 | 1 | n | n⋅T(1)=n |
共 log3n 層,每層 cn,加上葉層 n⋅T(1)=n,總和:
T(n)=n+cnlog3n=Θ(nlogn)
這個結果也和主定理的 Case 2 一致(a=3,b=3,c=log33=1,f(n)=n=nc,同階 → Θ(nlogn)),但展開法給出的是推導過程、不是查表。
展開法的關鍵步驟:
- 逐層代入 2–3 次,手動寫出前幾步,看出係數規律
- 寫出一般式 akT(n/bk)+(合併項之和)
- 找觸底條件 n/bk=1,解 k 通常是 logbn
- 代回求和 並化簡到 Θ 級別
問題複雜度(簡介)
- P:能在多項式時間 O(nk) 內解決的問題(排序、Dijkstra、最大流)
- NP:能在多項式時間內驗證一個解是否正確的問題
- NP-complete:NP 中最難的一批——所有 NP 問題都可多項式時間規約到它(TSP、SAT、背包)
- P ⊆ NP,但 P = NP 尚未證明
常見考法
| 題型 | 必備技能 |
|---|
| 成長率排序 | 背牢 O(1)≺loglogn≺logn≺n≺nlogn≺n2≺nk≺2n≺n! |
| 給 code 求複雜度 | 內層會變動 → 用 Σ 展開;∑i=1ni=2n(n+1) 要會背 |
| 主定理套用 | a,b,f(n)→c=logba→ 比 f 與 nc → Case 1/2/3 |
| 展開法推導 | 展 3 層看係數、找觸底 k=logbn、代回求和 |
| 區分 O/Ω/Θ | 「緊界」一定是 Θ;上界 O;下界 Ω |
| 辨識 P / NP / NPC | NP-complete 代表「無多項式解」,要走近似或啟發式 |