遞歸函數(Java)

  在之前講解函數內容時提到過函數自身調用自身叫做“遞歸”。那麼爲什麼要用到遞歸?,下面我們對遞歸的內容和在程序中的使用進行介紹,來說明爲什麼會用到遞歸以及何時進行遞歸。

遞歸的定義及優點

  遞歸的定義:遞歸做爲一種算法在程序設計語言中廣泛應用。 一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法,它通常把一個大型複雜的問題層層轉化爲一個與原問題相似的規模較小的問題來求解,遞歸策略只需少量的程序就可描述出解題過程所需要的多次重複計算,大大地減少了程序的代碼量。

我們先來看一個簡單的問題,之前在講解循環的內容時,其中有道求解前100項和問題,我們用了一個for循環去解決,現在我們怎麼用遞歸解決呢?我們知道,前一百項是從1開始即1+2+3+...+99+100,那麼我們不妨這樣想,前100項和是前99項和加上100得出的結果,而前99項和是前98項和加上99得出的結果,以此下去知道前1項和是從1開始的,那麼這就是遞歸的終止條件,假如前n項和爲F(n),那麼F(100)=F(99)+100,而F(99)=F(98)+99....F(2)=F(1)+2,F(1)=1。不理解文字描述可參考下面的圖:

                                     

因此用遞歸函數實現該功能的代碼爲:

class Demo01{
    public static void main(String[] args){
        int sum=f(100);         //調用求和函數
        System.out.println(sum);
        }

    //1+2+3+4+....+100
    public static int f(int n){
        if(n==1){  //如果求前1項和,則直接返回1
            return 1;
        }
        return f(n-1)+n; //如果求和項大於1,就不斷調用自身直到爲1;
    }

可能到這你還會覺得這和我們之前寫的for循環的代碼量差別不大,別急,我們再來看一個經典問題——斐波那契函數

     斐波那契數列(Fibonacci sequence),又稱黃金分割數列、因數學家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖爲例子而引入,故又稱爲“兔子數列”,指的是這樣一個數列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368........這個數列從第3項開始,每一項都等於前兩項之和。如果按照for循環來寫則是這樣的:

class Demon02{
  public static void main(String[] args){
       fibo(30);
    }
     
  public static void fibo(int n){
        int a=1;   //前兩項爲1
        int b=1;
        int c=0;   //存放第n項值
        for(int i=1;i<=n;i++){  
            if(i==1||i==2){  //如果i爲1或者2 ,則值爲1
                System.out.print(1+" ");
            }else{    //否則從第三項開始的第i項
                c=a+b;  //等於前兩個值的和
                System.out.print(c+" ");  //記錄每項的值
                a=b;   //將第i-1賦給a
                b=c;   //第i項賦給b
            }
        }
    }
}

  如果用遞歸實現:

                                                      

class Demon03{
   public static  void  main(String[] args){
        fibo_dg(30);
   }

 public static int fibo_dg(int n){
        if(n==1||n==2){  //如果是第1項或是第2項
            return 1;  //直接將結果返回1
        }
        return fibo_dg(n-1)+fibo_dg(n-2); //如果n>=3,就調用n-1項,直到n-1爲2,將後兩項相加得出結果返回
    }
}

對比 Demon02 和 Demon03 就會發現,用遞歸解決問題的代碼會比用for循環解決的代碼簡單的多,只是代碼看起來沒有那麼很好的理解,所以在用遞歸解決問題時,一定要將問題的邏輯和自己的思路理清。

遞歸的缺點

雖然遞歸是簡化了代碼,看起來也很簡潔,但是“人無完人”,遞歸也存在弊端。我們之前在講函數時,提到過函數的內存調用,而遞歸的前提就是先分裝成一個函數,然後調用自身,因爲主函數是無法自己調用自己的,那麼就說明了一個問題,就是函數的運行空間——“棧內存”是無限大的嗎?顯然這不是的,我們先來看一個栗子:

class Demon04{
   public static void main(String[] args){
       show();
  }
   public static void show(){
        System.out.println("show...");
        show();
    }
}

這裏void會不斷地調用自身,並且沒有給它一個結束的條件,我們來看一下運行的結果

                         

起初可能會不斷的打印show()...,但是當達到極限的時候,就會報錯,出現一個叫StackOverflowError的錯誤,這個錯誤叫棧溢出錯誤,原因是,程序在不斷的加載新的show()函數,將棧區間佔用滿了,所以不能再加載新的函數進棧,是因爲之前進棧的所有函數都沒有處理完,還佔用着棧內空間。所以在計算遞歸的層數越大,程序運行的越慢,最後可能因爲加載不下而導致棧溢出錯誤。

漢諾塔問題

 

雖然遞歸存在弊端,但是我們也不能忽略它的好處。在漢諾塔問題中,詮釋了遞歸的實用性。我們先來看一下問題的需求:有三根相鄰的柱子,標號爲A,B,C,A柱子上從下到上按金字塔狀疊放着n個不同大小的圓盤,要把所有盤子一個一個移動到柱子C上,並且在每次移動中同一根柱子上都不能出現大盤子在小盤子上方這種情況,要求給出移動的步驟。

如果是人爲自己思考的話,第一步先怎麼移以及第二步再怎麼移都是知道,如果要將其轉化爲程序語言,我們就要判斷不同情況的不同處理辦法。如果盤子數量少的話,我們可以用判斷加循環將其解決,那隨着盤子的數量增多呢,就要寫一大堆的判斷這是相當麻煩的,而且程序的可讀性也會降低,所以我們要找到通用的辦法去解決問題。

首先我們肯定是把上面n-1個盤子移動到柱子B上,然後把最大的一塊放在C上,然後把B上的n-2個盤子移動到A上,把B上最大的那個盤子再放到C上,這樣依次分解。假如我們以5個盤子爲例,

               

                                 

  這是從後往前考慮,想要將最大的盤子放在下面,就要將上面小的盤子都挪走,那麼上面小的盤子又有一個最大的盤子,又再次考慮將第二大的挪走依次類推,直到遞歸到最小的那個盤子,因此我們的程序爲:

import java.util.Scanner;
class Hano{
    public static void main(String[] args){
        Scanner scanner =new Scanner(System.in);
        System.out.print("請輸入盤子的個數:");  //首先接受要處理的盤子個數
        int level=scanner.nextInt();
        move("x","y","z",level);  //x,y,z表示三個柱子,level表示盤子的數量
    }
    public static void move(String from,String mid,String to,int level){  //規定盤子要從X到Y
        if(level==1){ //如果盤子數爲1,將盤子從X移到Y
            System.out.println(from+"->"+to);
        }else{ //否則遞歸調用,每次移動藉助的柱子編號是不一樣的
            move(from,to,mid,level-1); //移動前n-1項
            System.out.println(from+"->"+to);  //每移動一次就打印步驟
            move(mid,from,to,level-1); 
        }
    }
}

此問題比較難,建議將每一步都自己操作一番進行理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章