動態規劃篇——背包問題
本次我們介紹動態規劃篇的背包問題,我們會從下面幾個角度來介紹:
- 背包問題概述
- 零一背包問題
- 完全背包問題
- 多重背包問題
- 分組背包問題
背包問題概述
背包問題算是很經典的動態規劃問題,我們在面試中也經常出現
首先我們給出動態規劃的思想:

然后我們簡單介紹一下背包問題:
/*背包問題*/
有 N 件物品和一個容量是 V 的背包,
第 i 件物品的體積是 vi,價值是 wi,
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大,輸出最大價值,
/*輸入格式*/
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積,
接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i 件物品的體積和價值,
/*輸出格式*/
輸出一個整數,表示最大價值,
最后我們介紹我們下列將要講述了背包問題的前提:
/*01背包問題*/
每件物品只能使用一次
/*完全背包問題*/
每件物品無次數限制使用
/*多重背包問題*/
每件物品有不同的使用次數
/*分組背包問題*/
每組物品有若干個,同一組內的物品最多只能選一個
零一背包問題
我們首先介紹一下01背包規則:
/*背包問題*/
有 N 件物品和一個容量是 V 的背包,
第 i 件物品的體積是 vi,價值是 wi,
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大,輸出最大價值,
/*輸入格式*/
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積,
接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i 件物品的體積和價值,
/*輸出格式*/
輸出一個整數,表示最大價值,
/*限制條件*/
每件物品只能使用一次
然后我們對其進行分析:
/*內容分析*/
首先我們有 N 件物品,總容量為 V
如果我們想要求得最大 W 的情況,我們就需要計算所有的 N 和 V 情況
/*暴力求解方法分析*/
我們這里首先采用最暴力的方法(二維):
我們采用f[i][j]來表示前i件物品中進行選擇,其體積不超過j,儲存值為W最優解
我們會發現,f[i][j]無非就兩種情況:
在i比前一位增加一位后,如果我們當前的i沒有包含最后一位,那么一切都和上一位i的結果相同(f[i][j] = f[i-1][j])
那么我們就只需要判斷是否需要加上第i位,且前提是j >= v[i](f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]))
/*優化方法分析*/
下面我們介紹的優化方法來自于滾動陣列:
滾動陣列是指當我們只需要兩行資料時,我們可以拋出二維的概念,采用層級差來覆寫掉之前的資料資訊,從而轉換為一維
我們對上述暴力求解進行分析:
我們會發現其實我們所采用的無非只有兩行:f[i]和f[i-1]
那么我們只需要將f[i]所使用的f[i-1]的資訊在使用前保留下來,我們就可以將其簡化為一行,也就是一維
我們給出實際代碼以及代碼中的決議:
/*暴力求解方法*/
import java.util.Scanner;
public class Packsack01 {
final static int N = 1010;
static int n,m;
// 存放f[][],v[],w[]
static int[][] f = new int[N][N];
static int[] v = new int[N];
static int[] w = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
// 首先我們應該初始化f[][],但是由于需要初始化為0,陣列默認為0,所以我們不需要書寫
// 然后我們放入v,w
for (int i = 1; i <= n; i++) {
v[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
// 然后我們就可以逐層更新(第0層為前0個物品,肯定都是0,不用更新)
for (int i = 1; i <= n; i++) {
// 我們對前i個物品的體積v也進行遞增
for (int j = 0; j <= m; j++) {
// 如果我們不加入最后一個數,那么當前i層的值和i-1層的值相同
f[i][j] = f[i-1][j];
// 注意:由于加入第i個數不一定是最優解,所以我們需要進行w權重比較
// 我們比較的資料分別是上一層的不加i的w
// 注意這里由于上面f[i][j] = f[i-1][j],所以下面的f[i][j]實際上是上一層的f[i-1][j]
// 以及我們該層加上i之后的w,我們加上i之后v就需要去掉v[i]
// 同時我們選取前i-1個數的v為j-[v[i]]的w最優解加上w[i]來進行比較
if (j >= v[i]) f[i][j] = Math.max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
// 最后輸出即可
System.out.println(f[n][m]);
}
}
/*優化求解方法*/
import java.util.Scanner;
public class Packsack01 {
final static int N = 1010;
static int n,m;
// 存放f[],v[],w[]
static int[] f = new int[N];
static int[] v = new int[N];
static int[] w = new int[N];
// 建議和暴力求解對比觀看
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
// 首先我們應該初始化f[],但f[0]最開始都是0,就不需要初始化了
// 然后我們放入v,w
for (int i = 1; i <= n; i++) {
v[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
// 然后我們就可以逐層更新
for (int i = 1; i <= n; i++) {
// 我們對前i個物品的體積v也進行遞增
// 注意:由于下面判斷條件需要保證j>=v[i],所以我們這里可以直接從v[i]開始,畢竟前面的條件都不滿足
// 注意:我們這里需要倒敘書寫, 因為我們下面要使用f[i-1][j-v[i]]這里的i-1就是上一層,我們需要注意我們不能覆寫掉這一層!!!
for (int j = m; j >= v[i]; j--) {
// 這里簡化i之后,為f[j] = f[j],恒等式,我們就直接省略了
// 注意:由于加入第i個數不一定是最優解,所以我們需要進行w權重比較
// 我們比較的資料分別是上一個不加i的w
// 以及我們該層加上i之后的w,我們加上i之后v就需要去掉v[i]
// 同時我們選取前i-1個數的v為j-[v[i]]的最優解來進行比較,記得加上w[i]
// 這里我們需要注意,我們后面比較的值是上一層的f
// 所以我們前面的for回圈的方向需要轉換一下,防止上一層的資料被覆寫掉
f[j] = Math.max(f[j],f[j-v[i]]+w[i]);
}
}
// 最后輸出即可
System.out.println(f[m]);
}
}
完全背包問題
我們首先介紹一下完全背包規則:
/*背包問題*/
有 N 件物品和一個容量是 V 的背包,
第 i 件物品的體積是 vi,價值是 wi,
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大,輸出最大價值,
/*輸入格式*/
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積,
接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i 件物品的體積和價值,
/*輸出格式*/
輸出一個整數,表示最大價值,
/*限制條件*/
每件物品沒有使用次數限制
然后我們對其進行分析:
/*內容分析*/
首先我們有 N 件物品,總容量為 V
如果我們想要求得最大 W 的情況,我們就需要計算所有的 N 和 V 情況
/*暴力求解方法分析*/
我們首先介紹暴力求解方法:
我們其實所有步驟和01背包的步驟相似,但不同的是對于第i個物品的數量的大小的決定
我們在不能承載第i個物品前:f[i][j] = f[i-1][j]
在我們能承載第i個物品后:f[i][j] = Math.max(f[i-1][j],f[i-1][j - k*v[i]] + k * w[i])
所以我們只需要在01背包基礎上加上一個fork回圈來控制第i個物品的數量保證最優解即可
/*優化方法1分析*/
我們在上述暴力求解中直接采用了fork回圈,這時我們的時間復雜度在 O(n^3),所以我們想要減少時間復雜度
我們可以發現,我們上述承載第i個物品后:f[i][j] = Math.max(f[i-1][j],f[i-1][j - k*v[i]] + k * w[i])
那么相當于:f[i][j] = Math.max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i],...)
相當于我們直接在上一個f[i][j]的基礎上判斷是否能夠添加i物品,也就是:f[i][j] = Math.max(f[i][j],f[i][j-v[i]]+w[i])
/*優化方法2分析*/
最后一重優化其實就是01背包的優化,我們轉化為滾動陣列即可
下面我們介紹的優化方法來自于滾動陣列:
滾動陣列是指當我們只需要兩行資料時,我們可以拋出二維的概念,采用層級差來覆寫掉之前的資料資訊,從而轉換為一維
我們對上述暴力求解進行分析:
我們會發現其實我們所采用的無非只有兩行:f[i]和f[i-1]
那么我們只需要將f[i]所使用的f[i-1]的資訊在使用前保留下來,我們就可以將其簡化為一行,也就是一維
我們給出實際代碼以及代碼中的決議:
/*暴力求解演算法*/
import java.util.Scanner;
public class PacksackFull {
final static int N = 1010;
static int n,m;
static int[][] f = new int[N][N];
static int[] v = new int[N];
static int[] w = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
for (int i = 1; i <= n; i++) {
v[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
// 重點在這里!!!
// 我們之前的f[i][j] = f[i-1][j]也融入到下面的max判斷里去了
// 我們由于需要判斷第i個物品的數量,我們需要從0開始,判斷應該增加幾個i物品
for (int k = 0; k * v[i] <= j; k++) {
f[i][j] = Math.max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
}
}
System.out.println(f[n][m]);
}
}
/*優化演算法1*/
import java.util.Scanner;
public class PacksackFull {
final static int N = 1010;
static int n,m;
static int[][] f = new int[N][N];
static int[] v = new int[N];
static int[] w = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
for (int i = 1; i <= n; i++) {
v[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
// 我們為了減少一層回圈,我們直接將f[i][j]與前面的f[i][j]比較即可
// 注意:記得先給當前f[i][j]賦值
f[i][j] = f[i-1][j];
// 然后我們才能進行比較,我們將f[i-1][j]與f[i]層的比較(記得判斷是否可以加v[i]!)
if(j >= v[i]) f[i][j] = Math.max(f[i][j],f[i][j-v[i]]+w[i]);
}
}
System.out.println(f[n][m]);
}
}
/*優化演算法2*/
import java.util.Scanner;
public class PacksackFull {
final static int N = 1010;
static int n,m;
static int[] f = new int[N];
static int[] v = new int[N];
static int[] w = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
for (int i = 1; i <= n; i++) {
v[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
for (int i = 1; i <= n; i++) {
// 注意:由于這次我們使用的是第i層的資料,所以我們需要從前往后遍歷提前更新第i層資料,防止使用第i-1層資料
for (int j = v[i]; j <= m; j++) {
if(j >= v[i]) f[j] = Math.max(f[j],f[j-v[i]]+w[i]);
}
}
System.out.println(f[m]);
}
}
多重背包問題
我們首先介紹一下多重背包規則:
/*背包問題*/
有 N 件物品和一個容量是 V 的背包,
第 i 種物品最多有 si 件,每件體積是 vi,價值是 wi,
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大,輸出最大價值,
/*輸入格式*/
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積,
接下來有 N 行,每行三個整數 vi,wi,si,用空格隔開,分別表示第 i 種物品的體積、價值和數量,
/*輸出格式*/
輸出一個整數,表示最大價值,
/*限制條件*/
每個物品有一定的使用次數限制
然后我們對其進行分析:
/*內容分析*/
首先我們有 N 件物品,總容量為 V
如果我們想要求得最大 W 的情況,我們就需要計算所有的 N 和 V 情況
/*暴力求解方法分析*/
其實暴力求解方法和完全背包問題暴力求解方法完全相同
只不過是在k的限制條件上多加了一個k < s[i]的限制而已
/*優化方法分析*/
我們需要注意多重背包優化由于有數量限制的原因,無法使用完全背包優化!
我們因為多重背包有數量限制,當數量較少時,我們采用暴力求解是沒有問題的,但是當s數量過多,高達一兩千就會導致問題
我們的優化思路是
通過將該物品打包分類為多個新的物品,重新定義這些物品的v和w,s固定為1
我們選擇2的n次冪來打包物品,因為2的n次冪相加可以組成2的n+1次冪內的所有數!
我們給出實際代碼以及代碼中的決議:
/*暴力求解演算法*/
import java.util.Scanner;
public class PacksackNumber {
final static int N = 1010;
static int n,m;
// 多添加一個s陣列存放個數
static int[][] f = new int[N][N];
static int[] v = new int[N];
static int[] w = new int[N];
static int[] s = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
for (int i = 1; i <= n; i++) {
v[i] = scanner.nextInt();
w[i] = scanner.nextInt();
s[i] = scanner.nextInt();
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
// 重點在這里!!!
// 我們只需要多一個條件 k <= s[i]即可
for (int k = 0; k <= s[i] && k * v[i] <= j; k++) {
f[i][j] = Math.max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
}
}
System.out.println(f[n][m]);
}
}
/*優化演算法*/
import java.util.Scanner;
public class PacksackNumber {
// 因為是二進制,一個數最多就是2的12次方就會超過題目給的2000,所以給個將限制范圍1000*12
final static int N = 12000;
static int n,m;
// 這里的f采用一維即可,因為最后我們會轉變為01問題,可以采用滾動陣列優化
static int[] f = new int[N];
// 我們這里只需要記錄v,w即可,因為我們會根據輸入的資料重新更新v,w,不再存在s的概念
static int[] v = new int[N];
static int[] w = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
// 表示定義到第幾個資料
int cnt = 0;
// 我們根據輸入的資料重新定義v,w
for (int i = 1; i <= n; i++) {
// a是v,b是w,s是數量
int a = scanner.nextInt();
int b = scanner.nextInt();
int s = scanner.nextInt();
// 我們根據2的k次冪來劃分s,重新分成物品
int k = 1;// 相當于2的0次冪
// 一直更新到無法存放k
while ( k <= s){
// 更新資料位置
cnt++;
// 將資料存入
v[cnt] = a * k;
w[cnt] = b * k;
// 數量減去已存放的k,并且k翻倍(2次冪)
s -= k;
k *= 2;
}
// 判斷是否有剩余元素
if (s > 0){
// 若存放剩余元素,我們還需要存放
cnt ++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
// 我們目前擁有cnt個新物品(被我們分解的)
n = cnt;
// 我們對新物品進行裝載即可(01背包)
for (int i = 1; i <= n; i++) {
for (int j = m; j >= v[i] ; j--) {
f[j] = Math.max(f[j],f[j - v[i]] + w[i]);
}
}
System.out.println(f[m]);
}
}
分組背包問題
我們首先介紹一下分組背包規則:
/*背包問題*/
有 N 組物品和一個容量是 V 的背包,
每件物品的體積是 vij,價值是 wij,其中 i 是組號,j 是組內編號,
求解將哪些物品裝入背包,可使物品總體積不超過背包容量,且總價值最大,輸出最大價值,
/*輸入格式*/
第一行有兩個整數 N,V,用空格隔開,分別表示物品組數和背包容量,
接下來有 N 組資料:
每組資料第一行有一個整數 Si,表示第 i 個物品組的物品數量;
每組資料接下來有 Si 行,每行有兩個整數 vij,wij,用空格隔開,分別表示第 i 個物品組的第 j 個物品的體積和價值;
/*輸出格式*/
輸出一個整數,表示最大價值,
/*限制條件*/
每組物品有若干個,同一組內的物品最多只能選一個,
然后我們對其進行分析:
/*內容分析*/
首先我們有 N 組物品,總容量為 V
如果我們想要求得最大 W 的情況,我們就需要計算所有的 N組物品中每種物品使用 和 V 情況
/*求解方法分析*/
我們同樣采用一層迭代一層的原則,但由于每組商品只能選擇一次,所以我們在f[i][j]的情況下,需要與第i組的所有物品互動判斷一次
同樣我們由于f[i]只利用f[i-1]層原理,我們可以采用滾動陣列的原理來將二維陣列變為一維陣列
我們給出實際代碼以及代碼中的決議:
/*已優化演算法*/
import java.util.Scanner;
public class Packsack01 {
final static int N = 110;
static int n,m;
// 這里的f采用一維即可
static int[] f = new int[N];
// 我們這里使用了分組概念,需要二維陣列記錄資訊
static int[][] v = new int[N][N];
static int[][] w = new int[N][N];
// s這里記錄該組的個數
static int[] s = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
// 對分組輸入資料
for (int i = 1; i <= n; i++) {
// 記錄該組數量
s[i] = scanner.nextInt();
for (int j = 1; j <= s[i]; j++) {
// 記錄v,w
v[i][j] = scanner.nextInt();
w[i][j] = scanner.nextInt();
}
}
// 開始遍歷即可
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 0;k <= s[i]; k++) {
if (v[i][k] <= j){
f[j] = Math.max(f[j],f[j - v[i][k]] + w[i][k]);
}
}
}
}
System.out.println(f[m]);
}
}
結束語
好的,關于動態規劃篇的背包問題就介紹到這里,希望能為你帶來幫助~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/538123.html
標籤:Java
下一篇:JVM運行資料區深度決議
