揹包問題之01揹包

問題描述
已知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.






















相關文章
相關標籤/搜索