問題描述
已知n個物品和一個揹包容量爲C,物品i(i=1,2,3,......)的容量爲c[i] 價值w[i]。
物品i能夠裝入,也能夠不裝入,可是不能夠拆分。如何設計裝包使得裝包
總效益最大。
動態規劃逆推求解
設dp[i,j]爲揹包容量j,可取物品的範圍爲i,i+1,i+2,......n的最大效益值。
即這是從後往前做爲遞推的方向。也就是思考怎麼從i+1這個狀態轉移到i的狀態
無非是放入和不放兩種狀況。
這裏:
當 j<c[i]的時候 物品沒法放入。最大效益值爲dp[i+1,j]相同。
當 j>=c[i]的時候 會出現兩個選擇:
(1)不放入物品i 最大效益值爲dp[i+1,j]
(2)放入物品i 這個時候的最大效益是 dp[i+1,j-c[i]]+w[i]
這裏咱們指望的最大效益是這二者中的最大值。
所以這個遞推方程爲 dp[i,j]=max{ dp[i+1,j] ,dp[i+1,j-c[i]]+w[i] }
這個方程全面一點就是
這裏既能夠安裝上面的思路推出,也能夠
由dp[i,j]=max{ dp[i+1,j] ,dp[i+1,j-c[i]]+w[i] }
想到
j>c[i]才能夠保證結果是大於0 從而想到須要比較
j和c[i]的大小。從而得出完整的遞推關係式。
#
include
<stdio.h
>
#
include
<string.h
>
int main()
{
int n,c,p[
50],w[
50],m[
50][
500],dp[
500];
//參數輸入
printf(
"請物品的個數和揹包的總重量:");
scanf(
"%d%d",
&n,
&c);
for(
int i
=
1;i
<
=n;i
++)
{
printf(
"輸入w%d p%d:",i,i);
scanf(
"%d%d",
&w[i],
&p[i]);
}
//遞推
//初始化的操做
//注:如數組沒有初始化 則其值是隨機的
for(
int j
=
0;j
<
=c;j
++)
{
if(j
>
=w[n]) m[n][j]
=p[n];
else m[n][j]
=
0;
}
//使用遞推關係
for(
int i
=n
-
1;i
>
=
1;i
--)
for(
int j
=
0;j
<
=c;j
++)
{
if(j
>
=w[i]
&&m[i
+
1][j
-w[i]]
+p[i]
>m[i
+
1][j])
m[i][j]
=m[i
+
1][j
-w[i]]
+p[i];
else
m[i][j]
=m[i
+
1][j];
}
printf(
"最大值:%d\n",m[1][c]);
return
0;
}
動態規劃順推求解
設dp[i,j]爲揹包容量爲j,可選的物品爲1,2,3,....i的時候的最大效益。
從前日後遞推,有i-1狀態轉移到i狀態。
#
include
<stdio.h
>
#
include
<string.h
>
int main()
{
int n,c,p[
50],w[
50],m[
50][
500],dp[
500];
//參數輸入
printf(
"請物品的個數和揹包的總重量:");
scanf(
"%d%d",
&n,
&c);
for(
int i
=
1;i
<
=n;i
++)
{
printf(
"輸入w%d p%d:",i,i);
scanf(
"%d%d",
&w[i],
&p[i]);
}
//順推
//初始化
for(
int j
=
0;j
<
=c;j
++)
{
if(j
>
=w[
1]) m[
1][j]
=p[
1];
else m[
1][j]
=
0;
}
//利用遞推關係
for(
int i
=
2;i
<
=n;i
++)
for (
int j
=
0;j
<
=c;j
++)
{
if(j
>
=w[i]
&&m[i
-
1][j]
<m[i
-
1][j
-w[i]]
+p[i])
m[i][j]
=m[i
-
1][j
-w[i]]
+p[i];
else
m[i][j]
=m[i
-
1][j];
}
pritf(
"%d",m[n][c])
return
0;
}
對於初始化,能夠直接所有賦值爲0便可。第二次循環從w[i]開始
#
include
<cstdio
>
#
include
<cstring
>
#
include
<algorithm
>
using
namespace std;
int n,c,p[
50],w[
50],m[
50][
500],dp[
500];
int main()
{
//參數輸入
printf(
"請物品的個數和揹包的總重量:");
scanf(
"%d%d",
&n,
&c);
for(
int i
=
1;i
<
=n;i
++)
{
printf(
"輸入w%d p%d:",i,i);
scanf(
"%d%d",
&w[i],
&p[i]);
}
memset(m,
0,
sizeof(m));
for(
int i
=
1;i
<
=n;i
++)
for(
int j
=c;j
>
=w[i];j
--)
m[i][j]
=max(m[i
-
1][j],m[i
-
1][j
-w[i]]
+p[i]);
printf(
"%d",m[n][c]);
return
0;
}
進一步的思考
上面的思惟實際是揹包的從前日後 以及從後往前的比較。這裏進一步想想
程序實際是兩個for循環,咱們上面實際都是對i 從前日後和從後往前。
能不能這裏對j也有兩種遍歷呢?
在這篇博客裏面引入了一個問題。就是將二位數組下降爲一維。
再降維的時候 咱們只能將第二個循環從大到小進行遍歷。緣由是若是從小往大遍歷就會覆蓋。
一維的程序很容易寫成:
dp[j] =max { dp[j] , dp[j-c[i]+w[i]] }
參考《揹包九講》因而僞代碼是:
for i
=
1......N
for v
=V......
0
dp[v]
=max{dp[v],dp[v
-c[i]]
+w[i]}
其實這個代碼仍是有點抽象的,
在真正編程的時候應該這麼去寫:
for i
=
1......N
for v
=V......
0
if(v
>
=c[i]) dp[v]
=max(dp[v],dp[v
-c[i]]
+w[i]);
else dp[v]
=dp[v];
這個時候咱們很清楚的就能夠看到了 在v<c[i]的時候 實際上dp[v]是沒有發生變化的。
所以咱們能夠減小這一步的操做。將v = V......c[i]進一步加一優化。
for i
=
1.........N
for v
=V......c[i]
dp[v]
=dp[v]
>dp[v
-c[i]]
+w[i]
?dp[v]
:dp[v
-c[i]]
+w[i];
同時關於初始化的細節,《九講》裏面也說得很不錯。
若是是揹包必須裝滿即最後的最大值爲V。那麼初始化 dp[0]=0, dp[1.....V]=-INF
若是能夠不裝滿 那麼只用memset(dp,0,sizeof(dp))所有初始化爲0.