从来就没有救世主  也不靠神仙皇帝  要创造人类的幸福  全靠我们自己  

动态规划---背包

 

1.   0-1背包问题

  有一个容量为V的背包,外面有N件物品。放入第i件物品消耗空间Ci,得到价值Wi,如何装入可使价值总和最大?

  每种物品仅有一件

(1)

  定义一个N+1行、V+1列的数组arr:arr[i][j]表示前i件物品放到容量为j的背包里面所能获得的最大价值

  

 

 

  对第i件物品而言,有放或不放两种选择:(1) j<ci ,表示当前空间不够放第i件物品,则当前最大价值就等同于放前 i-1 件物品

                          (2)当前空间是足以放下第i件物品的:①不放第i件物品,则等同于放前i-1件物品的最大价值

                                      ②放第i件物品,则给第i件物品留下足够空间后,剩下空间给前i-1件物品。

  假设有5件物品,背包容量10,物品所占空间为:2 5 4 2 3  价值依次为:6 3 5 4 6

  依照上面的公式填表:有逗号的地方表示要进行决策,是放当前物品还是不放(前面的更大,表示不放当前物品;后面的更大,表示放当前物品)

    

 

  表填完后,最右下角的数arr[N][V]表示N件物品放到容量为V的背包里能获得的最大价值 

  

  重构解:两种方式

  (1)在做决策时记录,如果放当前物品,则记下其位置

  (2)根据arr数组:从最右下角开始,以此为例

      arr[5][10] > arr[4][10],说明第5件物品放了,还剩下空间 10-3==7

      arr[4][7]==arr[3][7],说明第4件物品没放  还剩下空间7

      arr[3][7]>arr[2][7],说明第3件物品放了  还剩下空间 7-4==3

      arr[2][3]==arr[1][3] ,说明第2件物品没放 还剩下空间3

      arr[1][3]>arr[0][3],说明第1件物品放了  还剩下空间3-2==1

      所以放了:第1、第3、第5件物品,且背包还剩下1空间

 

  main.cpp

int c[5] = {2,5,4,2,3};
int w[5] = {6,3,5,4,6};
int maxValue = maxValue_01pack(c,w,5,10);

  背包算法部分:

/*
c:各物品所占空间
w:各单个物品价值
n:物品数量
V:背包容量
*/
int maxValue_01pack(int c[],int w[],int N,int V)
{
    int **arr = getMNMatrix<int>(N+1,V+1);
    if(nullptr == arr) {
        return -1;
    }
    int maxValue = 0;
    //arr矩阵填值
    for(int i=0;i<=V;i++) {
        arr[0][i] = 0;
    }
    for(int i=0;i<=N;i++) {
        arr[i][0] = 0;
    }
    for(int i=1;i<=N;i++) {
        for(int j=1;j<=V;j++) {//j代表当前背包容量
            if(j<c[i-1]) {
                arr[i][j] = arr[i-1][j];
            }
            else {
                arr[i][j] = getMax(arr[i-1][j],arr[i-1][j-c[i-1]]+w[i-1]);
            }
        }
    }

  //重构解,由于0-1背包特殊,因此可在arr数组上直接重构解 stack
<int> tmp; for(int i=N,j=V;i>0;i--) { if(arr[i][j]>arr[i-1][j]) { tmp.push(i); j -= c[i-1]; } } while(!tmp.empty()) { cout<<tmp.top()<<" "; tmp.pop(); } cout <<endl; maxValue = arr[N][V]; freeMatrix(arr,N+1,V+1); return maxValue; }

 

  

 

