回溯法求解01背包问题

问题描述

在前面文章http://blog.csdn.net/zjq_1314520/article/details/74858504我们使用动态规划求解了背包问题,时间复杂度是 O(cn) ,当我们的c的值非常大的时候,说消耗的时间也是非常大的!
接下来我们就使用回溯法来求解这个问题,其时间复杂度为 O(n2n) ,这个结果当我们的c的值是小于 2n 的时候,该算法所需的时间是小于动态规划的!

问题分析

既然使用了回溯法,我们就的构造解析树,因为对于每个物品我们有两种处理方式(加入背包或者不加入背包),故该问题的解是一颗子集树。

在前面的利用贪心算法求解背包问题的时候我们能得到理论的最优解,在回溯法涉及到对于子树的剪枝的时候可以利用贪心算法的得到的最优解(背包问题的最优解是01背包问题解的上界)作为我们剪枝的条件。

我们定义子集树的左节点代表将当前物品加入背包,右节点代表不将当前物品加入背包。

进入左节点的条件(即加入物品到背包):
当前物品的重量小于背包的剩余可容纳的重量。

进入右节点的条件(即不把当前物品加入到背包):
在不加入当前物品的情况下,剩余节点的最大价值上界(利用贪心算法求解)加上当前的价值小于我们求解的当前的最大价值,我们就无需考虑其右节点的情况。

代码示例

#include <iostream>
using namespace std;

float c=7;              //背包的总容量
int n=4;                //物品的总件数
//默认按照单位价值从大到小排序 
float w[10]={0,1,2,3,5};    //物品的质量 从 1 开始存储
float v[10]={0,5,8,9,10};   //物品的价值 从 1 开始存储
// 单位价值为: 5 4 3 2
float cw=0;             //当前重量
float cv=0;             //当前价值
float bestv=0;          //当前最优价值

//利用贪心算法取出最优解的上界限 
int bound(int i){       //传入节点 i

    //保存剩余的背包容量
    float cleft=c-cw;       //总容量 减去 当前已经装了的重量 
    float tempv=cv;     //保存当前的价值 

    for(int j=i;j<=n;j++){
        if(w[j]<cleft){
            tempv+=v[j];
            cleft-=w[j];
         } else{
            tempv+=v[j]*(cleft/w[j]);
            break;
         }
     }
     return tempv;
} 

void knip(int i){   //i 表示当前是哪一件物品 从 1 开始 

    if(i>n){
        bestv=cv;
        return;
    }

    // 当前位于节点i 判断是否可以进入左节点
    if(cw+w[i]<=c){     //当前物品i的质量小于 背包剩余质量 可以被加入到背包 
        //加入背包 改变当前的价值和重量 
        cw+=w[i];
        cv+=v[i];

        knip(i+1);      // 进入下一个节点 

        //回溯到前一个状态
        cw-=w[i];
        cv-=v[i]; 
    }

    // 当前位于节点i 判断是否可以进入右节点 即当前物品不加入背包

    if(bound(i+1)>bestv){       //后续节点的价值上界大于当前最优价值,则可以进入右界面 否则最优的都小于或等于当前的 就没必要再进入右节点 
        knip(i+1);              //进入右节点 因为不加入到背包 故 当前的价值 重量 都不发生改变 
    }
} 

int main(){
    knip(1);
    cout<<"最大价值为:"<<bestv<<endl;
} 
    原文作者:回溯法
    原文地址: https://blog.csdn.net/zjq_1314520/article/details/75118121
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