閒來無事,想到了自己對解動態規劃問題掌握的還不大熟練,所以做個題練習下。
1,問題描述:
有一輛小船,能夠承載的最高重量爲c,當船承載的重量超過c時,船會沉沒。 現在有n個物品,物品i的重量爲w(i),價值爲v(i),應該如何選擇裝船的物品,保證船不沉,使得裝上船的物品總價值最大?
2,問題分析:
這是一道0-1動態規劃問題。對於動態規劃問題,最重要的是找到遞歸方程。要找到遞歸方程f(x,y),就要知道自變量x,y,因變量f。約束條件c一定是一個自變量,那麼還有一個自變量呢,是w還是v呢,我們可以看見,w和v可以看成是關於n的函數,所以另一個自變量就是n。那麼f(c,n)表示的含義爲:在容量限制爲c下,有n個商品情況下,能夠取得的商品最大價值。
這裏要注意的一點是:我們在程序中,f(c, n)的含義爲,在重量限制爲c,到選取第n個物品時,可以達到的最大價值。這是因爲我們一般會從一個商品開始,而到了第n個商品,也代表會有n種不同的商品。
計算f(c,n)即取第n個物品求最大價值的時候,一定是先過了第n-1個物品來的。從n-1到n有兩條路徑
1,不取第n-1個物品而來。f(c, n-1)
2,取第n-1個物品而來。 f(c-w[n], n-1) + v[n]
遞歸方程爲:
f(c, n) = max( f(c-w[n], n-1) + v[n], f(c, n-1))
其中:w[n]表示第n個物品的重量,v[n]表示第n個物品的價值。
3,喜聞樂見填表格
一般來說,動態規劃都會填寫最簡單的表格數據,而這些數據,將會用於填充剩下的空白表格。
4,實驗代碼:
public class Test {
private int c;
private int n;
private int[] w;
private int[] v;
private int[][] f;// f(x,y)表示在總重限制爲x時,選取第y個物品,可以達到的最大價值。這裏的選取條件是加入第y個物品,容量不超限制,且取得的價值最大。
// 如果拿了第i個物品,那麼相應的價值f(xxx, i)會比f(yyy, i-1)大,如果不變,說明不取第i個物品(這裏假設的前提是物品的價值爲正整數)
// 這樣就可以求出到底拿了哪些物品,以及價值的最大值。(最大值一定處於f矩陣的最右下角)
private Test(int c, int[] w, int[] v) {
this.c = c;
this.n = w.length;
this.w = new int[n + 1];
this.w[0] = 0;
System.arraycopy(w, 0, this.w, 1, n);// 使用第1爲做爲數組的開始
this.v = new int[n + 1];
this.v[0] = 0;
System.arraycopy(v, 0, this.v, 1, n);// 使用第1爲做爲數組的開始
this.f = new int[c + 1][n + 1]; //這裏也可以是反的
for (int[] ints : this.f) {
for (int anInt : ints) {
anInt = Integer.MIN_VALUE;// 全部初始化爲最小值
}
}
for (int i = 0; i < this.n + 1; i++) {
this.f[0][i] = 0; //容量限制爲0的情況下,最大價值總爲0
}
}
public void result() {
for (int cc = 1; cc < c + 1; cc++) {//計算第nn件物品在每個容量限制下能取得的最大價值
for (int nn = 1; nn < n + 1; nn++) {
if (cc >= w[nn]) {// 當前容量cc是否可以存放第nn個物品
f[cc][nn] = Math.max(f[cc - w[nn]][nn - 1] + v[nn], f[cc][nn - 1]);// 切分,這裏是反着分析的,也就是到了計算取第nn個物品求最大價值的時候,
// 則一定是先選過了第nn-1個物品來的。從nn-1到nn有兩條路徑
// 1,不取第nn-1個物品而來。
// 2,取第nn-1個物品而來。
// 遞歸方程:
// f(cc, nn) = Math.max(取第nn-1個物品而來, 不取第nn-1個物品而來) ==> f(cc, nn) = Math.max(f(cc-w[nn], nn-1) + v[nn], f(cc, nn-1))
} else {
f[cc][nn] = f[cc][nn - 1];// 如果第nn個物品要裝不下了。則是從不取第nn-1個物品而來的
}
}
}
System.out.println("重量:" + Arrays.toString(w));
System.out.println("價值:" + Arrays.toString(v));
for (int i = 0; i < this.f.length; i++) {
System.out.printf("容量限制爲:%s的最大價值\t", i);
for (int anInt : this.f[i]) {
System.out.print(anInt + "\t");
}
System.out.println();
}
System.out.println("可以獲利最大值爲:" + this.f[c][n]);
System.out.print("選取的物品編號爲:");
int cap = this.c;
for (int i = n; i >= 1; i--) {
if (f[cap][i] != f[cap][i - 1]) {// 這裏找的是由於i物品的加入,導致的價值變化,
cap -= w[i];//如果有變化,則表示選取了i物品,否則就是沒有取i物品
System.out.print(i + "\t");
}
}
}
public static void main(String[] args) {
new Test(20, new int[]{20, 10, 5, 15}, new int[]{4, 3, 2, 1}).result();
}
}
5,輸出:
完成的表格爲:
人工檢驗沒問題。