2.11 完全歸納
說到遞迴定義,費氏數(Fibonacci number)
是最常見的教科書例子之一。
簡而言之,第零個費氏數是 0, 第一個費氏數是 1, 之後的每個費氏數是之前兩個的和。
寫成遞迴定義如下:
fib :: Nat -> Nat
fib 0 = 0
fib 1 = 1
fib (2+n) = fib (1+n) + fib n
但這和我們之前談到的歸納定義稍有不同。
我們已知定義 f (Suc n) 時可假設 f 在 n 之上已有定義。
但在 fib 的定義中,fib (2+n) 用到了 fib 的前兩個值。這樣的定義是可以的嗎?
函數 fib 的定義可以視為完全歸納%
(又稱作強歸納)的例子。
回顧:先前介紹的數學歸納法中,使 P n 成立的前提之一是「對所有 n, 若 P n 成立, P (Suc n) 亦成立」。完全歸納則把這個前提增強如下:
給定述語
P :: Nat -> Bool. 若
對所有小於
n的值i,P i皆 成立,則P n亦成立,則我們可得知
P對所有自然數皆成立。
以更形式化的方式可寫成:
(∀ n . P n) <== (∀ n . P n <== (∀ i < n . P i)) (2.9 )
請注意:前提 P n <== (∀ i < n . P i) 隱含 P 0 成立,因為當 n := 0, 由於沒有自然數 i 滿足 i < n, 算式 (∀ i < n . P i) 可化簡為 True.
在完全歸納法之中,證明 P n 時,我們可假設 P 對所有小於 n 的值都已成立了。
對寫程式的人來說,有了完全歸納法,表示我們日後定義自然數上的函數 f :: Nat -> a 時,每個 f n 都可以自由使用 f 在所有小於 n 的輸入之上的值。因此 fib (2+n) 可以用到 fib (1+n) 與 fib n, 因為 n < 1+n < 2+n.
例 2.15
關於完全歸納,離散數學教科書中的一個常見例子是「試證明所有自然數都可寫成不相同的二的乘冪的和」,例如 $50 = 2^5 + 2^4 + 2$.
這可用完全歸納證明。我們以半形式的方式論述如下:
令 P n 為「$n$可寫成一串不相同的二的乘冪的和」. 對所有 n, 我們想要證明 P n <== (∀ i < n . P i). 當 n 為 Zero, 這一串數字即是空串列。
當 n 大於零,
我們可找到最接近
n但不超過n的二之乘冪,稱之為m(也就是 $m = 2^k$ 而且 $m \leq n < 2\times m$).由於 $n$ 不是
Zero, $m$ 也不是Zero. 也因此n - m < n.依據歸納假設
(∀ i < n . P i), 以及n - m < n,P (n-m)成立 ---n - m可以寫成不相同的二的乘冪的和。也因此
n可寫成不相同的二的乘冪的和 --- 把n-m加上 $m$ 即可.
上述證明僅用口語描述,因為如果形式化地寫下這個證明,就等同於寫個將自然數轉成一串二的乘冪的程式,並證明其正確性!下述函數 binary 做這樣的轉換,例如,binary 50 = [32,16,2]:
binary :: Nat -> List Nat
binary Zero = []
binary n = m : binary (n - m)
where m = last (takeWhile (<=n) twos)
twos = iterate (2 *:) 1
函數 binary 是一個完全歸納定義,和上述的證明對應得相當密切:串列 twos 是 [1,2,4,8...] 等等所有二的乘冪,m 是其中最接近而不超過 n 的。
遞迴呼叫 binary (n - m) 是許可的,因為 n - m < n, 而根據完全歸納,我們已假設對所有 i < n, binary i 皆有定義。
有了完全歸納法,我們在定義自然數上的函數時可允許更靈活的函數定義:
f :: Nat -> a
f b = .... {- 一些基底情況 -}
f n = ... f m ... f k ... {- 如果 m < n 且 k < n -}
f n 的右手邊可以出現不只一個遞迴呼叫,只要參數都小於 n.
但我們必須確定上述定義中的幾個子句足以包含所有狀況,沒有狀況被遺漏。
例如,我們若 把 fib 定義中的 fib 1 = ... 基底狀況去掉,
計算 fib 2 = fib 1 + fib 0 時便會出錯。
練習 2.43
證明 sum (binary n) = n.
答案
使用完全歸納。
狀況 n := Zero: 等號兩邊都歸約為 [].
狀況 n > Zero。
假設對於任何 0 <= i < n, sum (binary i) = i.
sum (binary n) =
= {- 令 m = last (takeWhile (<=n) twos), sum 之定義 -}
m + sum (binary (n - m))
= {- 因 n - m < n, 歸納假設 -}
m + (n - m)
= n
練習 2.44
證明當 n >= 1, fib (2+n) > {-"\alpha^n"-}, 其中
$\alpha = (1+\sqrt{5})/2$. 這個證明可用 n := 1 和 n := 2 當基底狀況。
答案
本證明的關鍵性質是 $\alpha^2 = (3+\sqrt{5})/2 = \alpha + 1$.
使用完全歸納證明 fib (2+n) > {-"\alpha^n"-},以 n := 1 和 n := 2 當基底狀況。
狀況 n := 1: fib 3 = 2 > {-"\alpha"-}.
狀況 n := 2: fib 4 = 3 > {-"(3+\sqrt{5})/2 = \alpha^2"-}.
狀況 n := 2+n 且 2+n > 4。
假設對於任何 3 <= i < 2+n, fib i > {-"\alpha^{i-2}"-}. 論證:
fib (2+n)
= fib (1+n) + fib n
> {- 歸納假設 -}
{-"\alpha^{n-1}"-} + {-"\alpha^{n-2}"-}
= {- 提出 $\alpha^{n-2}$-}
{-"(\alpha + 1) \times \alpha^{n-2}"-}
= {- $\alpha^2 = \alpha + 1$ -}
{-"\alpha^2 \times \alpha^{n-2}"-}
= {-"\alpha^n"-}
完全歸納 vs 簡單歸納 完全歸納有個較早的稱呼:強歸納(strong induction)。 原本的歸納法則相對被稱呼為簡單歸納或弱歸納。 強/弱歸納的稱呼可能使人以為完全歸納比簡單歸納更強 --- 意謂前者能證明出一些後者無法證明的定理。 事實上,完全歸納與簡單歸納是等價的:能用一個方法證出的定理,用另一個方法也能證出。 因此,使用哪一個純粹只是方便性的考量。
反應在程式設計上,給任一個完全歸納定義函數 f :: Nat -> A, 我們總能做出一個以簡單歸納定義的函數 fs :: Nat -> List A,
滿足 fs n = map f [n, n-1, ... 0].
例如,函數 fibs n 傳回 [fib n, fib (n-1).., fib 0].
fibs :: Nat -> List Nat
fibs 0 = [0]
fibs 1 = [1,0]
fibs (Suc n) = (x1+x0) : x1 : x0 : xs
where (x1:x0:xs) = fibs n
由 fib 到 fibs 的轉換可能令讀者想起演算法中的動態規劃(dynamic programming)。我們將在日後談到這個話題。
串列上的完全歸納
串列與自然數是類似的資料結構。串列上的完全歸納原則便是將 Zero 代換為 [],
將 Suc 代換為 (x:). 至於「小於」的關係,可定義為:
ys < xs <=> ys ∈ tails xs ⋀ ys /= xs
也就是說 ys 是 xs 的一個後段,但不是 xs 自己。
有了如上定義,串列上的完全歸納法是:
(∀ xs . P xs) <== (∀ xs . P xs <== (∀ ys < xs . P ys)) 應用在程式設計上,當定義 f xs 時,遞迴呼叫可作用在 xs 的任何後段上。
但對許多串列上的函數而言,這樣的模式還不夠靈活。我們得用下一節說到的良基歸納。