1. 程式人生 > >java資料結構與演算法之遞迴思維(讓我們更通俗地理解遞迴)

java資料結構與演算法之遞迴思維(讓我們更通俗地理解遞迴)

關聯文章:

  本篇是資料結構與演算法的第6篇,從這篇種我們將深入瞭解遞迴演算法,這可能是一篇分水嶺的博文,因為只有在理解遞迴的基礎上,我們才可能更輕鬆地學習樹的資料結構,實際上資料結構系列書籍中遞歸併沒有講得特別通俗易懂,博主目前看過的書籍中分析遞迴最好的是日本人吉城浩寫的《程式設計師的數學》,因此本篇會結合個人對遞迴的理解以及該書中的兩個博主認為比較合適例子來分析,本篇可能不會涉及太多的程式碼,相反的,更希望呈現給大家一種通俗易懂的思維方式,重在理解,畢竟理解得越多,需要記憶自然也就越少了,以下是主要知識點

漢諾塔的問題

  現在我們先不需要知道遞迴是什麼,也沒必要,我們先來看一個非常經典的遊戲—漢諾塔,該遊戲是數學家愛德華盧卡斯於1883年發明的,遊戲的規則如下,有三根細柱(A、B、C),A柱上套著6個圓盤,圓盤的大小都不一樣,它們按照從大到小的順序自下而上地擺放,現在我們需要把A柱上的圓盤全部移動到B柱上去,並且在移動時有如下約定:

  • 一次只能移動柱子最上端的一個圓盤。
  • 小圓盤上不能放大圓盤

此時約定將一個圓盤從一根柱子移動另一根柱子算移動“1”次,那麼將6個圓盤全部從A移動到B至少需要移動多少次呢?模型如下圖:

  圖雖然很清晰,但我們依然無法立即找到特別清晰的解法,既然如此,我們就嘗試先把問題的規模縮小點,把6個圓盤改為3個圓盤,先找出3層漢諾塔的解法,模型變為下圖:

  3層漢諾塔的解法就相對來說簡單多了,我們要把3個圓盤全部從A移動到B,只需要先將最小的圓盤從A移動到B,然後將次小的圓盤從A移動到C,接著再把最小的圓盤從B移動到C,然後把最大的圓盤從A移動到B,接著把最小盤從C移動到A,在把次小盤從C移動到B,最後把最小盤從A移動到B即可,這樣我們就完成了3此漢諾塔的解法了。這裡我們把3個圓盤從小到大分別設為a,b,c,那麼其移動過程如下:

/**
   元素   過程   
    a    A->B   
    b    A->C   
    a    B->C   
    c    A->B   
    a    C->A   
    b    C->B   
    a    A->B
    移動7次完結..
 **/

整個過程如下圖所示:
漢諾塔

  從上圖中,我們很容易理解3層漢諾塔的解法,但是細想一下會發現這7次動中我們好像在做重複的事情:移動圓盤,只不過方向時而不同罷了。重新回顧一下①②③④⑤⑥⑦的移動過程,然後把它們分為如下3種情況:

  • 在①②③中,移動了3次將2個圓盤從A柱移動到了C柱
  • 在④中,將最大的圓盤從A柱移動到了B柱
  • 在⑤⑥⑦中,移動了3次將2個圓盤從C柱移動到了B柱

  我們發現這個過程移動的操作是幾乎一樣的,只不過是移動的方向不同了,A->C和C->B兩種,其過程如下圖:
3層漢諾塔解法

  從圖確實可以看出雖然兩次移動的目的地不相同,但是兩次移動的操作卻是非常相似的,而且我們發現如果把3次移動看成是“移動2個圓盤”的操作就是“2層漢諾塔的解法”,也就是說在解決3層漢諾塔的過程中,我們使用了“2層漢諾塔的解法“。既然如此,那是不是意味著解決”4層漢諾塔“的過程中可以使用解決”3層漢諾塔的解法“呢?嗯,確實是如此的,這就是漢諾塔的解法規律,沒錯,我們已經發現這種規律!這樣的話,我們解決前面的6層漢諾塔的問題時,只需要先解決5層漢諾塔的問題,然後利用5層漢諾塔的解法來解決6層漢諾塔的問題即可!我們來看看利用5層漢諾塔解出6層漢諾塔的過程,如下:

  從圖中我們可以看出(a)和(c)就是5層漢諾塔的解法,為了解出6層漢諾塔需要使用到5層漢諾塔的解法,因此只要5層漢諾塔被解出,6層漢諾塔也就迎刃而解了。而5層漢諾塔的解法呢?沒錯利用我們前面發現的規律,用4層漢諾塔的解法去解出5層漢諾塔,如下過程:

  • ①.先將4個圓盤從A柱移動到C柱,即解出4層漢諾塔
  • ②.然後再將最大的圓盤(5箇中最大的圓盤)從A柱移動到B柱
  • ③.最後將4個圓盤從C柱移動到B柱,即再次利用解出的4層漢諾塔

