1. 程式人生 > 其它 >演算法基礎四:動態規劃---0-1揹包問題

演算法基礎四:動態規劃---0-1揹包問題

演算法基礎四:動態規劃---0-1揹包問題

一、演算法描述與分析

1、問題的理解與描述

問題理解

問題描述

2、解題思路

①思路

②狀態轉移方程

f(k,w):當揹包容量為w,現有k件物品可以偷所能偷到的最大價值。

③表格(圖示)

解釋:

  • 第一行和第一列為0,因為當揹包容量為0的時候,不論還有幾件物品可以偷,那麼價值都是0,偷不到。如果剩下0件物品可以偷,那麼價值也是0。相當於初始化。
  • 比如f(1,2),意思就是揹包容量還剩下2,現在還有一件物品可以偷,這個物品的重量為2,價值為3。或者帶入轉移方程也可以看出來,偷與不偷的情況。

二、演算法的虛擬碼描述

1.演算法虛擬碼

KNAPSACK(v, w, C)
	n <- length[v]
	for j <- 0 to C
		do f[0,j] <-0 #j控制容量
	for i<-1 to n
		do f[i,0] <-0
	for j<-1 to C
		do f[i,j] <- f[i-1,j]
			if wi <= j
				then if vi + f[i-1,j-wi] > f[i-1,j
					then f[i,j] <- vi + f[i-1,j-wi
	return  f

2、構造一個最優解

利用KNAPSACK返回的矩陣m,可以構造出如下最優解:

BUILD-SOLUTION(f,w,C)
	n <- length[w]
	j <- C
	for i<- n to 1
		do if f[i,j] = f[i-1,j]
				then x[i] <- 0
			else 
				x[i] <- 1
				j <- j-w[i]#如果拿了的話,容量減少
	return x

三、程式碼實現

1、演算法程式碼

public class Knapsack {
    public static int[][] knapsack(int[] w,int[] v,int c){
        int i,j,n = w.length;
        int [][] f = new int[n+1][c+1];
        for (i=1;i<n+1;i++)
            f[i][0] = 0;
        for (j=0;j<c+1;j++)
            f[0][j] = 0;
        for (i=1;i<=n;i++)
            for (j=1;j<=c;j++){
                f[i][j] = f[i-1][j];//預設其太重放不下。或者是不拿當前這個物品的情況。
                if (w[i-1]<=j)//此種情況就是如果當前物品能放下
                    if (v[i-1]+f[i-1][j-w[i-1]]>f[i-1][j])//並且拿了這個物品的總價值要大於不拿這個物品
                        f[i][j] = v[i-1]+f[i-1][j-w[i-1]];
            }
        return f;
    }

    public static int[] buildSolution(int[][] f,int[] w,int c){
        int i,j=c,n=w.length;
        int[] x = new int[n];
        for (i=n;i>=1;i--)
            if (f[i][j] == f[i-1][j])//說明當前這個物品,可能是沒拿,也有可能是放不下。
                x[i-1] = 0;//那麼就說明這個物品沒拿
            else {//否則就說拿了,需要減去這個重量,重新的去尋找下一個拿了的物品。
                x[i-1] = 1;
                j -= w[i-1];
            }

        return x;
    }

}

2、測試程式碼

public class Test {
    public static void main(String[] args) {
        int w[] = {2,3,4,5},v[]={3,4,5,7};
        int[][] f;
        int[] x;
        f = Knapsack.knapsack(w,v,9);
        x = Knapsack.buildSolution(f,w,9);
        for (int i = 0; i < 4; i++) {
            System.out.print(x[i]+" ");
        }
        System.out.println();
    }
}

四、思考求解動態規劃

1、組成部分一:確定狀態

①最後一步:

​ 在本題中,最後一步,也就是從最後一個物品出發,最後一個物品,拿還是不拿。不拿的價值是多少,拿了的價值是多少。

②子問題:

​ 前一個物品拿完之後,當前這個物品拿不拿,拿了之後價值和沒拿之後的價值。

2、組成部分二:轉移方程

​ 列出轉移方程

3、初始條件和邊界情況

  • 陣列要開多大?
  • 如果重量為0,值該怎麼辦。
  • 如果沒有物品可以拿,值該怎麼辦。
  • 迴圈要多少次

4、計算順序

  • 由於我們是從左到右的,一直到右下角,在計算當前的時候,會參考二維陣列的左面,左斜上方的格子裡 的值,自然我們的計算順序是從左到右,從上到下。