1. 程式人生 > >01揹包問題_記憶型的遞迴

01揹包問題_記憶型的遞迴

1. 問題描述:

有n個重量和價值分別為wi,vi的物品,從這些物品中挑選出總重量不超過W的物品,求所有挑選方案中價值總和的最大值。
1≤n≤100

1≤wi,vi≤100

1≤W≤10000

輸入:
n=4
(w,v)={(2,3),(1,2),(3,4),(2,2)}
W=5

輸出:
7(選擇第0,1,3號物品)

(因為對每個物品只有選和不選兩種情況,所以這個問題稱為01揹包)

 2. 我們發現使用普通的遞迴或者深搜來解決的時候會發現如果資料量大的話那麼耗時是比較大的,其中有可能在遞迴的過程中有些子問題是重複求解的,例如在遞迴的時候當前求解出f( n - 2 ),然後在後面遞迴的時候也求解f(n - 2)那麼這個就是重複子問題的重複求解了

,假如我們能夠在遞迴的時候求解一次子問題之後然後再次遇到重複的子問題的時候就不用再求解下去了,這個時候就要使用到我們的記憶性的遞迴了,把中間結果記錄下來,在遞迴之前看一下這個子問題是不是之前求解過,假如之前求解過那麼後面的時候直接返回結果就好了,這就是:計算之前先查詢,計算之後做儲存的操作

這道題目由於涉及到可以選擇當前物品的下標,可以選擇的剩下物品的質量這兩個變數,所以我們可以使用二維陣列這個資料結果來儲存中間的結果,這樣就不會重複的子問題重複求解了

只需要把簡單的遞迴的程式碼改動一下就可以了,增加的程式碼只是儲存中間結果的問題,所以這種遞迴也叫記憶性的遞迴(記憶性的深搜)

3. 具體的程式碼如下:

import java.util.Arrays;
import java.util.Scanner;
import static java.lang.Math.max;
public class Main{
    static int rec[][];
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int w[] = new int[n];
        int v[] = new int[n];
        for(int i = 0; i <n; i++){
            w[i] = sc.nextInt();
        }
        for(int i = 0; i <n; i++){
            v[i] = sc.nextInt();
        }
        int W = sc.nextInt();
        rec = new int[n][W + 1];
        for(int i = 0; i < n; i++){
            Arrays.fill(rec[i], -1);
        }
        int res = dfs(w, v, 0, n, W);
        System.out.println(res);
        sc.close();
    }

    private static int dfs(int[] w, int[] v, int cur, int n, int W){
        if(cur == n) return 0;
        if(rec[cur][W] > 0){
            return rec[cur][W];
        }
        int v1 = dfs(w, v, cur + 1, n, W);
        int ans;
        //進行剪枝假如不能夠拿取的時候把下面的支路都給剪段了,因為有的節點是左節點不拿取之後假如拿取右節點後超出質量的限制
        //所以這個時候使用if-else語句來進行比較大小進行返回,不能夠拿取的時候直接返回v1
        //因為呼叫右節點的時候說明左節點已經呼叫完了
        if(W >= w[cur]){
            int v2 = v[cur] + dfs(w, v, cur + 1, n, W - w[cur]);
            ans =  max(v1, v2);
        }else{
            ans = v1;
        }
            rec[cur][W] = ans;
            return ans;
    }
}