背包问题
来源:互联网 发布:福利软件下载 编辑:程序博客网 时间:2024/06/02 21:18
- 概述
- 问题抽象
- 问题变形
- 一般描述
- 0-1背包问题
- 案例
- 数学符号化
- 状态转移方程
- 根据状态转移方程 进行编程
- 案例
- 适用于贪心算法的背包问题
- 部分背包问题
- 案例
- 分析 及 解答
- 为何适合使用贪心算法
- 形式化表示
- 核心代码实现
概述
背包问题(Knapsack problem)是一种组合优化问题。
背包问题十分经典,是因为很多实际的问题、典型的问题,都可以通过背包问题通俗的表达出来。也就是说,很多实际问题中你都能看到背包问题的影子。(背包问题,可以看做一类问题的模型)
问题抽象
在组合优化问题中,在有限的约束条件中寻找最优的目标。
在背包问题中的约束条件:给定物品的特点、给定背包的限制。
背包问题的目标:最小的物品的数量、最大化物品的价值等等。
问题变形
前面我们把背包问题进行了切分,从大的角度来说,我们将背包问题切分成了约束条件和目标。而将约束条件和目标进行改变时,变可以得到很多背包问题的变形。
比如:
【约束条件】
给定物品的特点:
可拆分的 & 不可拆分的
等等
宝贝的特点:
有限的 & 无限的
等等【目标】
背包装最多的重量。
背包装最多的价值。
等等【多样的问题变形】
然后我们发现,把上面的约束条件与目标组合后,能够产生大量的背包问题的变形。(这也说明为何背包问题如此经典)
一般描述
问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
需要说明:
- 【一类问题】上面的一般表述,是较为抽象的,因为要概括这一类问题。为直观理解所以就抽象到该层次,如果继续抽象的话,“重量”和“价格”也可以抽象为一类事物,即“物品的属性”。
- 【目标:最优化】我们有一个目标(最大化或最小化),根据现有资源去决策,进而达到我们想要实现的目的。(需要说明的是,可行解可能有很多,但最优解一般是少数的,所以我们的目标可以表述为:在可行解空间中,找到最优解)
- 【问题具体化】因为上面关于背包问题的描述是一般问题,因此我们通过添加一些限制条件,使得背包问题具体化。(后面问题需要和问题一般描述相联系,更易系统理解)
0-1背包问题
在一般描述的基础上,添加限制条件:
- 【唯一性】每个物品具有唯一性。
【放入 & 不放入】每个物品或者整体放入背包中(0表示),或者不放入不放入背包中(1表示)。不存在只放入部分的情况(0< x < 1)。
(PS : 如果将物品换成几袋大米,再进行分配时,就打破了0,1的限制,因为大米是可以放进部分的)
案例
有编号为a、b、c、d、e的5件物品,他们的重量分别是2、2、6、5、4,他们的价值分别是6、3、5、4、6,现在给你一个承重为10的背包,如何让背包里装的物品拥有最大的价值??
数学符号化
n 代表东西的数量、c 代表包的最大承重、Wi 是第i件物品的重量、Pi 是第i件物品的价值、F[i,j] 是最大价值(i个物品放入承重j的包)。
说明:
- n , c , Wi , Pi 构成限制条件(约束)。
- F[i, j] 中则记录在个阶段取得最大值。(i , j 表示状态,i表示物品的状态,j表示背包的状态)
状态转移方程
说明:
- 假设
F[i][j] 已经是最大的了。 - 如果要求F[i,j]的最大值,则要看这两种 状态:
- 第一种情况(不带):
F[i−1,j] - 第二种情况(带):
F[i−1,j−Wi]+Pi(j>=Wi) (在背包中给第i个物品预留空间Wi,剩下的i-1件物品重新摆放,最后在加上第i个物品的价值Pi)
- 第一种情况(不带):
根据状态转移方程 进行编程
根据状态转移方程,我们很容易写出递归的解法。
方法1:(递归 + 未使用数组记忆)
Int rec( int i , int j){ int res; if(i==-1){ res=0; //表明物品已经放完了。 }else if(j<w[i]){ res=rec(i-1,j);//此时包内的空间不够放下第i个物品。 }else{ // 进行比较,如果放入物品的价值比原来已经放入物品的总价值高,则替换原来的 res=max(rec(i-1,j),rec(i-1,j-w[i])+v[i]); } dp[i][j]=res; // TODO 可通过该数组记忆,空间换时间 return dp[i][j]; }
说明:(针对关键算法)
- 【为何可使用一维数组?】将记录第i个物品的情况通过内循环的方式进行了替换。
- 【外层循环】外层一次循环表示的是,考虑第 i 个物品后的,更新过去的未考虑i的情况(针对 j >= w[i] 的情况,内层循环)
- 【为何内层循环倒序?】外层的循环,第 i 次循环依赖于 第 i-1 次循环的结果,而不应该 第 i 次 循环更新之后的结果,否则会出现重复加第i个物品的要求,这就不符合了0-1背包的问题。
- 举个简单的例子,
dp[j]=max(dp[j−w[i]]+val[i],dp[j]) ,假如dp[j−w[i]]+val[i]>dp[i] (表示需要更新上一次循环造成的影响,替换上一次的结果,而这一次则将价值未val[i]的第i个物品加入了这一种情况 )。但是存在另一个问题,在dp[j]=dp[j−w[i]]+val[i] 中你不能确定是否dp[j−w[i]] 在这之前是否已经加入了第i个物品(如果这个dp[j - w[i]]先发生,那么可能就已经加入第i个物品)。 - 所以针对上面的问题,通过逆序的方式(背包容量大的情况依赖于 背包容量小的情况,反之,则小不会依赖大,这个特点使得我们通过逆序的方式只加入一次)
- 举个简单的例子,
参考算法来自:背包问题之01背包
方法2:(一维滚动数组)
#include <iostream> using namespace std; int w[5], val[5]; int dp[10]; //表示背包容量为i时的最大值,初始化为0. int main() { int t, m, res=-1; cin >> t >> m; for(int i=1; i<=m; i++) { cin >> w[i] >> val[i]; } //关键算法(滚动数组) for(int i=1; i<=m; i++) //物品 for(int j=t; j>=0; j--) //容量,逆序 { if(j >= w[i]) dp[j] = max(dp[j-w[i]]+val[i], dp[j]); } cout << dp[t] << endl; return 0; }
参考:
01背包问题-通俗易懂
【PPT】0-1背包问题之动态规划法
适用于贪心算法的背包问题
部分背包问题
与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包。
案例
【更改前一个0-1背包问题】有编号为a、b、c、d、e的5袋大米,他们的重量分别是2、2、6、5、4,他们的价值分别是6、3、5、4、6,现在给你一个承重为10的背包,如何让背包里装的物品拥有最大的价值??
(每袋大米可部分装入背包之中)
分析 及 解答
为何适合使用贪心算法?
背包的总容量是有限的(为10),假如说最后一定能够把背包装满,那么我们 可以得到如下公式:
形式化表示
n 代表东西的数量、c 代表包的最大承重、(将物品按照 平均价值(W/P、)从大到小排序,第i个物品表示是平均价值第i大的),Wi 是第i件物品的重量、Pi 是第i件物品的价值、F(i) 是最大价值(前i个物品放入包中)。res(i)表示剩余的背包空间。
在此基础上可形式化为:
核心代码实现
//w[5],p[5]已经排好了序,下标越小,平均价值值越大。(5表示物体数量)//res表示背包剩余的最大承重。(初始为10)double results = 0for(int i = 0;i < 5 ; i++){if(w[i] <= res){ results += p[i]; }else{ results += p[i] / w[i] * res } //results即为所求}
更新中…
- 【无限背包】背包问题
- 背包问题---01背包
- 背包问题--部分背包
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- 背包问题
- jzoj 1277. 最高的奶牛
- POJ2431-Expedition
- 为什么文件属性中的“访问时间”和“修改时间”不一致?
- C#技巧:判断系统是否已经联网
- Java常用类Properties简单用法
- 背包问题
- 链表
- 初始三层
- Java基础---函数
- ArcGIS水文分析实战教程(9)雨量计算与流量统计
- 初试swift语言ios开发
- html的基础知识(章节一)
- Reverse String I、II、III(tag:String)
- Chapter1 面试流程