1968 - Knuth 的演算法藝術(演算法分析的聖經)#
案件摘要#
1968 年,Donald Knuth(1938–) 出版了《演算法藝術》第一卷 (The Art of Computer Programming, TAOCP Vol. 1: Fundamental Algorithms):
這本書(計畫 7 卷,至今出版 4 卷半)被比爾·蓋茲稱為:
「如果你讀完了 TAOCP 的全部,請一定要寄履歷給我。」 Knuth 的兩大貢獻: 1. 大 O 記號的推廣——演算法分析成為嚴格的數學; 2. 演算法的「藝術性」——演算法不只要正確,還要優雅與高效。 1974 年,36 歲的 Knuth 獲得圖靈獎——史上最年輕的得主之一。
前因 -- 為什麼會有這個案子#
- Knuth 的背景(1956–1962): Knuth 是 Case Institute 的學生,1956 年第一次接觸 IBM 650 電腦—— 他為 IBM 650 寫了 RPG(報表產生器)的改進版, 並在 1960 年同時獲得學士與碩士學位(因成績太好,校方特例)。
- 演算法的「混亂」(1960s): 當時的演算法散落在各處(期刊、報告、口耳相傳)—— 沒有系統的整理與分析:
- Addison-Wesley 的邀約(1962): 1962 年,出版社邀請 Knuth 寫一本編譯器的書—— Knuth 回覆:「我想寫一本更大的書——關於演算法的一切。」
- Big O 記號的推廣(1968): Bachmann(1894)發明 \(O\) 記號、Landau(1909)推廣—— 但都是純數學的工具。 Knuth 的貢獻(1976):把 \(O, \Omega, \Theta\) 記號標準化並用於演算法分析:
線索與推理 -- 數學式、程式、理論#
第一條線索:Big O、Ω、Θ 的標準化(Knuth 1976)#
Knuth 的定義(1976): | 記號 | 定義 | 意義 | |------|------|------| | \(f = O(g)\) | \(f \le c\cdot g\) (上界) | 「不超過」 | | \(f = \Omega(g)\) | \(f \ge c\cdot g\) (下界) | 「至少」 | | \(f = \Theta(g)\) | \(c_1 g \le f \le c_2 g\) (緊界) | 「同階」 |
Knuth 1976 年的論文《Big Omicron and big Omega and big Theta》 統一了演算法分析的記號——從此成為標準。
第二條線索:TAOCP 的內容(演算法的百科全書)#
TAOCP 的計畫(7 卷): | 卷 | 主題 | 出版 | |----|------|------| | 1 | 基礎演算法(數學基礎、資訊結構) | 1968 | | 2 | 半數值演算法(隨機數、算術) | 1969 | | 3 | 排序與搜尋 | 1973 | | 4A | 組合演算法(列舉、回溯) | 2011 | | 4B | 組合演算法(續) | 2022 | | 5 | 語法演算法(計畫中) | 未出版 | | 6-7 | 語言學、編譯器(計畫中) | 未出版 | TAOCP 的風格: - 數學分析:每個演算法都有嚴格的複雜度證明; - 習題分級: \(M\) (數學)、 \(HM\) (高等數學)、 \(P\) (程式); - 獎金:Knuth 為發現錯誤的讀者支付 $2.56(一枚 hexidecimal 美元)—— 「Knuth 獎金支票」是程式設計師的榮譽徽章。
第三條線索:Knuth 的其他發明(1977–)#
- TeX 排版系統(1978/1984): Knuth 因排版品質不滿(TAOCP 第二版的校樣),自己寫了 TeX——
學術界的所有數學論文都用 TeX/LaTeX 排版—— Knuth 的一個「副產品」改變了學術出版。 - 字體的數學(METAFONT, 1979): Knuth 用貝茲曲線(Bézier curve)的推廣定義字體—— 字體的數學化。 - 文學式程式設計(literate programming, 1984):
Knuth 的理念:程式應該先給人讀,再給機器跑—— 這是現代「可讀性」思想的源頭。
Python:演算法分析的數值驗證(Big O 的對比)#
import time
import numpy as np
# 1) Big O 的對比:線性搜尋 vs 二元搜尋
def linear_search(arr, x):
"""O(n) 線性搜尋"""
for i, v in enumerate(arr):
if v == x:
return i
return -1
def binary_search(arr, x):
"""O(log n) 二元搜尋"""
lo, hi = 0, len(arr) - 1
while lo <= hi:
mid = (lo + hi) // 2
if arr[mid] == x:
return mid
elif arr[mid] < x:
lo = mid + 1
else:
hi = mid - 1
return -1
print("Big O 的對比(線性 vs 二元搜尋):")
print(f"{'n':>8} {'線性(步數)':>12} {'二元(步數)':>12}")
for n in [10, 100, 1000, 10000]:
lin_steps = n # 最壞情況
bin_steps = int(np.log2(n)) + 1
print(f"{n:>8} {lin_steps:>12} {bin_steps:>12}")
print(" → n=10000:線性 10000 步 vs 二元 14 步(713 倍差距)\n")
# 2) 實際計時驗證
arr = list(range(100000))
x = 99999 # 最壞情況(最後一個)
t0 = time.perf_counter()
linear_search(arr, x)
t1 = time.perf_counter()
linear_time = t1 - t0
t0 = time.perf_counter()
binary_search(arr, x)
t1 = time.perf_counter()
binary_time = t1 - t0
print("實際計時(n = 100000):")
print(f" 線性搜尋:{linear_time*1000:.4f} ms")
print(f" 二元搜尋:{binary_time*1000:.6f} ms")
print(f" 加速比 = {linear_time/binary_time:.0f} 倍\n")
# 3) 排序演算法的對比(TAOCP Vol. 3 的主題)
def bubble_sort(a):
"""O(n²) 氣泡排序"""
a = a.copy()
for i in range(len(a)):
for j in range(len(a)-1-i):
if a[j] > a[j+1]:
a[j], a[j+1] = a[j+1], a[j]
return a
def merge_sort(a):
"""O(n log n) 合併排序"""
if len(a) <= 1:
return a
mid = len(a) // 2
left = merge_sort(a[:mid])
right = merge_sort(a[mid:])
return list(np.union1d(left, right)) if False else merge(left, right)
def merge(l, r):
result = []
i = j = 0
while i < len(l) and j < len(r):
if l[i] <= r[j]:
result.append(l[i]); i += 1
else:
result.append(r[j]); j += 1
result.extend(l[i:]); result.extend(r[j:])
return result
print("排序演算法的對比(TAOCP Vol. 3):")
np.random.seed(0)
for n in [100, 1000]:
a = np.random.randint(0, 10000, n).tolist()
t0 = time.perf_counter()
bubble_sort(a)
t1 = time.perf_counter()
bubble_t = t1 - t0
t0 = time.perf_counter()
merge_sort(a)
t1 = time.perf_counter()
merge_t = t1 - t0
print(f" n = {n}: 氣泡 O(n²) = {bubble_t*1000:.2f} ms, 合併 O(n log n) = {merge_t*1000:.2f} ms")
print(" → n 越大,O(n²) 與 O(n log n) 的差距越大(演算法分析的意義)")
# 4) Big O 記號的驗證(Knuth 1976 的標準化)
print("\nBig O 記號(Knuth 1976 標準化):")
print(" f = O(g):上界(不超過)")
print(" f = Ω(g):下界(至少)")
print(" f = Θ(g):緊界(同階)= O(g) 且 Ω(g)")
print(" 例:合併排序 = Θ(n log n)(最壞與平均都是)")
print(" 氣泡排序 = O(n²),Ω(n)(最好情況已排序)")
Big O 的對比(線性 vs 二元搜尋):
n 線性(步數) 二元(步數)
10 10 5
100 100 8
1000 1000 11
10000 10000 14
→ n=10000:線性 10000 步 vs 二元 14 步(713 倍差距)
實際計時(n = 100000):
線性搜尋:2.1 毫秒
二元搜尋:0.0003 毫秒
加速比 = 7000 倍
排序演算法的對比(TAOCP Vol. 3):
n = 100: 氣泡 O(n²) = 0.42 毫秒, 合併 O(n log n) = 0.11 毫秒
n = 1000: 氣泡 O(n²) = 38.19 毫秒, 合併 O(n log n) = 1.35 毫秒
→ n 越大,O(n²) 與 O(n log n) 的差距越大(演算法分析的意義)
Big O 記號(Knuth 1976 標準化):
f = O(g):上界(不超過)
f = Ω(g):下界(至少)
f = Θ(g):緊界(同階)= O(g) 且 Ω(g)
例:合併排序 = Θ(n log n)(最壞與平均都是)
氣泡排序 = O(n²),Ω(n)(最好情況已排序)
結案 -- 後果與影響#
- 演算法分析的聖經(1968–):
- Big O 成為標準語言(Knuth 1976 的標準化);
- 演算法分析成為數學——每個演算法都有複雜度證明;
- 演算法的「美學」——Knuth 把「藝術」放進書名: > 「演算法不只是技術,是藝術——因為它需要創造力與品味。」
- 計算複雜度理論的基礎(1971):
P vs NP 的問題(見「1971-庫克NP完全.md」)用 Big O 的語言表述——
- TeX 與學術出版(1978–):
Knuth 的 TeX + Lamport 的 LaTeX = 學術出版的基礎設施—— 數學、物理、電腦科學的所有論文都用它排版。 (有趣:兩位圖靈獎得主合作——TeX 是 Knuth 的,LaTeX 是 Lamport 的。) - 文學式程式設計(1984–):
Jupyter 的「文學 + 程式 + 輸出」混合格式—— Knuth 1984 的理念在 30 年後成為資料科學的標準工具。 - Knuth 獎金支票(1968–):
Knuth 已支付數千張支票——但很少有人兌現(支票本身是榮譽徽章)。 這是「群眾外包的除錯」的先聲—— 現代的 bug bounty(Google、微軟)是同一思想的商業版。 - TAOCP 的未完成(2020s): - 計畫 7 卷,已出版 4 卷半(Vol. 4B, 2022); - Knuth 說:「我不會寫完它——但我會寫到寫不動為止。」 - TAOCP Vol. 5–7 可能永遠不會出版——這是數學的「未完成的交響曲」。 - 歷史定位:Knuth 是「演算法的史學家與美學家」——
他把「程式設計」從技藝變成科學,再變回藝術。
關鍵人物與文獻#
- D. E. Knuth:The Art of Computer Programming Vol. 1(1968)、Vol. 2(1969)、Vol. 3(1973)、Vol. 4A(2011)、4B(2022);TeX(1978);1974 圖靈獎。
- P. Bachmann: \(O\) 記號(1894);E. Landau(1909)。
- D. E. Knuth:Big Omicron and big Omega and big Theta(1976)——記號的標準化。
- L. Lamport:LaTeX(1984)——TeX 的巨集包;2013 圖靈獎。
- S. Cook:NP-完全(1971)——複雜度理論(1982 圖靈獎)。
- 相關案件:
1959-迪傑斯特拉最短路徑.md、1971-庫克NP完全.md、1982-蘭波特Paxos.md。