0-1背包问题全解析

0-1背包问题
给定n个重量为w1/w2/w3.../wn,价值为v1/v2/v3.../vn的物品,容量为C的背包,求这个背包可以装下的价值最高的子集,每个物品只能使用一次

w = {2,1,3,2}   //重量
v = {12,10,20,15}   // 价值
C = 5   // 容量
#最佳子集为   [2,1,2]  12+10+15 = 37

暴力递归

对每个物品,都有选择/不选 两个状态,这样解空间就可以描述成一个状态树,

在这里插入图片描述
可以很像求子集的问题,不同的地方就是,子集的重量和需要小于背包重量,否则需要剪枝,如2->1->3不满足报容量,则3不可成为2->1这个子路径的叶节点,然后统计每一个子集的价值总和,比较得出最大值即可;

void backtrack(vector<int> w,vector<int> v,int C,int index,int& res,int val){

    if(index == w.size() || C<=0){
        res = max(res,val);
        return ;
    }
    
    if(C >= w[index]){
        backtrack(w,v,C-w[index],index+1,res,val+v[index]);
    }
    backtrack(w,v,C,index+1,res,val);
}

int main() {
    vector<int> w = {2,1,3,2};
    vector<int> v = {12,10,20,15};
    int res = 0,C=5;
    backtrack(w,v,C,0,res,0);
    cout<<res<<endl;
    return 0;
}

带备忘录的递归

因为上述方法会存在很多重复子问题的计算,所以我们可以保存子问题计算的结果,从而达到剪枝的效果

int backtrack2(vector<int> w,vector<int> v,int C,int index,int val,vector<vector<int>>& memo){
    if(index == w.size() || C <= 0){
        return val;
    }
    if(memo[index][C]!=-1)
        return memo[index][C];
    
    int aa = 0;
    if(C >= w[index]){
        aa = backtrack2(w,v,C-w[index],index+1,val+v[index],memo);
    }
    aa = max(aa,backtrack2(w,v,C,index+1,val,memo));
    
    memo[index][C] = aa;
    return aa;
}

int main() {
    vector<int> w = {2,1,3,2};
    vector<int> v = {12,10,20,15};
    int res = 0;

    vector<vector<int>> memo(w.size(),vector<int>(6,-1));
	res = backtrack2(w,v,5,0,0,memo);
    cout<<res<<endl;
    
    return 0;
}

动态规划

我们可以使用一个二维数组dp[n][C+1],表示第几个物体 在C容量下的价值;
dp[i][j] = max(dp[i-1][j] , dp[i-1][j-w[i] ] + v[i])
表示w[i]i个物体不装入背包的话,则直接由上一层价值传递,如果w[i]装入背包的话,则由dp[i-1][j-w[i]] 上一层 减去物体重量时的价值 + 物体本身的价值,取两者大,表格示意图如下:

	    | 背包容量
----------
物体id	|

在这里插入图片描述

int dfs_dp(vector<int> w ,vector<int> v,int C){
    vector<vector<int>> dp(w.size(),vector<int>(C+1,0));

    for (int i = 0; i <= C; i++) {
        dp[0][i] = w[0] <= i ? v[0] : 0;
    }

    for(int i=1;i<w.size();i++){
        for(int j=1;j <= C;j++){
            dp[i][j] = dp[i-1][j];
            if( j >= w[i] ){
                dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
            }
        }
    }
    for(auto i:dp){
        for(auto j:i)
            cout<<j<<" ";
        cout<<endl;
    }
    return dp[w.size()-1][C];
}

int main() {
    vector<int> w = {2,1,3,2};
    vector<int> v = {12,10,20,15};
    
    int res = 0;
    res = dfs_dp(w,v,5);

    cout<<res<<endl;
    return 0;
}

/*
0 0 12 12 12 12 
0 10 12 22 22 22 
0 10 12 22 30 32 
0 10 15 25 30 37 
*/

状态空间压缩

leetcode 416 Partition Equal Subset Sum

给定一个仅包含正整数的非空数组,确定该数组是否可以分成两部分,要求两部分的和相等

[1,2,3,6] --> [1,2,3],[6]
int dfs_dp(vector<int> w ,int C){
    //  抽象为: 使用n个(重量w)的物体,能否正好装满C容量的背包;
    // dp[n][c] 表示 使用n个物体,是否可以装满 C容量的背包;
    int n = w.size();
    vector<vector<bool>> dp(n,vector<bool>(C+1,false));
    
    dp[0][w[0]] = 1; // 初始,使用w[0]物体,可以正好装满w[0]容量的背包
    // dp[i][j] = dp[i-1][j]; 当前物体不入袋,继承上一个物体的状态
    // dp[i][j] = dp[i][j] || dp[i-1][j-w[i]]; 当前物体入袋||不入袋,只要有一个状态可以装满 即为True

    for(int i=1;i<n;i++){
        for(int j=0;j<=C;j++){
            dp[i][j] = dp[i-1][j];
            if(j>=w[i])
                dp[i][j] = dp[i][j] || dp[i-1][j-w[i]];
        }
    }
    
    for(auto i:dp){
        for(auto j:i)
            cout<<j<<" ";
        cout<<endl;
    }

    return dp[n-1][C];
}


int main() {
    vector<int> v1 = {1,2,3,6};

    bool flag = dfs_dp(v1,6);
    cout<<flag<<endl;
    return 0;
}
/*
0 1 0 0 0 0 0 
0 1 0 1 0 0 0 
0 1 0 1 1 0 1 
0 1 0 1 1 0 1 
*/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
0-1背包问题是一个经典的组合优化问题,它的目标是在给定的一组物品中选择一些物品放入容量为C的背包中,使得背包中物品的总价值最大。这个问题可以使用回溯法来决。 回溯法是一种通过搜索所有可能的来求问题的方法。在0-1背包问题中,我们可以使用回溯法来搜索所有可能的向量Xi,然后选择其中价值最大的向量作为最终的。 具体来说,我们可以按照以下步骤来设计回溯法算法: 1. 定义一个向量X,其中Xi表示第i个物品是否放入背包中。 2. 定义一个变量max_value,用于记录当前找到的最大价值。 3. 从第一个物品开始,依次考虑将其放入背包或不放入背包的情况。 4. 对于每种情况,计算当前的总价值,并与max_value进行比较。如果当前总价值大于max_value,则更新max_value。 5. 如果当前物品不是最后一个物品,则递归考虑下一个物品。 6. 如果当前物品是最后一个物品,则返回当前的总价值。 在实际实现中,我们可以使用一个递归函数来实现上述算法。具体来说,递归函数的参数包括当前的物品编号、当前的向量X、当前的总重量和总价值、背包的容量C、以及当前找到的最大价值max_value。递归函数的返回值为当前的总价值。 下面是一个使用回溯法决0-1背包问题的Python代码示例: ```python def backtrack(i, X, weight, value, C, max_value): if i == len(X): return value # 不放第i个物品 value1 = backtrack(i+1, X, weight, value, C, max_value) # 放第i个物品 if weight[i] <= C: X[i] = 1 value2 = backtrack(i+1, X, weight, value+value[i], C-weight[i], max_value) X[i] = 0 max_value = max(max_value, value2) return max(value1, max_value) # 测试代码 weight = [2, 3, 4, 5] value = [3, 4, 5, 6] C = 8 X = [0] * len(weight) max_value = backtrack(0, X, weight, 0, C, 0) print(max_value) ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值