算法基礎四:動態規划---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、計算順序
- 由於我們是從左到右的,一直到右下角,在計算當前的時候,會參考二維數組的左面,左斜上方的格子里 的值,自然我們的計算順序是從左到右,從上到下。