1. 程式人生 > >普通遞迴的改進(記憶性的遞迴)

普通遞迴的改進(記憶性的遞迴)

1. 我們發現假如有時候使用遞迴或者深搜的時候資料量比較大的時候計算速度非常緩慢,而且可以發現最重要的是遞迴的時候存在重複子問題的重複求解了,比如像斐波拉契數列的求解f(n)  = f(n - 1) + f(n - 2)就存在著重複子問題的重複求解f(n - 1)繼續遞迴下去的時候那麼又會重複計算f(n - 2),這樣假如資料量大的話耗時非常大,而且我們可以發現存在著像斐波拉契這樣一類具有這樣通項公式的遞迴形式問題的求解往往存在著很多重複子問題的重複求解,假如能夠把計算過的子問題的值儲存起來那麼計算之前查詢一下如果發現計算過那麼直接返回了,不用搜索下去了

這樣便會大大降低時間的複雜度,這個有點類似了深度優先搜尋的剪枝,但是又有點同,dfs通常使用if條件來進行提前的剪枝,不滿足條件的搜尋那麼接下來的支路都會剪斷,而這裡是遞迴之前進行查詢,遞迴之後進行儲存那麼就可以達到解決我們重複子問題重複求解的問題了

像斐波拉契數列求解的過程:

最後的f(2)遞迴呼叫f(1)和f(0)那麼當遞迴呼叫退回來之後記錄的是便是f(2)的值,再退回來記錄的是f(3)的值,f(2)的值,f(4)的值...

記憶型的遞迴特別是針對重複子問題的重複求解的問題,當我們發現問題可以使用遞迴來求解,而且存在重複的子問題那麼可以使用記憶型的遞迴來解決,這樣便可以大大減少搜尋的時間,像上面的f(5)的求解左邊的遞迴完成之後都不需要進行右邊的遞迴直接返回就可以了

斐波拉契數列記憶型的求解程式碼如下:

import java.util.Scanner;
public class Main{
    //使用一維陣列來進行記錄
    static int rec[];
    static int v1 = 0;
    static int v2 = 0;
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        rec = new int[n];
        //要先進行初始化
        rec[0] = 1;
        rec[1] = 1;
        int res = f(n - 1);
        System.out.println(res);
        for(int i = 0; i < n; i++){
            System.out.print(rec[i] + " ");
        }
        sc.close();
    }
    
    private static int f(int n){
        if(n == 0 || n == 1) return 1; 
        if(rec[n] > 0){
            //通過輸出語句可以看到的確在求解重複子問題的時候查詢之後發現之前求解過那麼就會直接返回了
            System.out.println(rec[n]);
            return rec[n];
        }else{
            v1 += f(n - 1);
            //左邊已經把f(n - 1)計算出來了所以v2直接加上f(n - 2)就是當前呼叫f(n)結束之後f(n)的值了
            v2 = v1 + f(n - 2);
            //呼叫完之後那麼進行f(n)的值的記錄
            rec[n] = v2;
            return v2;
        }
    }
}