這樣5層漢諾塔就被解出了,而4層漢諾塔則可以利用同樣的解法即使用3層漢諾塔的解法,3層漢諾塔再利用2層漢諾塔的解法……..依次類推即可,到此便已解出6層漢諾塔,實際上我們知道有了6層漢諾塔的解法自然就可以很輕鬆地解出7層漢諾塔,8層漢諾塔…….N層漢諾塔,也很容易發現這種利用已知的N-1層的解法來解決N層的問題的解題方式,它們每一層的解法結構都是相同即利用前一個已解決的問題結果來解決後一個問題。通過這種思考的方式,我們來總結一下N層漢諾塔的解法,不再使用具體的ABC三根柱子,而是將它們設為x、y、z。這樣的話,x、y、z在不同的情況下會不固定對應ABC中的某一根。這裡以x為起點柱,y為目標柱,z為中轉柱,然後給出解出N層漢諾塔的過程。利用z柱將n個圓盤從x柱轉移到y柱的解法如下:

Blog :http://blog.csdn.net/javazejian[原文地址]
/**
當 n=0時,無需任何移動
當 n>0時,
    ①將n-1個圓盤從x柱,經y柱中轉,移動到z柱(即解出n-1層漢諾塔)
    ②然後將1個圓盤從x柱移動到y柱(最大的圓盤)
    ③最後將n-1個圓盤從z柱,經x中轉移動到y柱(即解出n-1層漢諾塔) 
**/

  從上述過程可知為了解出n層漢諾塔,我們同樣需要先解出n-1層漢諾塔,為更通用地表示解出n層漢諾塔的移動次數,將其設為H(n)。利用上述步驟,則有如下關係:

  在數學上我們將這種H(n)和H(n-1)的關係式取了個名稱,叫做遞推公式,即已知H(0),由H(n-1)構成H(n)的方法也必然是已知的,只要依次計算便可以得出,如6層漢諾塔的遞推過程如下:

Blog :http://blog.csdn.net/javazejian[原文地址]
/**
    H(0)=0                     = 1-1
    H(1)=H(0)+1+H(0) = 1       = 2-1
    H(2)=H(1)+1+H(1) = 3       = 4-1
    H(3)=H(2)+1+H(2) = 7       = 8-1
    H(4)=H(3)+1+H(3) = 15      = 16-1
    H(5)=H(4)+1+H(4) = 31      = 32-1
    H(6)=H(5)+1+H(5) = 63      = 64-1
    .......                    = .........
    H(n)=H(n-1)+1+H(n-1)       = 2^n -1
**/

  這樣我們也就知道了6層次漢諾塔的最少移動次數為63次(關於2^n-1的公式只是總結出更為簡單的計算方式擺了)。到此我們來重新梳理一下漢諾塔的整個解題過程,在解出6層漢諾塔前,我們由於一時找不到解決的方法,因此先嚐試解出更為簡單3層漢諾塔的,而在這個過程中,我們慢慢發現瞭解決漢諾塔問題的通用規律,即使用n-1層的解法來解決n層漢諾塔的思考方式,通過這種思考方式最終成功地解決了6層漢諾塔的問題。而實際上我們利用的這種思考方式的本質就是將複雜的問題轉換為較為簡單的同類問題(回憶一下漢諾塔的問題解法)然後再找出解決方法最終利用簡單同類問題解出複雜問題的過程,而這種思維的方式就是遞迴!!是的,沒錯!遞迴不是演算法而是一種思考的思維方式,只不過我們將這種遞迴思維方式採用程式來解決時,該程式被稱為遞迴演算法罷了,而遞迴本身是一種思考問題的思維方式!到此我們對遞迴是否有些煥然大悟的感覺呢?或對遞迴有些許的理解了吧?

