Java學習:遞歸


遞歸的思想

以此類推是遞歸的基本思想。

具體來講就是把規模大的問題轉化為規模小的相似的子問題來解決。在函數實現時,因為解決大問題的方法和解決小問題的方法往往是同一個方法,所以就產生了函數調用它自身的情況。另外這個解決問題的函數必須有明顯的結束條件,這樣就不會產生無限遞歸的情況了。

遞歸的兩個條件

  1. 可以通過遞歸調用來縮小問題規模,且新問題與原問題有着相同的形式。(自身調用)
  2. 存在一種簡單情境,可以使遞歸在簡單情境下退出。(遞歸出口)

遞歸三要素:

  1. 一定有一種可以退出程序的情況;
  2. 總是在嘗試將一個問題化簡到更小的規模
  3. 父問題與子問題不能有重疊的部分

遞歸:自已(方法)調用自已

例子:用遞歸把目錄下所有的目錄及文件全部顯示出來

 

public class B {
    public static void main(String[] args) {
        File file = new File("f:\\123");
        listAllFile(file);
    }

    public static void listAllFile(File file) {
        File[] strs = file.listFiles();
        for (int i = 0; i < strs.length; i++) {
            // 判斷strs[i]是不是目錄
            if (strs[i].isDirectory()) {
                listAllFile(strs[i]);//遞歸調用自己
                System.out.println("目錄="+strs[i].getName());
            } else {
                System.out.println("文件名="+strs[i].getName());
            }
        }    
    }
}

遞歸算法的一般形式:

func( mode){
    if(endCondition){      //遞歸出口
          end;
    }else{
         func(mode_small)  //調用本身,遞歸
    }
}

例子

求一個數的階乘是練習簡單而典型的例子,階乘的遞推公式為:factorial(n)=n*factorial(n-1),其中n為非負整數,且0!=1,1!=1

我們根據遞推公式可以輕松的寫出其遞歸函數:

public static long factorial(int n) throws Exception {
    if (n < 0)
        throw new Exception("參數不能為負!");
    else if (n == 1 || n == 0)
        return 1;
    else
        return n * factorial(n - 1);
}

遞歸的過程

在求解6的階乘時,遞歸過程如下所示。

clip_image001

我們會驚奇的發現這個過程和棧的工作原理一致對,遞歸調用就是通過棧這種數據結構完成的。整個過程實際上就是一個棧的入棧和出棧問題。然而我們並不需要關心這個棧的實現,這個過程是由系統來完成的。

那么遞歸中的“遞”就是入棧,遞進;“歸”就是出棧,回歸。

我們可以通過一個更簡單的程序來模擬遞進和回歸的過程:

 

/*
 關於 遞歸中 遞進和回歸的理解*/
public static void recursion_display(int n) {
    int temp=n;//保證前后打印的值一樣
     System.out.println("遞進:" + temp);
    if (n > 0) {
        recursion_display(--n);
    }
    System.out.println("回歸:" + temp);
}

遞歸的例子

斐波那契數列

斐波那契數列的遞推公式:Fib(n)=Fib(n-1)+Fib(n-2),指的是如下所示的數列:

1、1、2、3、5、8、13、21.....

按照其遞推公式寫出的遞歸函數如下:

public static int fib(int n) throws Exception {
    if (n < 0)
        throw new Exception("參數不能為負!");
    else if (n == 0 || n == 1)
        return n;
    else
        return fib(n - 1) + fib(n - 2);
}

 遞歸調用的過程像樹一樣,通過觀察會發現有很多重復的調用。

 

image

歸並排序

歸並排序也是遞歸的典型應用,其思想:將序列分為若干有序序列(開始為單個記錄),兩個相鄰有序的序列合並成一個有序的序列,以此類推,直到整個序列有序。

//遞歸過程是:在遞進的過程中拆分數組,在回歸的過程合並數組
public static void mergeSort(int[] source, int[] temp, int first, int last) {
    if (first < last) {
        int mid = (first + last) / 2;
        mergeSort(source, temp, first, mid);    //歸並排序前半個子序列
        mergeSort(source, temp, mid + 1, last); //歸並排序后半個子序列
        merge(source, temp, first, mid, last);    //在回歸過程中合並
    } else if (first == last) {                    //待排序列只有一個,遞歸結束
        temp[first] = source[first];
    }
}

同樣調用過程向樹一樣,但是它並沒有重復調用的問題。在遞進的過程中拆分數組,在回歸的過程合並數組 。通過遞歸來實現歸並排序,程序結構和條理非常清晰。

clip_image002

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM