01背包类型问题的两种解法

来源:互联网 发布:不上班的23种活法 知乎 编辑:程序博客网 时间:2024/06/05 06:08

  这里讲两道题目(类型均是01背包类型的),两道题目均用了回溯法和动态规划两种解决办法,做了以后还是有所启发的。
  第一道题目就是著名的01背包问题。
  01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2……Wn,与之相对应的价值为P1,P2……Pn。求背包能放的最大价值。
  回溯法的解法:

//0-1背包问题,假定n为8(总共有8种物品),M=110(总共能放的重量为110),重量是w={1,11,21,23,33,43,45,55},价值p={11,21,31,33,43,53,55,65},且已按单位价值排好序#define maxsize 9#include <IOSTREAM>using namespace std;int n=8;int M=110;   //总共能放的重量为110int bestp=0;    //最大的价值int noww=0;    //当前容量double nowp=0;     //当前价值,会出现小数的int x[maxsize]={0};      //用来判断物品是否被选int bestx[maxsize]={0};      //用来表示最优解下哪些物品被选int w[maxsize]={0,1,11,21,23,33,43,45,55}; //物品重量,0号元素不用int p[maxsize]={0,11,21,31,33,43,53,55,65};  //价值,0号元素不用int Check(int noww,int k,int nowp) {    int flag=0;        for (int i=k+1;i<=n;i++)    {        if (noww+w[i]<M)     //如果可以放就放进去        {            noww=noww+w[i];            nowp=nowp+p[i];        }        else        {            flag=1;         //flag=1表示当前有放不进去的            break;        }    }    if (flag==1)   //如果不是全部可以放进去    {        nowp=nowp+(p[i]/w[i])*(M-noww);     //分割后装满算最大价值    }    return nowp;}void Backtrack(int noww,int k,int nowp)  //noww为当前的重量,k为层数(第几个物品),nowp为现在的价值{    if (k>n)  //表明所有的物品已经全部选择过了    {        if (nowp>bestp||bestp==0)    //1.当前的解比之前的最优解还要好,把当前解给最优解。2.如果是第一次也要赋值        {            bestp=nowp;            for(int i=1;i<=n;i++)            {                bestx[i]=x[i];            }        }        return;    //要返回不然会一直递归下去    }    if(noww+w[k]<=M)  //如果到这一层为止的数小于M,则进入左子树,否则一定不可能,左子树便不生成    {        x[k]=1;        Backtrack(noww+w[k],k+1,nowp+p[k]);    }    if (Check(noww,k,nowp)>=bestp)  //重要的剪枝,如果这一层不加,加上剩余的最大值(考虑分割,虽然不能取,但可以找到一个最大值),如果这个最大值比最优解还要小,那右子树不用考虑    {        x[k]=0;        Backtrack(noww,k+1,nowp);    }}int main(){       cout<<"0-1背包问题,假定n为8(总共有8种物品),M=110(总共能放的重量为110),重量是w={1,11,21,23,33,43,45,55},价值p={11,21,31,33,43,53,55,65},且已按单位价值排好序。"<<endl<<endl<<endl;    Backtrack(0,1,0);    cout<<"最优解是:选重量是";    for (int i=1;i<=n;i++)    {        if (x[i]==1)        {            cout<<w[i]<<",";        }    }    cout<<"总价值为"<<bestp<<"的物品。"<<endl;    return 0;}

  动态规划的解法:

//0-1背包问题#include <IOSTREAM>using namespace std;int n=4;int C=11;int V[4+1][11+1];int p[4+1]={0,3,4,5,7};int w[4+1]={0,2,3,5,6};int X[11+1];int Knapsack(){    for (int i = 0; i <= C; ++i)    {        X[i] = 0;    }    for (int j = 1; j <= n; ++j)    {        for (int i = C; i >= 1; --i)        {            if (i >= w[j] && X[i - w[j]] + p[j] > X[i])            {                X[i] = X[i - w[j]] + p[j];            }        }    }    return X[C];}int main(){       int bestp=Knapsack();    cout<<"选总价值为"<<bestp<<"的物品。"<<endl;    return 0;}

  第二道题目是去年暑假做的。浙大07年考研复试题最大报销额。
  题目是这样的:
  现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。
  输入:
  测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(N<=30)是发票张数。随后是 N 行输入,每行的格式为:
  m Type_1:price_1 Type_2:price_2 … Type_m:price_m
  其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
  输出:
  对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
  样例输入:
  200.00 3
  2 A:23.50 B:100.00
  1 C:650.00
  3 A:59.99 A:120.00 X:10.00
  1200.00 2
  2 B:600.00 A:400.00
  1 C:200.50
  1200.50 3
  2 B:600.00 A:400.00
  1 C:200.50
  1 A:100.00
  100.00 0
  样例输出:
  123.50
  1000.00
  1200.50
  当时碰到这道题目,第一想法就是用回溯法,于是写下了如下程序:

#include <iostream>#include <vector>#include <string.h>#include <string>#include <stdlib.h>using namespace std;typedef struct Unit{    double A;    double B;    double C;    double sum;}Unit;vector <Unit> ve;double maxsum, Q;void dfs(int k, double sum){    if (k >= ve.size())    {        if (sum>maxsum)        {            maxsum = sum;        }    }    else    {        dfs(k + 1, sum);        if (sum + ve[k].sum <= Q)        {            dfs(k + 1, sum + ve[k].sum);        }    }}int main(){    int i, N, j, num, flag;    double ctmp;    string temp, subtmp;    Unit utmp;    while (cin >> Q >> N, N != 0)    {        ve.clear();        for (i = 0; i<N; i++)        {            flag = 0;            utmp.A = 0;            utmp.B = 0;            utmp.C = 0;            utmp.sum = 0;            cin >> num;            for (j = 0; j<num; j++)            {                cin >> temp;                if (temp[0] == 'A')                {                    subtmp = temp.substr(2);                    ctmp = atof(subtmp.data());                    if (ctmp <= 600)                    {                        utmp.A += ctmp;                        utmp.sum += ctmp;                    }                    else                    {                        flag = 1;                        break;                    }                }                else if (temp[0] == 'B')                {                    subtmp = temp.substr(2);                    ctmp = atof(subtmp.data());                    if (ctmp <= 600)                    {                        utmp.B += ctmp;                        utmp.sum += ctmp;                    }                    else                    {                        flag = 1;                        break;                    }                }                else if (temp[0] == 'C')                {                    subtmp = temp.substr(2);                    ctmp = atof(subtmp.data());                    if (ctmp <= 600)                    {                        utmp.C += ctmp;                        utmp.sum += ctmp;                    }                    else                    {                        flag = 1;                        break;                    }                }                else                {                    flag = 1;                    break;                }            }            if (flag == 0 && utmp.sum <= 1000 && utmp.sum <= Q)            {                ve.push_back(utmp);            }        }        maxsum = 0;        dfs(0, 0);        cout.setf(ios::fixed);        cout.precision(2);        cout << maxsum << endl;    }    system("pause");    return 0;}

  然而提交的时候发现超时了,看来回溯法还是太慢了。后来在网上找答案时发现了这道题的动态规划解法,由于是别人的代码,我这边就不贴了。网址是http://blog.csdn.net/xiexingshishu/article/details/8799669。
  由于当时年轻(其实是笨),没有发现这其实就是个01背包问题,若回溯法时间太长的话可以用动态规划解来减少时间。

0 0
原创粉丝点击