声明
作者非职业程序员,本文仅在分享自己的心得,错误较多欢迎指出,各位大佬不喜勿喷
本文采用和作者一样的小白一理解的的Pascal语言
这篇博客同时为了我的一位不懂代码但对算法十分感兴趣的朋友而写,故没有放一些“高级”元素
写得有些草率哈,请谅解:P
本文为介绍贪心算法,重点讲述部分背包问题。
本文参考背包问题详解(含代码)_林一百二十八的博客-CSDN博客
一、题目描述
1.01背包
有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的空间是 Ci,得到的价值是 Wi。求解将哪些物品装入背包可使价值总和最大。
特点:每种物品仅有一个,可以选择放或者不放。也就是0(不放入)和1(放入)这两种状态
2.部分背包
有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的空间是 Ci,得到的价值是 Wi,物品可以分割。求解将哪些物品装入背包可使价值总和最大。
特点:物品可以分割
3.完全背包
有N种物品和一个容量为V 的背包,每种物品都有无限件可用。放入第i种物品的费用是Ci,价值是Wi。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。
特点:每一种物品都可以无限使用
4.多重背包
有N种物品和一个容量为V的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
特点:每一种物品的数量不同
5.其它背包……
分组背包、关联背包……还有那些长得不像背包问题的背包问题……
这是最基本的背包问题,每个物品最多只能放一次。
第二个基本的背包问题模型,每种物品可以放无限多次。
每种物品有一个固定的次数上限。
将前面三种简单的问题叠加成较复杂的问题。
一个简单的常见扩展。
一种题目类型,也是一个有用的模型。后两节的基础。
另一种给物品的选取加上限制的方法。
我自己关于背包问题的思考成果,有一点抽象。
试图触类旁通、举一反三。
------------------------------
转载自
二、题目分析
1.部分背包&贪心算法
从特点入手,物品可以分割,意味着我们不必因为填不满背包而苦恼。在分割过程中,去抓住不变的量,比如——性价比,即Wi/Ci,表示单位重量的价值。那么如何去放呢?既然不用担心空间浪费或安排不合理,那就直接把所有物品按性价比从大到小排序,依次装入背包,到装不下时,就分割当前物品,把剩余空间填满。
①.贪心算法
这个思路中,我们确保每次装进去的都是目前剩下性价比最高的物品,所以总的价值也就最大了。这就是贪心算法。贪心算法中,正如其名,不考虑以后可能的分支,而是专注于当前子问题的最优解。我就要最好的,管它有没有更好的,就这么简单。
②.活学活用
有这么一个最优装载问题:有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的空间是 Ci,每件物品等价。求解将哪些物品装入背包可使价值总和最大。(不可分割)
这里物品等价,也就是说,Wi/Ci=1/Ci,故物品越小,性价比越划算,因此,我们可以得到如下的贪心思路:将物品按空间从小到大排序,从小的优先开始装,装到剩余空间不够了为止。
2.01背包
接下来咱按顺序过一遍。
对于无法分割的情况,目光短浅的贪心算法不再适用,穷举计算的暴力算法则会导致时间超限,那么如何优化呢?无穷枚举是一种搜索,优化搜索可以通过保留历史最优解的方式——嗯?动态规划!
这里的动态转移方程为:
dp[i,v]=max(dp[i,v],dp[i−1,v−Ci]+Wi)
翻译一下:dp[i,v]储存将前i件物品放入容量为v的背包中这个子问题。dp[i,v]的初始值为dp[i-1,v],即前i-1个物品选择后的最大值。在面临对第i个物品的选择、当前背包容量为v时,取 1.不选这个物品,当前最大价值即为上一次判断之后的最大价值和 2.选择这个物品,当前价值即为上一次选择之后,剩余容量距离现在需要剩余容量恰为Ci时的价值加上这个物品的价值这两种情况下的最大值。
最后输出dp[N,V]即可。不难得出,只要物品信息确定,通过这个方程可以得出所有V的情况下的答案,最后按需选择V输出。
3.完全背包&多重背包
我这里只提供一种思路,其余思路参看大神文章(链接在开头)
一切问题转化为数学问题,一切数学问题转化为代数问题,一切代数问题转化为方程问题——笛卡尔
一切背包问题(除部分背包)转化为01背包问题,一切01背包问题转化为动态规划问题——MPCBexplorer
对于完全背包,不妨看作装满容量为j的背包,重量为wi的物品顶多V/ci件的01背包,即数量k<=j/wi。通过物品选择循环和重量遍历循环的基础上遍历k以达到目的
dp[i][j] = max(dp[i][j],dp[i-1][j-kci] + kwi), 0<=k<=j/ci
对于多重背包,大佬的两种思路,一种直接化为01背包,还有一种通过完全背包间接转化为01背包。我个人认为后者更易于理解。
完全背包,不就是有数量的01背包么?虽说数量无线,但一定有0<=k<=j/wi,k实际上是有范围的;多重背包也一样,不过有两种情况:1.当剩余空间足够,0<=k<=mi;2.当物品足够,0<=k<=j/wi——你看,无非多了个足够空间下取物上限的条件而已!在程序中具体决定哪一个为上限时,取
Ki=min(num[i],j/ci)
即可,然后就是上面完全背包的那一套思路:dp[i][j] = max(dp[i][j],dp[i-1][j-kci] + kwi)
三、上代码
1.部分背包
program part_bag;
var
n,i:longint;
v,ans:real;
a,c,w:array[1..10000] of real;
//性价比排序
//系统文件中盗个快排改一下
//快排:基于二分查找法的一种排序
procedure sort(l,r: longint);
var
i,j: longint;
x,y:real;
begin
i:=l;
j:=r;
x:=a[(l+r) div 2];
repeat
while a[i]<x do
inc(i);
while x<a[j] do
dec(j);
if not(i>j) then
begin
y:=a[i];
a[i]:=a[j];
a[j]:=y;
y:=c[i];
c[i]:=c[j];
c[j]:=y;
y:=w[i];
w[i]:=w[j];
w[j]:=y;
inc(i);
j:=j-1;
end;
until i>j;
if l<j then
sort(l,j);
if i<r then
sort(i,r);
end;
begin
//数量,大小
read(n,v);
for i:=1 to n do
begin
//大小,价值
read(c[i],w[i]);
//性价比
a[i]:=w[i]/c[i];
end;
sort(1,n);
//DEBUG调试: 输出排序后性价比
//for i:=1 to n do writeln(a[i]:0:2);
i:=n;
//空间足够时
while v>=c[i] do
begin
//贪心:全部装入
ans:=ans+w[i];
v:=v-c[i];
dec(i);
end;
//分割最后一个物品
ans:=ans+w[i]*(v/c[i]);
writeln(ans:0:2);
end.
输入 #1
4 50↙
10 60↙
20 100↙
30 120↙
15 45↙
输出 #1
240.00
2.01背包
program o1_bag;
var
n,v,i,j,best:longint;
c,w:array[1..10000] of longint;
//注意下标从0开始
dp:array[0..10000,0..10000] of longint;
//取最大值的函数
function max(a,b:longint):longint;
begin
if a>b then exit(a)
else exit(b);
end;
begin
read(n,v);
for i:=1 to n do read(c[i],w[i]);
//i:在前i个物品中
for i:=1 to n do
//j:当剩余容量为j时
for j:=0 to v do
begin
//无论剩余空间够不够,至少有
dp[i,j]:=dp[i-1,j];
//当剩余空间足够时
if j>=c[i] then dp[i,j]:=max(dp[i-1,j-c[i]]+w[i],dp[i,j]);
end;
best:=0;
//找出最大值输出
for j:=1 to v do
if dp[n,j]>best then best:=dp[n,j];
writeln(best);
end.
输入 #1
4 5
↙
1 2↙
2 4↙
3 4↙
4 5↙
输出 #1
8
3.完全背包
program completed_bag;
var
n,v,i,j,k,best:longint;
c,w:array[1..10000] of longint;
//注意下标从0开始
dp:array[0..10000,0..10000] of longint;
//取最大值的函数
function max(a,b:longint):longint;
begin
if a>b then exit(a)
else exit(b);
end;
begin
read(n,v);
for i:=1 to n do read(c[i],w[i]);
//i:在前i个物品中
for i:=1 to n do
//j:当剩余容量为j时
for j:=0 to v do
begin
//无论剩余空间够不够,至少有
dp[i,j]:=dp[i-1,j];
for k:=0 to j div c[i] do
//当剩余空间足够时
if j>=k*c[i] then dp[i,j]:=max(dp[i-1,j-k*c[i]]+k*w[i],dp[i,j]);
end;
best:=0;
//找出最大值输出
for j:=1 to v do
if dp[n,j]>best then best:=dp[n,j];
writeln(best);
end.
输入 #1
4 5
↙
1 2↙
2 4↙
3 4↙
4 5↙
输出 #1
10
4.多重背包
program numbers_of_bags;
var
n,v,i,j,k,best:longint;
c,w,m:array[1..10000] of longint;
//注意下标从0开始
dp:array[0..10000,0..10000] of longint;
//取最大值的函数
function max(a,b:longint):longint;
begin
if a>b then exit(a)
else exit(b);
end;
//取最小值的函数
function min(a,b:longint):longint;
begin
if a<b then exit(a)
else exit(b);
end;
begin
read(n,v);
for i:=1 to n do read(c[i],w[i],m[i]);
//i:在前i个物品中
for i:=1 to n do
//j:当剩余容量为j时
for j:=0 to v do
begin
//无论剩余空间够不够,至少有
dp[i,j]:=dp[i-1,j];
for k:=0 to min(m[i],j div c[i]) do
//当剩余空间足够时
if j>=k*c[i] then dp[i,j]:=max(dp[i-1,j-k*c[i]]+k*w[i],dp[i,j]);
end;
best:=0;
//找出最大值输出
for j:=1 to v do
if dp[n,j]>best then best:=dp[n,j];
writeln(best);
end.
输入 #1
3 7↙
2 3 12↙
2 5 15↙
1 2 3↙
输出 #1
17
四、结语
从背包问题的角度去讲解贪心算法,没想到主角竟然是动态规划。贪心是入门级的算法,应该掌握;不过以后出镜率不高,毕竟动态规划才是王道,贪心总是容易漏掉最优解。
诗云:
满屏荒唐言,一把辛酸泪。
都云Coder痴,谁解其中恨!
代码中若有BUG,恳请指出,谢谢!
累死偶咧!23点31分完初稿!