遞歸算法遞歸算法是算法中最基礎,入門級別的算法,簡單理解:不停直接或間接調用自身函數,每次調用會改變一個或者多個變量,直到變量到達邊界,結束調用。html
借用知乎上Memoria的回答:算法
假設你在一個電影院,你想知道本身坐在哪一排,可是前面人不少,你懶得去數了,因而你問前一排的人「你坐在哪一排?」,這樣前面的人 (代號 A) 回答你之後,你就知道本身在哪一排了——只要把 A 的答案加一,就是本身所在的排了。不料 A 比你還懶,他也不想數,因而他也問他前面的人 B「你坐在哪一排?」,這樣 A 能夠用和你如出一轍的步驟知道本身所在的排。而後 B 也如法炮製。直到他們這一串人問到了最前面的一排,第一排的人告訴問問題的人「我在第一排」。最後你們就都知道本身在哪一排了。編程
正規的說法:遞歸算法就是經過不斷調用自身將原問題分解爲跟原問題相同解決方法的子問題,最後將各子問題的解合併獲得原問題的解。遞歸的核心思想是分治策略,也就是將一個規模大的問題分解成一些規模小的同類問題,而後經過這些小問題求得大問題的解。函數
遞歸算法和分治法的區別:遞歸是算法的實現方式,分治是算法的設計思想。 這跟你吃飯能夠用筷子吃,也用手吃同樣。也就是我使用的是分治法的思想,但不必定使用遞歸算法來實現該實現。優化
遞歸算法和循環的區別:遞歸算法是將問題規模縮小,最終獲得問題的解;而循環是一種由遠變到近的過程,問題的規模不見得縮小了,可是慢慢在調整接近答案。spa
好比上面的電影院例子:.net
遞歸算法的時間複雜度是O(n^2),因此也是比較暴力破解算法。設計
優勢:只須要幾條代碼就能夠解決問題。code
缺點:htm
設計步驟:
1.找出終止條件:當n=1時;當n=2時 2.終止後要返回什麼結果:當n=1時,F1=1;當n=2時,F2=1 3.遞歸部分:Fn=F(n-1)+F(n-2)(n>2,n∈N*)),也就是一個數會等於前兩個數相加的結果。
代碼:
public class Fibonacci { public static int fib(int n){ // 1. 終止條件 if(n == 1 || n == 2){ // 2.終止完要返回什麼 return 1; } // 3.遞歸部分 return fib(n-1) + fib(n-2); } public static void main(String[] args) { System.out.println(fib(10)); } }
能夠試試你的n很大的話(n=100便可感覺到),這段代碼會運行半天還沒算出結果,這就是遞歸算法的缺點。用動態規劃也能夠作並且更好更快計算,不過如今這裏是學遞歸算法。後面到動態規劃會拿出來優化。
過程圖:
如圖能夠看到,像F(4)和F(3)重複計算了幾回,也就致使要多些運算時間和內存空間。
階乘的公式直接推:n!=n*(n-1)*(n-2)…3*2*1
終止條件就是當n=1時返回1。
核心代碼:
public static int factorial(int n){ if(n == 1){ return 1; } return n*factorial(n-1); }
過程圖:
解決:
主要是利用漢諾塔理解下遞歸的思想。
來分析它的步驟:(->表示移動)
如今假設只有一個盤片,那麼直接就是:A->C
假設如今有兩個盤片,那麼就須要三步:
對於此三步,咱們再解析一下:
當有三個盤片時,咱們把三個盤片從上到下編號1,2,3。想要讓A柱編號3的盤片移動到C柱,必須先移開A柱前兩個盤片,而後才能夠把A柱編號3的盤片移動到C柱。咱們能夠直接把前兩個當成一個總體直接移動到B柱,不在乎怎麼移的,而後把編號3的盤片移動到C柱。如圖:
經過這張圖可得,目前咱們已經解決了移動編號3的盤片的問題,那麼剩下的問題就是解決在B柱上的兩個盤片該如何操做?這不就是轉變成只有兩個盤片時該如何移動的問題了嗎?雖然如今是在B柱上,那麼這跟剛剛有兩個盤片的操做同樣。
後面四個盤片五個盤片…n個盤片都是同樣的遞歸思想,至此對於漢諾塔的遞歸思想應該是有頭緒了。
總結:
對於有n個盤片的漢諾塔,咱們把n-1個盤片當成一個總體移動到B柱(不用去管細節,怎麼移動的,反正移到最後一步確定是這樣的結果,可是得知道,確定是藉助C柱來把這些盤片從A柱移動到B柱,也就是會將其中的柱做爲輔助柱來輔助移動,這句等下能夠理解代碼),而後把第n個盤片移動到C柱,最後繼續解決n-1個盤片如何移動的問題。一直到n=1時,直接A->C。
由於解決F(N)以前解決F(N-1),而解決F(N-1)以前解決F(N-2),直到n=1,因此遞歸時首先打印的就是n=1時的移動,而後一直一直回退打印,直到最後打印F(N)。跟前兩道的例題運行流程圖同樣。
代碼實現:
public class HanoiTower { /** * * @param n 盤片數 * @param a 柱子A * @param b 柱子B * @param c 柱子C * @return 移動步驟 */ public static void hanoi(int n, char a, char b, char c){ // 終止條件 if(n == 1){ // 剩最後一個盤片時直接從A移動到C System.out.println(a + "->" + c); } else { // 第一步,把n-1當成總體,從A移動到B,以C柱做爲輔助柱來輔助移動,但咱們只須要移動後的結果。 hanoi(n-1, a, c, b); // 第二步,剩最後一個盤片時直接從A移動到C System.out.println(a + "->" + c); // 第三步,繼續解決n-1的問題,此時的問題變成在B柱移動到C柱如何操做 // 從B移動到C,以A柱做爲輔助柱 hanoi(n-1, b, a, c); } } public static void main(String[] args) { hanoi(3,'A','B','C'); } }
當n=3時,它的運行結果是:
A->C A->B C->B A->C B->A B->C A->C
會疑惑,第一步是A->C???而不是A->B???
正如前面所說,遞歸是解決F(N)以前解決F(N-1),而解決F(N-1)以前解決F(N-2),而且咱們是將前兩片當成一個總體的思路移動到B柱,其實是:編號1的盤片先從A移動到C,而後編號2的盤片從A移動到B,最後編號1的盤片從C移動到B。
我講得好囉嗦,不過應該完全明白了。
因此在設計遞歸算法時是不須要去在乎細節,咱們把F(N-1)看成一個總體,去解決F(N)。