2. 完全背包问题

  有N种物品,一个容量为V的背包,每种物品有无限件可用。

  放入第i种物品占ci,价值wi

 

  分析:

    每种物品可选0件、1件..... [V/ci]件

    用arr[i][j]表示前i种物品放入容量为j的背包时的最大权值,则有方程:

    

      0-1背包的递推式与此不同的是,k只取0和1

     类似0-1背包填表,但对每一个位置时间不是常数级别,而是O(j/ci),则总的时间复杂度为O(N*V*ΣV/ci)

    填完表后,最右下角的数就是最大值,关键在于如何重构解:用一个N行V列数组存储第i件物品在当前体积下所取的k值,即是第i件物品的个数(0-1背包由于k只取0或1的原因,可直接通过arr数组推导各物品的个数,因此不用

  再建立一个N*V的求解数组了)

 

  例子:5件物品,背包体积10,各物品占体积如下c,价值如w

int c[5] = {2,5,4,2,3};
int w[5] = {6,3,5,4,10};

int maxValue = maxValue_pack(c,w,5,10);

  具体的求解及重构解的函数如下:

int maxValue_pack(int c[],int w[],int N,int V)
{
    vector<vector<int>> d(N+1,vector<int>(V+1,0));//N+1行 V+1列
    vector<vector<int>> num(N,vector<int>(V,0));//用于重构解
    int i,j,k;
    int num_i = 0;//第i件物品在各种v的情况下的个数,i从1开始
    for( i=1;i<=N;i++) {
        num_i = 0;
        for( j=1;j<=V;j++) {
            for( k=0;k*c[i-1]<=j;k++) {
                if(d[i][j]<d[i-1][j-k*c[i-1]]+k*w[i-1]) {
                    d[i][j] = d[i-1][j-k*c[i-1]]+k*w[i-1];
                    num_i = k;
                }
            }
            num[i-1][j-1] = num_i;
        }
    }

    //重构解
    stack<pair<int,int>> solution;
    for(int i=N,j=V;(i>=0)&&(j>0);i--) {  //j>0条件是必要的,比如全部选择最后一个物品,空间也用完了,再去找前面的物品时,会有 num[i-1][0-1]越界错误
        if(num[i-1][j-1] != 0) {
            solution.push({i,num[i-1][j-1]});
            j -= (num[i-1][j-1]*c[i-1]);
        }

    }
    while(!solution.empty()) {
        cout<<solution.top().first<<"号:"<<solution.top().second<<""<<endl;
        solution.pop();
    }

    return d[N][V];
}

 

 

  调试输出:

  

 

 

  d数组内容如下:

  

 

   num数组的内容如下:

  

 

 

3. 多重背包问题

 

4. 混合三种背包问题

 

5.二维费用的背包问题

 

6. 分组的背包问题

  问题:

    N件物品,容量为V的背包

    第i件物品费用ci,价值wi

    物品划分为K组,每组中物品相互冲突,最多选一件

    求解:价值总和=物品数量*物品价值,累加

  分析:

    完全背包问题是每件物品有若干选择策略,而分组背包是每组物品有若干策略(是选择本组的一件,或者本组一件都不选)

    F[k,v]表示前k组物品花费空间v所能取得的最大权值

    F[k,v] = max{F[k-1,v],F[k-1,v-ci]+wi | i∈组k}  

    对比于0-1背包,里面的一个物品,在这里是一组物品

  实现:

    一个  K*V 的d数组,存储最大权值计算结果;一个 K*V 的so数组,存储各组在当前容量下物品的下标,如果当前组不选,则该值为-1

  