遞迴的思維方式

  有了上述的分析,我們就可以這樣去理解和使用遞迴,假設現在碰到了一個很複雜的難題,我們也明白‘簡單問題易解’的道理,那麼此時就可以利用類似於漢諾塔的解題的思考方式,即判斷能否將目前複雜的問題轉換為較為簡單的同類問題呢?可以的話,就先轉換為簡單同類的問題來解決,然後再利用簡單的同類問題解法來解決複雜的同類問題,這就恰恰就是遞迴思維方式的精髓所在,嗯,這就是遞迴!大家現在是不是已開始理解遞迴了呢?我們在回顧一下漢諾塔問題的解法,以便加深對遞迴的理解,如下圖:

  上圖很清晰表現出n層漢諾塔的解法過程,通過複雜問題化為同類簡單問題來求解,上述的圖形還有一個名稱叫做遞迴結構,根據該結構我們就可以建立起之前H(n)遞推公式了,很顯然發現遞迴結構並建立遞推公式的過程十分重要,這樣有助於我們把握本質問題即通過n-1層漢諾塔的解法來解決n層漢諾塔的問題,這樣的發現能力需要我們有比較敏銳的洞察力和思維能力,這就需要我們再遇到複雜問題時,多采用遞迴的思維(複雜問題簡單化)方式去思考,去挖掘規律。ok~,到此相信我們對遞迴已有比較清晰的瞭解了吧。接下來我們看看如何使用程式來實現遞迴演算法並解決漢諾塔的問題。

漢諾塔的遞迴演算法程式實現

  通過前面的分析,我們明白所謂的遞迴不過就是把複雜問題簡單化的思維方式,而這種思維方式從程式語言的角度出發則稱為遞迴演算法,它通過程式的函式方法直接或者間接呼叫函式自身的過程,回憶一下前面分析漢諾塔的遞推公式:H(n)=H(n-1)+1+H(n+1)

我們通過程式的遞迴演算法實現漢諾塔如下:

package com.zejian.structures.recursion;

/**
* Created by zejian on 2016/12/11.
* Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
* 漢諾塔的遞迴演算法實現
*/
public class HanoiRecursion {

 /**
  * @param n 漢諾塔的層數
  * @param x x柱 起點柱(A)
  * @param y y柱 目標柱(B)
  * @param z z柱 中轉柱(C)
  * 其中 A B C 只是作為輔助思考
  */
 public void hanoi(int n, char x ,char y ,char z){

     //H(0)=0
     if (n==0){
         //什麼也不做
     }else {
         //遞推公式:H(n)=H(n-1) + 1 + H(n-1)
         //將n-1個圓盤從x移動到z,y為中轉柱
         hanoi(n-1,x,z,y); //----------------------->解出n-1層漢諾塔:H(n-1)

         //移動最大圓盤到目的柱
         System.out.println(x+"->"+y);//------------> 1

         //將n-1個圓盤從z移動到y,x為中轉柱
         hanoi(n-1,z,y,x);//------------------------>解出n-1層漢諾塔:H(n-1)
     }

 }

 /**
  * @param n 漢諾塔的層數
  * @param x x柱 起點柱(A)
  * @param y y柱 目標柱(B)
  * @param z z柱 中轉柱(C)
  * 其中 A B C 只是作為輔助思考
  */
 public int hanoiCount(int n, char x ,char y ,char z){
     int moveCount=0;
     //H(0)=0
     if (n==0){
         //什麼也不做
         return 0;
     }else {
         //遞推公式:H(n)=H(n-1) + 1 + H(n-1)
         //將n-1個圓盤從x移動到z,y為中轉柱
         moveCount += hanoiCount(n-1,x,z,y); //------------->解出n-1層漢諾塔:H(n-1)

         //移動最大圓盤到目的柱
         moveCount += 1; //---------------------------------> 1

         //將n-1個圓盤從z移動到y,x為中轉柱
         moveCount +=hanoiCount(n-1,z,y,x);//--------------->解出n-1層漢諾塔:H(n-1)
     }

     return moveCount;
 }
 //測試
 public static void main(String[] args){
     HanoiRecursion hanoi=new HanoiRecursion();
     System.out.println("moveCount="+hanoi.hanoiCount(6,'A','B','C'));

     hanoi.hanoi(3,'A','B','C');
 }

}

從程式碼可以發現遞迴演算法的蹤影:

