04.3 演算法複雜度初探:離散數學與計算的可操作性#
從「能解」到「多快能解」#
前三節我們回答「有多少?」;本章問「找到答案要多長時間?」。這個轉向的歷史節點是:
- 1936 年,圖靈:定義了「可計算」,但兩個都可計算的問題,實用上可能天差地遠。
- 1965 年,Cobham 與 Edmonds:提出「多項式時間 vs 指數時間」的劃分,Edmonds 並首次呼籲研究「好的演算法」(如他的最小成本擴充路徑解最大流)。
- 1971 年,Cook 的 NP-完全性定理:把「多項式 vs 指數」升級為數學上精確的未解問題 P vs NP——Clay 研究所懸賞百萬美元。
這一章把前幾章的計數成果( \(n!\) 、 \(2^n\) 、Fibonacci 的 \(\varphi^n\) 、二項式係數)全部兌現成複雜度語言。
漸近記號#
定義#
設 \(f, g\) 為非負函數:
- \(f = O(g)\) :存在 \(c, n_0\) ,使 \(n \ge n_0\) 時 \(f(n) \le c\,g(n)\) (上界)。
- \(f = \Omega(g)\) : \(g = O(f)\) (下界)。
- \(f = \Theta(g)\) :同時成立(緊界)。
注意:大 \(O\) 論的是極限趨勢,常數因子與小規模行為都被抹去;它回答的是「問題規模放大時,代價怎麼長」。
增長賽跑(用 03 章的成果)#
由 Stirling 公式 \(n! \sim \sqrt{2\pi n}(n/e)^n\) ,得到經典排序(由慢到快):
import math
for n in (10, 20, 30, 50):
print(f"n={n}: log2n={math.log2(n):6.1f} n²={n**2:7d} "
f"2^n={2**n:.1e} n!={math.factorial(n):.1e}")
# n=50: n² = 2.5e3,2^n = 1.1e15,n! = 3.0e64
# n! 已達可觀測宇宙原子數(約 1e80)的量級
工程直覺: \(n^2\) 演算法在 \(n = 10^6\) 時要 \(10^{12}\) 步(分鐘級), \(2^n\) 在 \(n = 100\) 時需 \(10^{13}\) 倍宇宙年齡——複雜度階之差,不是快慢之差,而是可行與不可行之差。這就是密碼學(128 位金鑰 \(= 2^{128}\) 空間)能站穩腳跟的數學根據,也是 03.1 節乘法法則的回響。
分治與 Master 定理#
合併排序的分析#
合併排序把長 \(n\) 陣列對半分、遞迴排序、線性合併:
展開法(延續 04.1):第 \(i\) 層有 \(2^i\) 個子問題、各長 \(n/2^i\) 、每個合併代價 \(\Theta(n/2^i)\) ,該層總計 \(\Theta(n)\) ;共 \(\log_2 n\) 層,故
定理(Master 定理,簡式)#
設 \(T(n) = a\,T(n/b) + \Theta(n^d)\) , \(a \ge 1, b > 1\) ,則
證明梗概(展開樹 + 幾何級數):展開樹共 \(a^{\log_b n} = n^{\log_b a}\) 個葉(每葉代價 \(\Theta(1)\) )。第 \(i\) 層總代價為 \(\Theta(n^d)\,(a/b^d)^i\) ,是公比 \(r = a/b^d\) 的幾何級數: - \(r < 1\) ( \(d > \log_b a\) ):級數由頂層主導, \(T = \Theta(n^d)\) ; - \(r = 1\) :各層等額 \(\Theta(n^d)\) ,乘層數 \(\log_b n\) ; - \(r > 1\) ( \(d < \log_b a\) ):由底層主導, \(T = \Theta(n^{\log_b a})\) 。 \(\blacksquare\)
檢驗:合併排序 \(a=2, b=2, d=1\) , \(\log_b a = 1 = d\) ,得 \(\Theta(n \log n)\) 。二元搜尋 \(a=1, b=2, d=0\) ,得 \(\Theta(\log n)\) 。Karatsuba 乘法(1960,把 \(n\) 位乘法從 \(n^2\) 降到 \(n^{\log_2 3} \approx n^{1.585}\) ,而掀起演算法學革命) \(a=3, b=2, d=1\) ,得 \(\Theta(n^{1.585})\) 。
演算法策略與其複雜度譜系#
| 策略 | 代表問題 | 複雜度 | 註記 |
|---|---|---|---|
| 窮舉 | 漢諾塔模擬、TSP 暴力 | \(O(2^n)\) / \(O(n!)\) | 03 章的計數告訴我們窮舉空間多大 |
| 分治 | 合併排序、Karatsuba | \(O(n \log n)\) 、 \(n^{1.585}\) | Master 定理 |
| 貪婪 | 最小生成樹 | \(O(E \log V)\) | 需要擬陣理論證明正確性 |
| 動態規劃 | 背包、編輯距離 | \(O(nW)\) 、 \(O(n^2)\) | 記憶化 04.1 的 Fibonacci |
| 隨機化 | 快排平均 | \(O(n \log n)\) | 機率母函數(04.2) |
| 遞迴搜尋 | 飽和回溯 | 指數級 | Hamiltonian 路徑至今無多項式演算法 |
例(動態規劃 vs 窮舉):0-1 背包窮舉 \(2^n\) ;DP 用「前 \(i\) 物、容量 \(w\) 」狀態表降到 \(O(nW)\) ——犧牲記憶體換時間,遞迴式 \(dp[i][w] = \max(dp[i-1][w],\ dp[i-1][w-w_i] + v_i)\) 正是 04.1「按最後一步分類」的直接翻譯。
例(快排平均複雜度,機率母函數的用武之地):隨機基準點下,期望比較次數滿足
(兩邊同乘 \(n\) 後相鄰相消,是 04.1 遠迴技巧與 04.2 生成函數的合奏。)快排最壞 \(O(n^2)\) 但平均 \(O(n \log n)\) 且常數極小——「平均 vs 最壞」的區分,正是離散機率進入演算法學的原因。
P 與 NP:可操作性的極限#
定義(非形式)#
- P:可在多項式時間求解的判定問題。
- NP:解的長度多項式,且可多項式時間驗證。
顯然 \(\text{P} \subseteq \text{NP}\) ;是否相等是本世紀最重要的公開問題。
兩個對照組#
- Hamiltonian 路徑問題(1857 年 Hamilton 的二十面體遊戲、1859 年 Kirkman 的學校女生問題同源):給定圖,是否存在經過每頂點恰一次的路徑?驗證一條給定路徑只要 \(O(n)\) (NP 內),但至今沒有已知的多項式演算法。對照組 Euler 路徑(1736,七橋問題)有一行判準:「奇度頂點為 0 或 2」, \(O(n)\) 判定——Euler 容易、Hamilton 難,是 P vs NP 鴻溝的教科書縮影。
- TSP:窮舉 \((n-1)!/2\) 條路線——03.2 的排列計數直接量化了絕望程度。Held–Karp 動態規劃(1962)用子集 DP 把 \(O(n!)\) 壓到 \(O(n^2 2^n)\) ,六十年來無人再進一步——組合爆炸可以被馴服,但未必能被消除。
Cook–Levin 定理(1971):SAT 是 NP-完全的——NP 中任何問題都能多項式歸約到它。此後 Karp(1972)用 21 個歸約把背包、分拆、團、著色等一網打盡。若任何一個 NP-完全問題找到多項式演算法,則 \(\text{P} = \text{NP}\) ,整個 NP 一次全解——歸約把千百個難題捆成一個。
為何本課程要談它:因為「這問題是 NP-完全」本身就是用計數與歸約證明出來的結論;知道邊界在哪,工程師才不會浪費半年尋找不存在的快演算法,而改投精確指數演算法、近似演算法或啟發式。
from itertools import permutations
# Hamiltonian:n=9 時窮舉 8!/2 = 20160 條路線尚可;n=15 即 4.4e10,絕望
def has_hamiltonian_brute(adj, n):
for perm in permutations(range(1, n), n-1):
path = (0,) + perm
if all(adj[path[i]][path[i+1]] for i in range(n-1)):
return True
return False
import itertools
def has_hamiltonian_dp(adj, n):
# Held-Karp:O(n^2 2^n) 子集 DP,n=20 也可行(約 4 億狀態)
dp = {(1 << v, v): adj[0][v] for v in range(1, n) if adj[0][v]}
for size in range(2, n):
new = {}
for (S, u), w in dp.items():
if bin(S).count("1") != size - 1: continue
for v in range(1, n):
if not (S >> v) & 1 and adj[u][v]:
key = (S | (1 << v), v)
if key not in new or new[key] > w + adj[u][v]:
new[key] = w + adj[u][v]
dp = new if size > 2 else {**dp, **new}
return bool(dp)
# 小圖驗證兩法一致
adj = [[0,1,1,0],[1,0,1,1],[1,1,0,1],[0,1,1,0]]
print(has_hamiltonian_brute(adj, 4), has_hamiltonian_dp(adj, 4)) # True True
章末小結#
- 大 \(O\) / \(\Theta\) / \(\Omega\) 把「多快」形式化;Stirling 與 03 章的計數給出增長階的真實尺規。
- Master 定理以展開樹與幾何級數一分鐘解掉所有 \(T(n) = aT(n/b) + n^d\) 。
- 策略光譜:窮舉 → 分治 → 貪婪/DP → 隨機化;DP 是「遞迴 + 記憶化」對 04.1 的直接兌現。
- Euler 容易、Hamilton 難、TSP 絕望而可馴——P vs NP 是組合爆炸的極限定律。
- 計數告訴我們問題空間多大;複雜度告訴我們探索空間多貴。兩者合起來,才是「可操作性」的完整圖像。
下一步#
本冊自 03 章的加乘法則出發,經排列組合、容斥與 Pólya、遞迴、生成函數,至此收束於複雜度。下一冊「圖論」將展開另一條主線:從 Euler 的七橋、Kirkman 的學校女生問題、Hamilton 的二十面體,到電話號碼與 Steiner 三元組的網路設計、Ramsey 理論與 EKR 定理——你會發現:本冊的每一件計數工具,在圖的世界裡都有一個新的戰場。