【什么是遞歸】
在數學與計算機科學中,遞歸 (Recursion))是指在函數的定義中使用函數自身的方法,直觀上來看,就是某個函數自己調用自己。
遞歸有兩層含義:
遞歸問題必須可以分解為若干個規模較小、與原問題形式相同的子問題。並且這些子問題可以用完全相同的解題思路來解決;
遞歸問題的演化過程是一個對原問題從大到小進行拆解的過程,並且會有一個明確的終點(臨界點)。
一旦原問題到達了這個臨界點,就不用再往更小的問題上拆解了。最后,從這個臨界點開始,把小問題的答案按照原路返回,原問題便得以解決。
簡而言之,遞歸的基本思想就是把規模大的問題轉化為規模小的相同的子問題來解決。 在函數實現時,因為大問題和小問題是一樣的問題,因此大問題的解決方法和小問題的解決方法也是同一個方法。這就產生了函數調用它自身的情況,這也正是遞歸的定義所在。
格外重要的是,這個解決問題的函數必須有明確的結束條件,否則就會導致無限遞歸的情況。
總結起來,遞歸的實現包含了兩個部分,一個是遞歸主體,另一個是終止條件。
【漢諾塔問題】
漢諾塔問題是源於印度一個古老傳說的益智玩具。大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞着 64 片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上,並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。
我們可以把這個問題抽象為一個數學問題。如下圖所示,從左到右有 x、y、z 三根柱子,其中 x 柱子上面有從小疊到大的 n 個圓盤。現要求將 x 柱子上的圓盤移到 z 柱子上去。要求是,每次只能移動一個盤子,且大盤子不能被放在小盤子上面。求移動的步驟。
我們來分析一下這個問題。這是一個大規模的復雜問題,如果要采用遞歸方法去解決的話,就要先把問題化簡。
我們的原問題是,把從小到大的 n 個盤子,從 x 移動到 z。
我們可以將這個大問題拆解為以下 3 個小問題:
把從小到大的 n-1 個盤子,從 x 移動到 y;
接着把最大的一個盤子,從 x 移動到 z;
再把從小到大的 n-1 個盤子,從 y 移動到 z。
代碼如下:
public static void main(String[] args) { String start = "x"; String temp = "y"; String end = "z"; hanio(6, start, temp, end); }
public static void hanio(int n, String start, String temp, String end) { if (n < 1) { System.out.println("漢諾塔的層數不能小於1"); } else if (n == 1) { System.out.println("移動: " + start + " -> " + end); return; } else { hanio(n - 1, start, end, temp); System.out.println("移動: " + start + " -> " + end); hanio(n - 1, temp, start, end); } } |
拋開用於處理輸入異常的代碼部分不談,它的代碼包含了 2 個部分:
終止條件,即如何處理小規模的問題,實現的代碼量一定是很少的;
遞歸體,即大問題向小問題分解的過程,實現的代碼量也不會太多。
因此,一個復雜問題的遞歸實現,通常代碼量都不會很多。
【總結】
遞歸的核心思想是把規模大的問題轉化為規模小的相似的子問題來解決。
在函數實現時,因為解決大問題的方法和解決小問題的方法往往是同一個方法,所以就產生了函數調用它自身的情況。
另外這個解決問題的函數必須有明顯的結束條件,這樣就不會產生無限遞歸的情況了。