0-1背包问题的三种解法

0-1背包问题除了耳熟能详的动态规划,还有回溯法和分支限界法

1. 动态规划

使用动态规划,要求原问题具有最优子结构性质,即原问题的最优解包含了其子问题的最优解;并且子问题有重叠的部分,会被重复计算。

0-1背包问题是要求将N个具有一定价值v_i、重量w_i的物品放入承重为C的背包中(放则x_i=1,不放则x_i=0),使得价值最大,即max sum_N(x_i*v_i)   s.t sum_N(x_i*w_i)<=C,其子问题可写为将i个具有一定价值v_i、重量w_i的物品放入承重为j的背包中(放则x_i=1,不放则x_i=0),使得价值最大,即max sum_i(x_i*v_i)   s.t sum_i(x_i*w_i)<=j。若原问题的最大价值记为c[N][C],则子问题的最大价值可记为c[i][j]。

动态规划的求解思路是自底向上,即先求解最小的子问题,再基于已经求解的子问题求解下一个子问题的最优解。为了利用已经求解的子问题,可以建一个表来记录。

对于0-1背包问题,可建立如下递归式

主程序如下:

int KnapSack(int n, int w[ ], int v[ ]) {
     for (i=0; i<=n; i++)   //初始化第0列
           V[i][0]=0;
     for (j=0; j<=C; j++)   //初始化第0行
           V[0][j]=0;
for (i=1; i<=n; i++)   //计算第i行,进行第i次迭代
           for (j=1; j<=C; j++)
                  if (j<w[i])   V[i][j]=V[i-1][j];         
                  else   V[i][j]=max(V[i-1][j], V[i-1][j-w[i]]+v[i]);

     j=C;    //求装入背包的物品
     for (i=n; i>0; i--){
         if (V[i][j]>V[i-1][j]) {
              x[i]=1;
              j=j-w[i];
         }
         else x[i]=0;
     }
     return V[n][C];    //返回背包取得的最大价值
}

2. 回溯法

回溯法类似深度优先搜索,一股脑往前冲,路走不通了就回头。常用于解决两种问题:寻找满足条件的子集(比如背包问题,x_i=0|1|...),或者排列的方式(比如TSP问题,1234还是2341还是....),根据这两种问题有子集树和排列树两种模板可用。 

利用子集树求解0-1背包问题,主要还是如何剪枝。即,在什么情况下会选择不搜索左子树(放弃x_i=1),在什么情况下会选择不搜索右子树(放弃x_i=0)。

当选择x_i=1,背包就会超重时,就会放弃搜索左子树;当选择x_i=0,当前背包的价值和剩余未放入物品(不包括物品i,因为已经选择不放)的价值总和都小于目前最佳价值时,说明已经预测到这个没有前景了==,可以放弃继续搜索右子树了。

主程序如下:

int Bound(int i){
    int wr = c - wc;  // 背包剩余容量
    int vb = vc;        // vc为当前背包价值
    // 按单位价值递减顺序装入物品
    while(i <= n && w[i] <= wr){
        wr -= w[i]; vb += v[i]; ++i;
    }
    if(i <= n) vb += (v[i]/w[i])*wr; // 继续装满背包
    return vb; // 返回背包价值上界
}
backtrack(int i){
    if( i > n) {   // vc当前背包价值,m当前最优价值
        m = ( m < vc )? vc : m;   output(x);
    } else {  // wc当前背包重量
        if ( wc + w[i] <= C ) {  // 左子树(将 i 放入背包)
            x[i]= 1; wc += w[i]; vc += v[i];
            backtrack(i+1);
            x[i]= 0; wc -= w[i]; vc -= v[i];
        }
        if(Bound(i+1) > m){  // 右子树(拿出物品i)
            backtrack(i+1);
        }
    }
}

3. 优先队列式分支限界法

分支限界类似广度优先搜索,其原理在《对比Dijkstra和优先队列式分支限界》中已经介绍。

对于0-1背包问题,每个结点的邻接点只有两个,即0和1,记为左子树和右子树。

猜你喜欢

转载自www.cnblogs.com/fanmu/p/9200632.html
今日推荐