遞歸,就是這麼簡單

圖片描述

什麼是遞歸?

維基百科給出瞭如下定義:

程序調用自身的編程技巧稱爲遞歸.遞歸作爲一種算法在程序設計語言中廣泛應用。

上面的說法略顯官方。簡而言之,遞歸就是自己調用自己,但是這個調用它是有一定條件的,比如:

  • 子問題須與原始問題爲同樣的事,且更爲簡單。
  • 調用自身的次數不能太多,否則會造成程序堆棧溢出。
  • 必須設置遞歸邊界,也就是遞歸的結束條件,否則遞歸會無限循環直到程序堆棧溢出。

遞歸與循環的區別

  • 遞歸

優點:代碼簡潔、清晰(需要你理解算法,否則會更暈)
缺點:調用次數控制不好,容易造成堆棧溢出,此外,它的每次傳遞參數都是相當於在壓棧,每次返回結果都相當於出棧,這個過程是非常影響執行效率的。

  • 循環

優點:邏輯簡單,速度快
缺點:不能解決所有的問題,有些問題必須用遞歸實現。比如,著名的漢若塔問題,如果有誰可以用其他方式寫出來我服。

遞歸的使用場景

關於使用場景,我總結了一句話:調用次數較少且用循環實現極爲噁心的時候,可以嘗試使用遞歸。

關於遞歸的幾個實例

  1. 計算 int 形數組的總和
public class Main {

    private static int sum(int[] arr, int z) {

        if (z == arr.length) {
            return 0;
        }

        int x = sum(arr, z + 1);

        int res = arr[z] + x;

        return res;
    }


    public static void main(String[] args) {
        int arr[] = {1, 2};
        sum(arr, 0);
    }
}

這個示例最簡單,當然這裏是爲了方便說明問題,實際上用循環實現纔是最好的。目的就是計算 arr 數組各元素的總和,看輸入參數,大家可以猜到返回結果是 3 。下面我說下看這類程序的小技巧。

首先,我們要找到程序的遞歸邊界,也就是遞歸結束的條件(這樣說也不準確,看具體的代碼實現,有時遞歸邊界確實是遞歸結束的條件,返回最終結果,但有時又是遞歸最後一層返回結果的條件,比如以下程序)。

  • 當 z = 2 時,這層遞歸返回 0 ,也就是 x = 0、返回到 z = 1 的這層遞歸。
  • 此時,res = arr[1] + 0 ,返回 res = 2 也就是 x = 2 給到 z = 0 的這層遞歸。
  • 這時,res = arr[0] + 2 = 3 至此,遞歸結束,返回結果給調用方。

沒看懂的,請複製代碼 debug 一步一步的運行。一開始看反正我是被繞暈的。

  1. 計算 1 到 100 的總和
public class Main {

    static int i = 1;

    public static void show(int sum) {
        sum = sum + i; //業務代碼1
        //遞歸頭
        if (i == 10) {
            System.out.println(sum);
            return;
        }
        i++;   //業務代碼2
        show(sum); //遞歸體
    }

    public static void main(String[] args) {
        int sum = 0;
        show(sum);
    }
}

以上寫法的遞歸邊界,就屬於我上面說的,它就是遞歸結束的條件。它的返回結果就是遞歸的最終結果,而不是上一層的結果。

  1. 斐波那契數列
public class Main {
 
    public static int f(int n) throws Exception {
        if(n==0){
           throw new Exception("參數錯誤!");
        }
        if (n == 1 || n == 2) {
            return 1;
        } else {
            return f(n-1)+f(n-2); //自己調用自己
        }
 }
 
 
    public static void main(String[] args) throws Exception {
        for (int i = 1; i <=10; i++) {
            System.out.print(f(i)+" ");
        }
    }  
}
  1. 計算文件夾大小

由於 File 類下length() (返回值類型爲 long 型) 方法只能統計文件的大小,沒有方法直接統計文件夾的大小,需要使用遞歸的方法遍歷到所有的文件,並累加,最終計算出文件夾大小。

public class Main {

    public static void main(String[] args) {
        File dir = getDir();
        System.out.println(getFileLength(dir));
        System.out.println("統計完成!");
    }
    
    public static File getDir() {
        //1,創建鍵盤錄入對象
        Scanner sc = new Scanner(System.in);
        System.out.println("請輸入一個文件夾路徑:");
        //2,定義一個無限循環
        while(true) {
            //3,將鍵盤錄入的結果存儲並封裝成File對象
            String line = sc.nextLine();
            File dir = new File(line);
            //4,對File對象判斷
            if(!dir.exists()) {
                System.out.println("您錄入的文件夾路徑不存在,請重新錄入:");
            }else if(dir.isFile()) {
                System.out.println("您錄入的是文件路徑,請重新錄入:");
            }else {
                //5,將文件夾路徑對象返回
                return dir;
            }
        }        
    }

    public static long getFileLength(File dir) {
        //1,定義一個求和變量
        long len = 0;
        //2,獲取該文件夾下所有的文件和文件夾listFiles();
        File[] subFiles = dir.listFiles();
        //3,遍歷數組
        if(subFiles != null) {
            for (File subFile : subFiles) {
                //4,判斷是文件就計算大小並累加
                if(subFile.isFile()) {
                    len = len + subFile.length();
                    //5,判斷是文件夾,遞歸調用
                }else {
                    len = len + getFileLength(subFile);
                }
            }
        }
        return len;
    }
}

總結:這篇主要是介紹下遞歸的定義、與循環的區別以及它的使用場景,最後提供了幾個代碼示例給大家研究,看不懂的請複製代碼,debug 一步一步運行理解。

參考鏈接:https://www.jianshu.com/p/edfc4e35f383

推薦閱讀:

1、java | 什麼是動態代理

2、SpringBoot | 啓動原理

3、SpringBoot | 自動配置原理

一個優秀的廢人

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