揹包問題描述:
假設有一個固定容量的揹包,然後有許多具有價值屬性和重量屬性的物品,要求在不超過揹包最大容量的基礎上裝的物品的總價值最大。
假設共有N個物品,揹包的容量爲M,物品 i 的價值與重量分別爲 value[i] 和 weight[i]。dp[i][j] 爲可選物品爲一到i,揹包空間爲j時的最大價值。
0/1揹包
0/1揹包爲最基礎的揹包問題,顧名思義,所有的物品只有一件,只有拿或不拿兩種選擇,dp[i][j]的表達式如下:
最大價值爲選i物品和不選i物品的最大值。
實現代碼如下:
/**
* N : 物品個數
* M : 揹包容量
*/
int N = 4;
int M = 15;
int[] weight = {0, 2, 4, 5, 9};
int[] value = {0, 3, 5, 8, 10};
public int zeroOneBackpack() {
int[][] dp = new int[N + 1][M + 1];
for(int i = 1; i <= N; i++) {
for(int j = 1; j <= M; j++) {
if(j < weight[i]) {
dp[i][j] = dp[i - 1][j];
}else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
return dp[N][M];
}
從上述轉移方程中可以看出來,dp[i][j] 的取值只與其上一行的dp值有關,因此使用一個一維數組存儲上一行的結果,可以將二維動態規劃優化爲一維。轉移方程變爲:
但是此時需要注意由於之前二維dp[i][j]計算中用到了dp[i - 1][j - weight[i]],若原地修改的還是按照從左至右的順序,由於j - weight[i]在j前面,如此會出現計算dp[i][j]時用到的是dp[i][j - weight[i]]。代碼如下:
public int zeroOneBackpack() {
int[] dp = new int[M + 1];
for(int i = 1; i <= N; i++) {
for(int j = M; j >= 0; j--) {
if(j < weight[i]) {
dp[j] = dp[j];
}else {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
}
return dp[M];
}
完全揹包
其與0/1揹包的不同在於,所有的物品的數量是無窮大,一件物品可以拿多次。在0/1揹包中,dp[i][j]的值爲拿該物品和不拿該物品兩種可能中價值最大的,通過類比可以得到完全揹包中的dp[i][j] 的值應該爲拿該物品,拿一件, 拿兩件......拿j / weight[i]件中的價值最大的。其數學表達式如下:
實現代碼如下:
public int completeBackpack() {
int[][] dp = new int[N + 1][M + 1];
for(int i = 1; i <= N; i++) {
for(int j = 1; j <= M; j++) {
for(int k = 0; k <= j / weight[i]; k++) {
dp[i][j] = Math.max(dp[i][j],
dp[i - 1][j - k * weight[i]] + k * value[i]);
}
}
}
return dp[N][M];
}
上述解法的時間複雜度爲O(N * M * M)。
對上述解法的優化推導如下:
實現代碼如下:
public int completeBackpack() {
int[][] dp = new int[N + 1][M + 1];
for(int i = 1; i <= N; i++) {
for(int j = 1; j <= M; j++) {
if(j < weight[i]) {
dp[i][j] = dp[i - 1][j];
}else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
}
}
}
return dp[N][M];
}
此時時間複雜度降爲了O(N * M),我們在同0/1揹包那樣,將額外空間複雜度也降爲O(M),奇妙的事情發生了,優化後的轉移方程也爲:
當然跟之前不同了,上述公式中的dp[j - weight[i]]的值取的是本輪的新計算的值,而0/1揹包中則爲上一輪的舊值,因此從前往後遍歷即可,實現代碼如下:
public int completeBackpack() {
int[] dp = new int[M + 1];
for(int i = 1; i <= N; i++) {
for(int j = 1; j <= M; j++) {
if(j < weight[i]) {
dp[j] = dp[j];
}else {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
}
return dp[M];
}