/**
*Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
*/
public void hanoi(int n, char x ,char y ,char z){

   //H(0)=0
   if (n==0){
       //什麼也不做
   }else {
       //呼叫自身函式hanoi()
       hanoi(n-1,x,z,y);
       //移動最大圓盤到目的柱
       System.out.println(x+"->"+y);
       //呼叫自身函式hanoi()
       hanoi(n-1,z,y,x);
   }
}

  因此到此我們也就明白了,遞迴思維在程式中的體現即為遞迴演算法,而遞迴演算法本身在程式內部的實現就是函式呼叫自身函式,這樣大家總該理解遞迴演算法了吧。這裡有點要提醒大家的是,不要陷入程式遞迴的內部去思考遞迴演算法,記住要從遞迴思維的本質(複雜問題簡單化)出發去理解遞迴演算法,千萬不要去通過試圖解析程式執行的每一個步驟來理解遞迴(解析程式的執行是指給函式一個真實值,然後自己一步步去推出結果,這樣的思考方式是錯誤的!),那樣只會讓自己得到偽理解(沒有真正理解)的結果。記住!遞歸併不是演算法,是一種複雜問題簡單化的思維方式,而這種思維方式在程式中的體現就遞迴演算法!遞迴演算法在實現上就是函式不斷呼叫自身的過程!

遞迴的定義

  通過前面大篇幅的分析,到此我們總算是理解遞迴了,那麼接下來我們給出遞迴的正式定義,相信有了上述基礎,理解遞迴的正式定義還是比較輕鬆的,遞迴其實是數學中一種重要的概念定義方式,而遞迴演算法則是針對程式設計而言的,即不同角度的兩種稱呼但本質是一樣的。
遞迴的定義(從數學的角度):用一個概念的本身直接定義自己。如階乘函式F(n)=n!可以定義為:

關於階乘這裡簡單說明一下

階乘是什麼?
1 x 2 x 3 x 4 x 5 = 5!
這裡的5!就稱為5的階乘,之所以稱為階乘是因為乘數呈階梯狀遞減而得名,如下:
5! = 5 x 4 x 3 x 2 x 1 = 120
4! = 4 x 3 x 2 x 1 = 24
3! = 3 x 2 x 1 = 6
2! = 2 x 1 = 2
1! = 1 = 1
0! = 1
注意0的階乘0!被定義為1,這是數學裡的規定。
n的階乘如下:
n!= n x (n-1) x (n-2) xx 2 x 1
很顯然n!是一種遞推公式,也符合遞迴思維,因此有:
當n=0時,n! = 1 ;
當n>=1時,n x (n-1)!
可以發現它使用了階乘(n-1)!來定義階乘n!,是不是跟漢諾塔很相似?沒錯,確實是遞迴思維的體現。
ok~,關於階乘我們就簡單瞭解這些。

  遞迴演算法的定義(從程式的角度):任何呼叫自身函式的過程都可以稱為遞迴演算法(前面實現的漢諾塔程式就是一個很好的例子)。這裡需要注意的是遞迴必須滿足以下兩個條件:

  • ①邊界條件:至少有一條初始定義是非遞迴的,如漢諾塔的H(0)=0,階乘的0!=1。
  • ②遞迴通式:由已知函式值逐步計算出未知函式值,如漢諾塔的H(0)=0,可以推算出H(1)=H(0)+1+H(0)。

邊界條件和遞推通式是遞迴定義的兩個基本要素,缺一不可,並且遞迴通式必須在有限次數內運算完成達到邊界條件以保證能夠正常結束遞迴,得到運算結果。好~,以上便是遞迴的定義,還是那句話理解好遞迴思維(複雜問題簡單化)才是重點!

斐波那契數列中的遞迴思想

  如果上述的分析都明白了,那就說明你已掌握了遞迴,但為了加深對遞迴的理解,我們再來看一個思考題(來自程式設計師的數學思考題),題目是這樣的,假如動物中有一種特殊的種類,它出生2天后就開始以每天1只的速度繁殖後代。假設第1天,有1只這樣的動物(該動物剛出生,從第3天開始繁殖後代)。那麼到第11天,共有多少隻呢?

