[算法] 0-1背包(dp+dfs)

题目描述

给定n种物品和一个背包,物品i的重量是Wi,其价值为Vi,背包的容量为C。如何选择装入背包的物品,可以使得装入背包中物品的总价值最大?

​ 0-1背包,每件物品的状态只有两个:放(1)和不放(0),不能只放入一部分(放入一部分的是部分背包问题,采取贪心策略优先选择价值重量比高的物品就好)。

​ 0-1背包有很多种解决方法,这里只整理两种:动态规划(填表,dp)和回溯(深度搜索,dfs)

动态规划

​ 想要使用动态规划求解0-1背包问题,对题目的条件要求有些苛刻:因为是利用背包的容量C作为要填写表格的列,所以要求物品的重量W和价值V,以及背包的容量C均必须是整数

给出示例:

物品数量 n=5,背包总容量 C=10

物品的重量分别为:w={ 2,2,6,5,4 }

物品的价值分别是:v={ 6,3,5,4,6 }

​ 我们定义一个二维数组 m[i] [j],意义为:背包剩余容量为 j 时,可选择物品 i,i+1,…,n 时,能获取的最大价值(最优解)

当只剩下最后第n个物品时:

在这里插入图片描述

其他情况:

在这里插入图片描述

当剩余容量 j 比当前物品 i 的重量大时:

​ 在放入第i个物品的背包价值 m[i+1] [j-w[i]]+v[i]不放入第i个物品的背包价值 m[i+1] [j] 中挑选最大值

当剩余容量 j 比当前物品 i 的重量小时:

​ 最有价值即m[i+1] [j]

填表

在这里插入图片描述

以[4,9]为例,m[4][9]=10,此时剩余背包容量为9,当前物品4的重量w[4]=5,价值v[4]=4,如果不装当前的物品4,则背包中物品价值和=m[5] [9]=6,如果将当前的物品装入背包,则价值和=v[4]+m[5] [9-w[4]]=4+6=10;

完整题目

输入

每组输入包括三行,
第一行包括物品个数n,以及背包容量C。
第二、三行包括两个一维数组,分别为每一种物品的价值和重量。

输出

输出背包的最大总价值

样例输入

5 10
6 3 5 4 6
2 2 6 5 4

样例输出

15
11001

代码

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int inf=0x7fffffff;
const int maxn=1010;

int n,c;
int v[maxn],w[maxn];
int m[maxn][maxn];

int main(){
    while(cin>>n>>c){
        memset(m,0,sizeof(m));
        for(int i=0;i<n;i++){
            cin>>v[i];
        }
        for(int i=0;i<n;i++){
            cin>>w[i];
        }
        //先填表格的最后一行
        for(int i=0;i<=c;i++){
            if(i<w[n-1]) m[n-1][i]=0;
            else m[n-1][i]=v[n-1];
        }
        //再继续往上依次填写表格
        for(int i=n-2;i>=0;i--){
            for(int j=0;j<=c;j++){
                if(j<w[i]) m[i][j]=m[i+1][j];
                else{
                    m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
                }
            }
        }
        //表格右上角为最优解
        cout<<m[0][c]<<endl;
    }
    return 0;
}

构造最优解:

上面的代码仅仅是求出了背包所能装的最大价值,我们现在引入数组 x[],来标记装入背包的是哪些物品:

int x[maxn];
for(int i=0;i<n-1;i++){
    //此处c用来记录剩余背包容量
    if(m[i][c]==m[i+1][c]){
        x[i]=0;
    }
    else{
        x[i]=1;
        c-=w[i];
    }
}
//最后一行单拎出来判断
x[n-1]=(m[n-1][c]>0?1:0);
//输出
for(int i=0;i<n-1;i++){
    cout<<x[i]<<" ";
}
cout<<x[n-1]<<endl;




回溯法

因为动态规划解决的0-1背包问题必须为整数,而实际应用中,这种情况少之又少,所以引入回溯法(dfs)。

数据结构

物品i的重量是wi,价值 为vi,背包的容量为C, 物品重量wi背包容量C可为正整数或小数

int n;//物品数目
int v[maxn],w[maxn],C;
int cv;//当前价值
int bestv=0;//最优价值
int  x[maxn];//当前解
int bestx[maxn];//当前最优解
int cw=0;//当前重量

基本思想

  • 按照贪心算法的思路,优先装入价值/重量比大的物品
  • 当剩余容量装不下后续的物品时,再用回溯法修改先前的装入方案
  • 知道求得全局最优解为止

搜索函数

void dfs(int   t){
    if(t>=n){
        if(bestv<cv){
            bestv=cv;
            for(int j=0;j<n;j++){
                best[j]=x[j];
            }
        }
        //输出
    }
    else{
        //搜索左枝
        if(ccw+w[t]<=C){
            x[t]=1; cv+=v[t]; cw+=w[t];
            dfs(t+1);//继续向下深度搜索
            x[t]=0; cv-=v[t]; cw+=w[t];//回退
        }
        //搜索右枝
        x[t]=0;
        dfs(t+1);
    }
}

改进思路

  • 前述算法完全搜索解空间树时:

    • 约束条件确定是否搜索其左子树
    • 直接进入右子树(没有判断)–>耗时
  • 改进

    • 只要左结点是一个可行结点,搜索就进入左子树——约束函数
    • 当右子树可能包含最优解时才进入右子树搜索,否则剪去右子树——限界函数

    剪枝方法

    • r:当前剩余物品价值和总和

    • cv:当前获得价值

    • bestv:当前最优价值

    • cv+r<=bestv时,可剪去右子树–>当前价值与剩余物品价值之和小于等于已求最优价值,无法得到更优解

      • 计算右子树上界贪心

        将剩余物品依其单位重量价值排序(降序),然后依次装入物品,直至装不下 时,再装入物品的一部分而装满背包,此时得到的价值是右子树中解的上界

改进代码
int bound(int  i){//计算当前结点处的上界
    int left=C-cw;//剩余容量
    int  b=cv;//当前价值
    while(w[i]<=left&&i<=n){
        b+=v[i];
        left-=w[i];
        i++;
    }
    //装满背包
    if(i<=n) b+=left*v[i]*w[i];
    return b;   
}
void dfs(int t){//回溯
    if(t>n){
        //代码省略,同上
    }
    else{
        //搜索左枝
         if(ccw+w[t]<=C){
            x[t]=1; cv+=v[t]; cw+=w[t];
            dfs(t+1);//继续向下深度搜索
            x[t]=0; cv-=v[t]; cw+=w[t];//回退
        }
        //搜索右枝
        if(bound(t+1)>bestv){
            x[t]=0;
            backtrack(t+1);
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值