http://blog.csdn.NET/wangjinyu501/article/details/8248492 原版
一、基本概念
遞歸算法是一種直接或者間接調用自身函數或者方法的算法。Java遞歸算法是基於Java語言實現的遞歸算法。遞歸算法的實質是把問題分解成規模縮小的同類問題的子問題,然後遞歸調用方法來表示問題的解。遞歸算法對解決一大類問題很有效,它可以使算法簡潔和易於理解。遞歸算法,其實說白了,就是程序的自身調用。它表現在一段程序中往往會遇到調用自身的那樣一種coding策略,這樣我們就可以利用大道至簡的思想,把一個大的複雜的問題層層轉換爲一個小的和原問題相似的問題來求解的這樣一種策略。遞歸往往能給我們帶來非常簡潔非常直觀的代碼形勢,從而使我們的編碼大大簡化,然而遞歸的思維確實很我們的常規思維相逆的,我們通常都是從上而下的思維問題, 而遞歸趨勢從下往上的進行思維。這樣我們就能看到我們會用很少的語句解決了非常大的問題,所以遞歸策略的最主要體現就是小的代碼量解決了非常複雜的問題。
遞歸算法解決問題的特點:
1)遞歸就是方法裏調用自身。
2)在使用遞增歸策略時,必須有一個明確的遞歸結束條件,稱爲遞歸出口。
3)遞歸算法解題通常顯得很簡潔,但遞歸算法解題的運行效率較低。所以一般不提倡用遞歸算法設計程序。
4)在遞歸調用的過程當中系統爲每一層的返回點、局部量等開闢了棧來存儲。遞歸次數過多容易造成棧溢出等,所以一般不提倡用遞歸算法設計程序。
在做遞歸算法的時候,一定要把握住出口,也就是做遞歸算法必須要有一個明確的遞歸結束條件。這一點是非常重要的。其實這個出口是非常好理解的,就是一個條件,當滿足了這個條件的時候我們就不再遞歸了。
二、程序示例
①斐波納契數列(Fibonacci Sequence)
問題描述:求解Fibonacci數列的第n個位置的值?(斐波納契數列(Fibonacci Sequence),又稱黃金分割數列,指的是這樣一個數列:1、1、2、3、5、8、13、21、……在數學上,斐波納契數列以如下被以遞歸的方法定義:F1=1,F2=1,Fn=F(n-1)+F(n-2)(n>2,n∈N*))。
求解代碼:
- public class Fibonacci {
- /**
- * time:2012.12.2
- * author:王金宇
- * description:用遞歸實現斐波那契數列,但是此方法是嫉妒危險的,適用於求解比較小的位置數值
- */
- public static void main(String[] args) {
- Fibonacci fibonacci=new Fibonacci();
- int result=fibonacci.fib(5);
- System.out.println(result);
- }
- public int fib(int index){
- if(index==1||index==2){
- return 1;
- }else{
- return fib(index-1)+fib(index-2);
- }
- }
- }
程序分析:這個實例是非常經典的實例,主要是利用遞歸實現了Fibonacci數列。這個遞歸算法的出口是在
- if(index==1 || index==2){
- return 1;
- }
這個代碼段上,如果程序的index符合條件就會停止進行遞歸。所以這個程序的運行流程是:
剛纔說了這個方法十幾度危險的,爲什麼這麼說,原因在於在這個遞歸裏做了冗餘的工作,如圖,我們在f4裏面已經計算了f2,可是f3裏有同樣計算了f2,以此類推那些冗餘的工作,在數值比較小的情況下,計算機還是可以接受的。但是,當求解的數值比較大,它是成指數級增長的,所以不要再遞歸中做重複的工作。
②n的階乘
問題描述:求5的階乘
求解代碼:
- public class Factorial_Five {
- /**
- * time:2012.12.2
- * author:王金宇
- * description:遞歸求n的階乘
- */
- public static void main(String[] args) {
- Factorial_Five factorial_Five=new Factorial_Five();
- int result=factorial_Five.factorial(5);
- System.out.println(result);
- }
- public int factorial(int index){
- if(index==1){
- return 1;
- }else{
- return factorial(index-1)*index;
- }
- }
- }
程序執行流程如下:
③列出某個目錄下所有的子目錄和文件
- /*
- * time:2012.12.2
- * author:王金宇
- * description:列出某個目錄下所有的子目錄和文件
- */
- public class ListDir {
- static void getDir(String strPath) throws Exception {
- try {
- File f = new File(strPath);
- if (f.isDirectory()) {
- File[] fList = f.listFiles();
- for (int j = 0; j < fList.length; j++) {
- if (fList[j].isDirectory()) {
- System.out.println(fList[j].getPath());
- getDir(fList[j].getPath()); // 在getDir函數裏面又調用了getDir函數本身
- }
- }
- for (int j = 0; j < fList.length; j++) {
- if (fList[j].isFile()) {
- System.out.println(fList[j].getPath());
- }
- }
- }
- } catch (Exception e) {
- System.out.println("Error: " + e);
- }
- }
- public static void main(String[] args) {
- String strPath = "E:";
- System.out.println(strPath);
- try {
- getDir(strPath);
- } catch (Exception e) {
- }
- }
- }
(2)向出口逼近:如果不是1,是n ,則我們先挪動上面n-1塊盤子,等上面挪完,即遞歸返回的時候,我們挪動最底下的盤子。
求解代碼:
- import javax.swing.JOptionPane;
- /*
- * time:2012.12.2
- * author:王金宇
- * description:
- */
- public class Hanoi {
- private final static String from = "盤子B";
- private final static String to = "盤子C";
- private final static String mid = "盤子A";
- public static void main(String[] args) {
- String input = JOptionPane.showInputDialog("請輸入你要移動的盤子數");
- int num = Integer.parseInt(input);
- Hanoi.move(num, from, mid, to);
- }
- private static void move(int num, String from2, String mid2, String to2) {
- if (num == 1) {
- System.out.println("移動盤子1 從" + from2 + "到" + to2);
- } else {
- move(num - 1, from2, to2, mid2);
- System.out.println("移動盤子" + num + " 從" + from2 + "到" + to2);
- move(num - 1, mid2, from2, to2);
- }
- }
- }
還有很多的遞歸的例子,我會繼續更新。
三、遞歸算法轉換成非遞歸算法
遞歸算法實際上是一種分而治之的方法,它把複雜問題分解爲簡單問題來求解。對於某些複雜問題(例如hanio塔問題),遞歸算法是一種自然且合乎邏輯的解決問題的方式,但是遞歸算法的執行效率通常比較差。因此,在求解某些問題時,常採用遞歸算法來分析問題,用非遞歸算法來求解問題;另外,有些程序設計語言不支持遞歸,這就需要把遞歸算法轉換爲非遞歸算法。將遞歸算法轉換爲非遞歸算法有兩種方法,一種是直接求值,不需要回溯;另一種是不能直接求值,需要回溯。前者使用一些變量保存中間結果,稱爲直接轉換法;後者使用棧保存中間結果,稱爲間接轉換法,下面分別討論這兩種方法。
1. 直接轉換法
直接轉換法通常用來消除尾遞歸和單向遞歸,將遞歸結構用循環結構來替代。尾遞歸是指在遞歸算法中,遞歸調用語句只有一個,而且是處在算法的最後。例如求階乘的遞歸算法:
public long fact(int n)
{
if (n==0) return 1;
else return n*fact(n-1);
}
當遞歸調用返回時,是返回到上一層遞歸調用的下一條語句,而這個返回位置正好是算法的結束處,所以
,不必利用棧來保存返回信息。對於尾遞歸形式的遞歸算法,可以利用循環結構來替代。例如求階乘的遞歸算法
可以寫成如下循環結構的非遞歸算法:
public long fact(int n)
{
int s=0;
for (int i=1; i
s=s*i; //用s保存中間結果
return s;
}
單向遞歸是指遞歸算法中雖然有多處遞歸調用語句,但各遞歸調用語句的參數之間沒有關係,並且這些遞歸
調用語句都處在遞歸算法的最後。顯然,尾遞歸是單向遞歸的特例。例如求斐波那契數列的遞歸算法如下:
public int f(int n)
{
if (n= =1 | | n= =0) return 1;
else return f(n-1)+f(n-2);
}
對於單向遞歸,可以設置一些變量保存中間結構,將遞歸結構用循環結構來替代。例如求斐波那契數列的算
法中用s1和s2保存中間的計算結果,非遞歸函數如下:
public int f(int n)
{
int i, s;
int s1=1, s2=1;
for (i=3; i {
s=s1+s2;
s2=s1; // 保存f(n-2)的值
s1=s; //保存f(n-1)的值
}
return s;
}
2. 間接轉換法
該方法使用棧保存中間結果,一般需根據遞歸函數在執行過程中棧的變化得到。其一般過程如下:
將初始狀態s0進棧
while (棧不爲空)
{
退棧,將棧頂元素賦給s;
if (s是要找的結果) 返回;
else {
尋找到s的相關狀態s1;
將s1進棧
}
}
間接轉換法在數據結構中有較多實例,如二叉樹遍歷算法的非遞歸實現、圖的深度優先遍歷算法的非遞歸實現等等,請讀者參考主教材中相關內容。