Java中的迭代與遞迴
遞迴
提到迭代,不得不提一個數學表示式: n!=n*(n-1)*(n-2)*...*1
有很多方法來計算階乘。有一定數學基礎的人都知道n!=n*(n-1)!因此,程式碼的實現可以直接寫成:
程式碼一
int factorial (int n) {
if (n == 1) {
return 1;
} else {
return n*factorial(n-1);
}
}
在執行以上程式碼的時候,其實機器是要執行一系列乘法的: factorial(n) → factorial(n-1) → factorial(n-2) → ... → factorial(1)。所以,需要不斷的跟蹤(跟蹤上次計算的結果)並呼叫乘法進行計算(構建一個乘法鏈)。這類不斷呼叫自身的運算形式稱之為 遞迴 。遞迴可以進一步的分為線性遞迴和數形遞迴。資訊量隨著演算法的輸入呈線性增長的遞迴稱之為線性遞迴。計算n!(階乘)就是線性遞迴。因為隨著N的增大,計算所需的時間呈線性增長。另外一種資訊量隨著輸入的增長而進行指數增長的稱之為樹形遞迴。
迭代
另外一種計算n!的方式是:先計算1乘以2,然後用其結果乘以3,再用的到的結果乘以4....一直乘到N。在程式實現時,可以定義一個計數器,每進行一次乘法,計數器都自增一次,直到計數器的值等於N截至。程式碼如下:
程式碼二
int factorial (int n) {
int product = 1;
for(int i=2; i<n; i++) {
product *= i;
}
return product;
}
和程式碼一相比,程式碼二沒有構建一個乘法鏈。在進行每一步計算時,只需要知道當前結果(product)和i的值就可以了。這種計算形式稱之為迭代。迭代有這樣幾個條件:1、有一個有初始值的變數。2、一個說明變數值如何更新的規則。3、一個結束條件。( 迴圈三要素:迴圈變數、迴圈體和迴圈終止條件 )。和遞迴一樣。時間要求隨著輸入的增長呈線性的可以叫做線性迭代。
迭代 VS 遞迴
比較了兩個程式,我們可以發現,他們看起來幾乎相同,特別是其數學函式方面。在計算n!的時候,他們的計算步數都是和n的值成正比的。但是,如果我們站在程式的角度,考慮他們是如何執行的話,那麼這兩個演算法就有很大不同了。
(注:原文中關於其區別寫的有點扯,這裡就不翻譯了,下面是筆者自己總結內容。)
首先分析遞迴,其實遞迴最大的有點就是把一個複雜的演算法分解成若干相同的可重複的步驟。所以,使用遞迴實現一個計算邏輯往往只需要很短的程式碼就能解決,並且這樣的程式碼也比較容易理解。但是,遞迴就意味著大量的函式呼叫。函式呼叫的區域性狀態之所以用棧來記錄的。所以,這樣就可能浪費大量的空間,如果遞迴太深的話還有可能導致堆疊溢位。
接下來分析迭代。其實,遞迴都可以用迭代來代替。但是相對於遞迴的簡單易懂,迭代就比較生硬難懂了。尤其是遇到一個比較複雜的場景的時候。但是,程式碼的難以理解帶來的有點也比較明顯。迭代的效率比遞迴要高,並且在空間消耗上也比較小。
遞迴中一定有迭代,但是迭代中不一定有遞迴,大部分可以相互轉換。
能用迭代的不要用遞迴,遞迴呼叫函式不僅浪費空間,如果遞迴太深的話還容易造成堆疊的溢位。
數形遞迴
前面介紹過,樹遞迴隨輸入的增長的資訊量呈指數級增長。比較典型的就是斐波那契數列:

用文字描述就是斐波那契數列中前兩個數字的和等於第三個數字:0,1,1,2,3,5,8,13,21......
遞迴實現程式碼如下:
int fib (int n) {
if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return fib(n-1) + fib(n-2);
}
}
計算過程中,為了計算fib(5),程式要先計算fib(4) 和 fib(3),要想計算fib(4) ,程式同樣需要先計算 fib(3) 和 fib(2)。在這個過程中計算了兩次fib(3)。
從上面分析的計算過程可以得出一個結論:使用遞迴實現斐波那契數列存在冗餘計算。
就像上面提到的,可以用遞迴的演算法一般都能用迭代實現,斐波那契數列的計算也一樣。
int fib (int n) {
int fib = 0;
int a = 1;
for(int i=0; i<n; i++) {
int temp = fib;
fib = fib + a;
a = temp;
}
return fib;
}
雖然使用遞迴的方式會有冗餘計算,可以用迭代來代替。但是這並不表明遞迴可以完全被取代。因為遞迴有更好的可讀性。
為了讓學習變得輕鬆、高效,今天給大家免費分享一套Java教學資源。幫助大家在成為Java架構師的道路上披荊斬棘。需要資料的歡迎加入學習交流群:9285,05736