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

  这里讲两道题目(类型均是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;   //总共能放的重量为110
int 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背包问题,若回溯法时间太长的话可以用动态规划解来减少时间。

你可能感兴趣的:(动态规划,回溯法,01背包)