栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

背包问题(普通、完全、多重)的思路分析和代码模板

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

背包问题(普通、完全、多重)的思路分析和代码模板

一、普通背包问题 问题描述:

给定 n 件物品,物品的重量和价值分别为w[i]和v[i]。现挑选物品放入背包中,假定背包能承受的最大重量为 m,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

即只需要决定放不放这个物品,不涉及放多少的问题(物品只有一个)。

直接想法:

暴力枚举(时间复杂度O(2ⁿ))

即排列组合, 枚举每一个物品是否在背包里

改进想法:

剪枝法(1.跳过超重的部分 2.相同重量且有更优解时,及时剪枝)

在将上述爆搜方法的缺点克服后, 我们已经窥得动态规划的一点端倪。或者说,动态规划本质上是一种优化的爆搜

进阶想法:

动态规划

即f数组是一个集合, 其存储的值是集合中唯一有意义的情况,时间复杂度O(nm),明显优于暴力

关于初始化,因为后面有剪枝,而且下一排需要用到它的值,这样是为了使每个f [i] [c]都能有正确的值

for(int i=0;ic) continue;  //剪枝去超重情况
        f[i][c]=max(f[i][c],f[i-1][c-w[i]]+v[i]);  //取拿和不拿中的最大值
    }
}

一维数组优化

有的朋友就问了,这里为什么没有初始化了? 因为这是一个一维数组,每排用到的都是当前所在的值

至于内循环顺序为什么变了,因为每个物品只有一个,如果先排前面的,就有可能出现放两次的可能

for(int i=1;i<=n;i++)
{
    for(int c=m;c>=0;c--)
    {
        if(w[i]>c) continue;
        f[c]=max(f[c],f[c-w[i]]+v[i]);
    }
}

更进一步的常数优化:

改后的内层循环只循环到最小的会改变的重量的地方,避免了部分无效循环

for(int i=1;i<=n;i++)
{
    sumw+=w[i];
    bound=max(m-sumw,w[i]);
    for(int c=m;c>=bound;c--)
    {
        f[c]=max(f[c],f[c-w[i]]+v[i]);
    }
}

二、完全背包问题 问题描述:

给定 n 件物品,物品的重量和价值分别为w[i]和v[i],每一件物品可以取无数个。现挑选物品放入背包中,假定背包能承受的最大重量为 m,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

即需要决定放不放这个物品,如果放要放多少个的问题(物品有无数个)。

直接想法:

直接在普通背包动态规划的基础上对每个物品,枚举其可能的个数(时间复杂度O(nmk))(k=m/w[i])

对于一些比较特殊的样例,可以将这个时间复杂度卡得很高

for(int i=0;i 
改进想法: 

二进制优化(将每次循环拿的件数从增加一件改成增加2的k次幂件直至放不下)

for(int i=1;i<=n;i++)
{
    for(int a=1;a*2=w[i];c--)
  		{
        	f[c]=max(f[c],f[c-a*w[i]]+a*v[i]);
    	}
    }
}
进阶想法:

一维数组优化:

其实不难发现,以下代码与普通背包一维数组优化只有内循环顺序有差别,因为这次需要包括放多个的情况

for(int i=1;i<=n;i++)
{
    for(int c=0;c<=m;c++)
    {
        if(w[i]>c) continue;
        f[c]=max(f[c],f[c-w[i]]+v[i]);
    }
}

三、多重背包问题 问题描述:

给定 n 件物品,物品的重量和价值分别为w[i]和v[i],每一件物品对应的数量有u[i]个。现挑选物品放入背包中,假定背包能承受的最大重量为 m,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

即需要决定放不放这个物品,如果放要放多少个的问题(物品有u[i]个)。

直接想法:

直接在普通背包动态规划的基础上对每个物品, 枚举其可能的个数(时间复杂度O(nmk))

对于一些比较特殊的样例,可以将这个时间复杂度卡得很高

for(int i=0;ic) continue;
            f[i][c]=max(f[i][c],f[i-1][c-k*w[i]]+k*v[i]);  //取拿不同件数中的最大值
        }
    }
}
改进想法:

二进制优化(将每次循环拿的件数从增加一件改成增加2的k次幂件直至放不下或者不够,将剩余的再循环一次)

for(int i=1;i<=n;i++)
{
    for(int a=1;a*2=a*w[i];c--)
  		{
        	f[c]=max(f[c],f[c-a*w[i]]+a*v[i]);
    	}
    }
    if(a*2<=m)  //如果放得下,就放剩余的
    {
        for(int c=m;c>=(m-a+1)*w[i];c--)  //(m-a+1)是剩余的数量
  		{
        	f[c]=max(f[c],f[c-(m-a+1)*w[i]]+(m-a+1)*v[i]);
    	}
    }
}
进阶想法:

单调队列优化

由于理解难度较高,以下只给出模板,具体原理可以自己去参考https://github.com/tianyicui/pack(背包九讲),原理讲的特别清晰!!!

for(int i=1;i<=n;i++)
{
    if(w[i]*a[i]>m)
    {
        for(int c=0;c<=m;c++)
        {
        if(c>=w[i])
        f[c]=max(f[c],f[c-w[i]]+v[i]);
        }
    }
    else
    {
         k=1;amount=a[i];
         while(k=0;c--)
             {
                 if(c>=w[i])
                 f[c]=max(f[c],f[c-w[i]]+k*v[i]);
             }
             amount-=k;
             k<<=1;
         }  
         for(int c=amount*w[i];c>=0;c--)
         {
             f[c]=max(f[c],f[c-w[i]]+amount*v[i]);
         }
    } 
}

by  SZU  jt  2022-05-19

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/917170.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号