作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!
數學歸納法
數學歸納法(mathematical induction)是一種數學證明方法,常用於證明命題(命題是對某個現象的描述)在自然數范圍內成立。隨着現代數學的發展,自然數范圍內的證明實際上構成了許多其他領域(比如數學分析)的基礎,所以數學歸納法對於整個數學體系至關重要。
數學歸納法本身非常簡單。如果我們想要證明某個命題對於自然數n都成立,那么:
第一步 證明命題對於n = 1成立。
第二步 假設命題對於n成立,n為任意自然數,證明在此假設下,命題對於n+1成立。
命題得證
想一下上面的兩個步驟。它們實際上意味着,命題對於n = 1成立 -> 命題對於n = 2成立 -> 命題對於n = 3成立……直到無窮。因此,命題對於任意自然數都成立。這就好像多米諾骨牌,我們確定n的倒下會導致n + 1的倒下,然后推倒第一塊骨牌,就能保證任意骨牌的倒下。
我們來看一下使用數學歸納法來證明高斯求和公式:
n為任意自然數。
(這個公式據說是高斯小學時想出來的。老師懲罰全班同學,必須算出1到100的累加,才能回家。於是高斯想出了上面的方法。天才都是被逼出來的么?)
我們的命題是: 高斯求和公式對於任意自然數n都成立。
下面為數學歸納法的證明步驟:
第一步 n = 1,等式左邊(1的累加)為1,右邊(右邊公式代入n=1)也為1,等式兩邊相等,等式成立,因此命題對於 n = 1 成立。
第二步 假設上述公式對於任意n成立, 即1到n的累加為n*(n+1)/2
那么,對於n+1,等式的左邊(從1到n+1的累加)等於n*(n+1)/2 + (n+1),即(n+1)*(n+2)/2,
等式的右邊的n用n+1代替,成為(n+1)*(n+2)/2,
等式兩邊相等,等式成立。因此,當假設命題對於n成立時,命題對於n+1成立。
因此,命題得證。
遞歸
遞歸(recursion)是計算機中的重要概念,它是指一個計算機程序調用其自身。為了保證計算機不陷入死循環,遞歸要求程序有一個能夠達到的終止條件(base case)。比如下面的程序,是用於計算高斯求和公式:
/* * Gauss summation */
int f(n) { if (n == 1) { return 1; // base case
} else { return f(n-1) + n; // induction
} }
在程序中規定了f(1)的值,以及f(n)和f(n-1)的關系。這正是數學歸納法思想的體現。想要得到f(n),必須計算f(n-1);想要f(n-1),必須計算f(n-2)……直到f(1)。由於我們已經知道了f(1)的值,我們就可以填補前面所有的空缺,最終返回f(n)的值。
遞歸是數學歸納法在計算機中的程序實現。使用遞歸設計程序的時候,我們設置base case,並假設我們會獲得n-1的結果,並實現n的結果。這就好像數學歸納法,我們只關注初始和銜接,而不需要關注具體的每一步。
棧
遞歸是用棧(stack)數據結構實現的。正如我們上面所說的,計算f(n),需要f(n-1);計算f(n-1),需要f(n-2)……。我們在尋找到f(1)之前,會有許多空缺: f(n-1)的值什么? f(n-2)的值是什么? …… f(2)的值是什么?f(1)的值是什么? 我們的第一個問題是f(n)是什么,結果,這個問題引出下一個問題,再下一個問題…… 每個問題的解答都依賴於下一個問題,直到我們找到第一個可以回答的問題: f(1)的值是什么?
我們用棧來保存我們在探索過程中的疑問。C語言中,函數的調用已經是用棧記錄離場情境和返回地址。遞歸是函數對自身的調用,所以很自然的,遞歸用棧來保存我們的“疑問” 。
我們假設棧向下增長。首先,我們調用f(100),那么當執行到
return f(n-1) + n;
f(100)暫停執行,並記錄當前的狀態,比如n的值,當前執行到的位置。隨后調用f(99),棧增加一個frame,直到調用f(98) ... 棧不斷增長,直到f(1)。f(1)得到結果1,並返回給f(2)。f(1)棧frame刪除,轉移到f(2)frame情境中繼續執行
return f(n-1) + n;
然后返回給f(3) ... 直到f(99)返回給f(100),並執行
return f(n-1) + n;
返回f(100)的值,得到結果。
上述過程是C編譯器自動完成的。在實現遞歸算法時,也可以自行手動實現棧。這樣可以得到更好的運行效率。
總結
數學歸納法
遞歸
棧
歡迎繼續閱讀“紙上談兵: 算法與數據結構”系列。