/*
a:各物品属性信息
N:物品数量
V:背包容量
K:分组数
num:各组物品件数,例题中为 0 2 3 2
*/
int maxValue_array_pack(vector<goods> &a,int N,int V,int K,int num[])
{
    //2个K*V的二维数组,一个为求解用, 一个用来重构解
    vector<vector<int>> d(K+1,vector<int>(V+1,0));
    vector<vector<int>> so(K,vector<int>(V,-1));
    int i_in_k = 0;
    int i_index = 0;
    for(int k=1;k<=K;k++) {

        for(int v=1;v<=V;v++) {
            i_in_k = -1;//表示当前组不选,如果选一个,则此变量值为该物品在a数组中的下标
            d[k][v] = d[k-1][v];
            for(int i = i_index;i<i_index+num[k];i++) { //遍历第k组里面的所有物品
                if(v>=a[i].c) {
                    if (d[k][v] < d[k - 1][v - a[i].c] + a[i].w) {
                        d[k][v] = d[k - 1][v - a[i].c] + a[i].w;
                        i_in_k = i;
                    }
                }
            }
            so[k-1][v-1] = i_in_k; //当前组在各种容量下的解决方案出来了
        }
        i_index += num[k];
    }
    cout<<"最大权值:"<<d[K][V]<<endl;
    //重构解
    stack<int> solution;
    for(int i=K,j=V;(i>=0)&&(j>0);i--) {
        if(so[i-1][j-1] != -1) {
            solution.push(so[i-1][j-1]);
            j -= (a[so[i-1][j-1]].c);
        }
    }
    ostringstream cost;
    ostringstream value;
    while(!solution.empty()) {
        cout<<""<<solution.top()+1<<"件物品"<<endl;
        if(solution.size()==1) {
            cost<<a[solution.top()].c;
            value<<a[solution.top()].w;
        }
        else {
            cost<<a[solution.top()].c<<"+";
            value<<a[solution.top()].w<<"+";
        }
        solution.pop();
    }
    cout<<"空间消耗:"<<cost.str()<<endl;
    cout<<"权值组成:"<<value.str()<<endl;
    return d[K][V];
}

 

  调用上面函数的入参和初始化:

typedef struct goods {
    int c; //消耗
    int w;//收益、价值、重要度等
    int flag;//区分组、主附件等
}goods;
vector<goods> a;
int N,V;
initGoods(a,N,V);
int num[4] = {0,2,3,2};   //第1分组有2个物品,第2分组有3个物品,第3分组有2个物品
maxValue_array_pack(a,N,V,3,num); //这个分组数为3,暂时写死,包括上面的num数组都可以通过goods数组里的数据统计而来,暂时不管了

 

void initGoods(vector<goods> &a,int &N,int &V)
{
    cout<<"输入物品总数量N:";cin>>N;
    cout<<"输入背包容量V:"; cin>>V;
    for(int i=0;i<N;i++) {
        goods x;
        cout<<"输入第"<<i+1<<"件物品的消耗c:";cin>>x.c;
        cout<<"输入第"<<i+1<<"件物品的价值w:";cin>>x.w;
        cout<<"输入第"<<i+1<<"件物品的标志flag:";cin>>x.flag;
        a.push_back(x);
    }
    cout<<"信息录入完成."<<endl;
}

 

  调试用物品信息:

  

  d数组数据:

  

 

  so数组数据:

  

 

 

 

 

  

7. 有依赖的背包问题   

  物品间存在依赖关系,物品a依赖于物品b,如果要选物品a的话,则必须先选b(b为主件,a为主件a的附件)

  现有多个物品,其中有主件也有附件,一个主件会有多个附件,但只能选择0个附件、1个附件、2个附件....等这些方案中的一个。

  对于某一个主件,可用策略如:此主件不选、仅有主件、主件加1个附件、主件加2个附件....【每件物品只选一个,不是完全背包的选无限个,如1主件只能买1个、1号附件只能买1个...】

 

  由于主件和附件选择互斥,所以可将每一种策略视作一个组,在这个组内选一个方案【这个组内方案是很多的,设主件A有n个可选附件,则方案有:

    再加上不选主件的情况,有2n+1种可行方案】

 

 

  例题:金明的预算方案

    有V元钱,N件物品,第i件物品价格/消耗为ci,重要度/价值为wi 。每个主件最多2个附件。每件物品最多买一件。

    目标:价格*重要度的累加和最大

    输入:V钱数;N物品数;各物品价格 、重要度、主还是附件

    

   

 

---------------------------------------------------------------------------------------------------------------------------

8. 要求背包全部用完和不要求全部用完的区别

 

9. 时间复杂度的优化

 

10. 空间复杂度的优化

    

 

posted @ 2020-04-10 21:35  T,X  阅读(185)  评论(0编辑  收藏  举报