我們先來按一般順序思考,先不要考慮第11天,先從第1天開始,看能不能找出規律:
【第1天】只有1只動物
【第2天】只有1只動物,還沒有繁殖後代,總量為1
【第3天】第1天的1只動物,繁殖1個後代,總量為2
【第4天】第1天的1只動物又繁殖1只,其他還沒繁殖,總量為3
【第5天】第1天和第3天出生的動物又繁殖1個後代,其他沒有繁殖,總量為5
【第n天】.....

 第1天 ------12天 ------13天 ------2 = 1 + 14天 ------3 = 1 + 25天 ------5 = 2 + 36天 ------8 = 3 + 57天 ------13 = 5 + 8

   這個過程中貌似沒發現什麼規律,但我們發現從第3天開始動物的數量似乎前兩天的總和,也就是第3天,是第1天的動物數量加上第2天的動物數量,而第4天則是第2天和第3天的動物數量的和。這樣的話我們可以歸納一下,不去直接想”第n天有多少隻動物“而是如下思考:

  • 第n-1天出生的動物,在第n天還存活著。
  • 第n-2天以前出生的動物,在第n天繁殖了後代

  因此可以總結出遞推公式,假設在第n天時,第n-1天以前繁殖的動物都活著,並且第n-2天以前出生的動物會繁殖1個後代,設第n天的動物總數為F(n),則有:F(n)=F(n-1)+F(n-2) 其中 n>=3,如下圖所示

注意為了讓F(2)=F(1)+F(0)成立,定義F(0)=0,而F(1)則依然為1,因此有如下公式:

我們來驗證這個遞推公式是否符合遞迴條件

  • ①邊界條件:至少有一條初始定義是非遞迴的,F(0)=0;F(1)=1。
  • ②遞迴通式:由已知函式值逐步計算出未知函式值,F(0)=0;F(1)=1,可以推算出F(2)=1,最終也可以推算F(n)的結束。

顯然兩個條件都符合,說明該通用公式可以在有限的次數內運算完成並達到邊界條件得出結果,因此我們可以利用遞推公式求出第11天的動物的數量:

F(0)=0
F(1)=1
F(2)=F(0)+F(1)=1
F(3)=F(2)+F(1)=2
F(4)=F(3)+F(2)=3
F(5)=F(4)+F(3)=5
F(6)=F(5)+F(4)=8
F(7)=F(6)+F(5)=13
F(8)=F(7)+F(6)=21
F(9)=F(8)+F(7)=34
F(10)=F(9)+F(8)=55
F(11)=F(10)+F(9)=89

也就是說第11天的動物總數為89

在這個問題中出現的數列就是著名的斐波那契數列,是由數學家斐波那契發現的,由此得名斐波那契數列。

01123581321345589 ,…

  到此我們也就知道斐波那契數列同樣是用遞迴定義的,前面我們將求解第n天的動物數量分解為求第n-1天和第n-2天以前的動物繁殖後代數量,從把複雜的問題分解為較為簡單的同類問題,而不去糾結第n天到此有多少隻動物的問題,最終發現求解的規律,並通過遞推公式求得第n天的結果,這個過程再次體現了遞迴的思維方式。既然斐波那契數列是遞迴思維的產物,那麼也可以通過程式的遞迴演算法來求解,接下來我們就看看如何使用程式中的遞迴演算法來實現斐波那契數列。

斐波那契數列的遞迴程式實現

實現程式碼比較簡單就不過多分析了,程式碼如下:

package com.zejian.structures.recursion;

/**
 * Created by zejian on 2016/12/11.
 * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
 * 斐波那契數列的實現
 */
public class Fibonacci  {

    /**
     * 斐波那契數列的實現
     * 0,1,1,2,3,5,8,13,21......
     * @param day
     */
    public long fibonacci(int day){

        if(day==0){ //F(0)=0
            return 0;
        }else if (day==1||day==2){//F(1)=1
            return 1;
        }else {
           return fibonacci(day-1)+fibonacci(day-2); //F(n)=F(n-1)+F(n-2)
        }
    }

    /**
     * 更為簡潔的寫法
     * @param day
     * @return
     */
    public long fib(int day) {
        return day== 0 ? 0 : (day== 1 || day==2 ? 1 : fib(day - 1) + fib(day - 2));
    }

    //測試
    public static void main(String[] args){
        Fibonacci fibonacci=new Fibonacci();
        System.out.println("第11天動物數量為:"+ fibonacci.fib(11));
        System.out.println("第11天動物數量為:"+ fibonacci.fibonacci(11));
    }
}

遞迴演算法的效率問題

  到此我們已對遞迴分析完了,相信大家對遞迴已很熟悉了,通過遞迴的思維方式,在解決某些問題的時候確實使得我們思考的方式得以簡化,同時代碼也更加精煉,容易閱讀。那麼既然如此,那是不是什麼問題都要用遞迴來解決呢?難道遞迴就沒有缺點嗎?下面我們就來討論一下遞迴的不足之處也就是它的效率問題。我們這裡以斐波那契數列的實現為例:

/**
 * 更為簡潔的寫法
 * @param day
 * @return
 */
public long fib(int day) {
       return day== 0 ? 0 : (day== 1 || day==2 ? 1 : fib(day - 1) + fib(day - 2));
 }

  這段程式碼相當精簡直觀清晰,但是!如果用這段程式碼計算fib(500)時,我們就淚奔了,它的執行時間也許會讓人抓狂吶。我們以fib(5)為例,計算過程如下:

  從上圖可以看出,在計算Fib(5)的過程中,Fib(1)計算了兩次、Fib(2)計算了3次,Fib(3)計算了兩次,原本只需要5次計算就可以完成的任務卻計算了9次。更重要的是這個問題隨著規模的增加會愈發明顯,以至於Fib(500)的計算時間已相當恐怖。造成這種困境的原因是,當呼叫fib(n-1)時,還要呼叫fib(n-2),也就是說fib(n-2)呼叫了兩次,同樣的道理,呼叫f(n-2)時f(n-3)也呼叫了兩次,而這些多餘的呼叫是完全沒有必要的,還可預見的是這種計算方式隨著數量的增加,計算量將呈指數級增長,這是一個相當嚴重的問題。那麼如何改良這個計算過程呢?我們重新回顧一下斐波那契數列:

01123581321345589 ,…

  為了減少函式重複呼叫提高效率,我們使用迭代的方式來實現斐波那契數列程式碼如下:

//BigInteger可以防止資料異常
//BigInteger 任意大的整數,原則上是,只要你的計算機的記憶體足夠大,可以有無限位的
// 遞推實現方式(迭代的方式效率高,時間複雜度O(n))
public  BigInteger fibonacciN(int n){
   if (n == 1) {
       return new BigInteger("0");
   }
   //f(0)=0;
   BigInteger n1 = new BigInteger("0");
   //f(1)=1;
   BigInteger n2 = new BigInteger("1");
   //記錄最終值f(n)
   BigInteger sn = new BigInteger("0");
   for (int i = 0; i < n - 1; i++) {
       sn = n1.add(n2);//相加
       n1 = n2;
       n2 = sn;
   }
   return sn;
}

  // 與上述相同的遞推實現方式 ,使用long返回值,當n過大會造成資料溢位,計算結果可能是一個未知的負數,因此建議使用BigInteger
public static long fibonacciNormal(int n){
      if(n <= 2){
          return 1;
      }
      long n1 = 1, n2 = 1, sn = 0;
      for(int i = 0; i < n - 2; i ++){
          sn = n1 + n2;
          n1 = n2;
          n2 = sn;
      }
      return sn;
  }

  這樣我們就把問題的規模降低到O(n)級別了,執行時間也很快,那為什麼使用迭代就快,而使用遞迴就會變得慢呢?我們都知道,遞迴呼叫實際上是函式自己在呼叫自己,而函式的呼叫開銷是很大的(包括空間和時間),而系統要為每次函式呼叫分配儲存空間,提供給函式進行執行。而在函式呼叫結束後,則需要釋放空間,即所謂的彈棧復點。因此函式呼叫消耗的空間和時間並不是非常樂觀的。但難度就不用遞迴了麼?並非如此,當我們在遇到同一個問題時,如果遞迴解決的(時間和空間)複雜度不明顯優於其它解決方案時,此時就不應該使用遞迴,否則可以使用遞迴。其實博主想說的是遞迴雖然有缺點,但在很多複雜的問題上我們使用遞迴的形式來解釋或者求解時問題確實很容易被解釋的更清楚,而使用迭代是無法實現的或者難以理解的(如漢諾塔問題,樹的遍歷等等),此時遞迴巨大的優勢就顯示出來了。同時我們更應該記住在相同的問題面前,如果使用遞迴的效果與迭代的效果相差不了多少,我們更應該傾向於使用迭代,畢竟執行效率上迭代還是相當有優勢的。
  ok~,關於遞迴我們就聊到這裡吧,相信已經很清晰了,記住重在理解,切勿陷入遞迴程式內部去思考!
github原始碼下載(含文章列表)