贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
思想
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止
过程
-
建立数学模型来描述问题;
-
把求解的问题分成若干个子问题;
-
对每一子问题求解,得到子问题的局部最优解;
-
把子问题的解局部最优解合成原来解问题的一个解。
假设山洞中有 n 种宝物,每种宝物有一定重量 w 和相应的价值 v,毛驴运载能力有限,
只能运走 m 重量的宝物,一种宝物只能拿一样,宝物可以分割。那么怎么才能使毛驴运走宝
物的价值最大呢?
我们可以尝试贪心策略:
(1)每次挑选价值最大的宝物装入背包,得到的结果是否最优?
(2)每次挑选重量最小的宝物装入,能否得到最优解?
(3)每次选取单位重量价值最大的宝物,能否使价值最高?
思考一下,如果选价值最大的宝物,但重量非常大,也是不行的,因为运载能力是有限
的,所以第 1 种策略舍弃;如果选重量最小的物品装入,那么其价值不一定高,所以不能在
总重限制的情况下保证价值最大,第 2 种策略舍弃;而第 3 种是每次选取单位重量价值最大
的宝物,也就是说每次选择性价比(价值/重量)最高的宝物,如果可以达到运载重量 m,
那么一定能得到价值最大。
因此采用第 3 种贪心策略,每次从剩下的宝物中选择性价比最高的宝物。
算法设计
(1)数据结构及初始化。将 n 种宝物的重量和价值存储在结构体 three(包含重量、价
值、性价比 3 个成员)中,同时求出每种宝物的性价比也存储在对应的结构体 three 中,将
其按照性价比从高到低排序。采用 sum 来存储毛驴能够运走的最大价值,初始化为 0。
(2)根据贪心策略,按照性价比从大到小选取宝物,直到达到毛驴的运载能力。每次选
择性价比高的物品,判断是否小于 m(毛驴运载能力),如果小于 m,则放入,sum(已放入
物品的价值)加上当前宝物的价值,m 减去放入宝物的重量;如果不小于 m,则取该宝物的
一部分 m * p[i],m=0,程序结束。m 减少到 0,则 sum 得到最大值。
完美图解
假设现在有一批宝物,价值和重量如表 2-3 所示,毛驴运载能力 m=30,那么怎么装入
最大价值的物品?
表 2-3 宝物清单
宝物 i 1 2 3 4 5 6 7 8 9 10
重量 w[i] 4 2 9 5 5 8 5 4 5 5
价值 v[i] 3 8 18 6 8 20 5 6 7 15
(1)因为贪心策略是每次选择性价比(价值/重量)高的宝物,可以按照性价比降序排
序,排序后如表 2-4 所示。
表 2-4 排序后宝物清单
宝物 i 2 10 6 3 5 8 9 4 7 1
重量 w[i] 2 5 8 9 5 4 5 5 5 4
价值 v[i] 8 15 20 18 8 6 7 6 5 3
性价比 p[i] 4 3 2.5 2 1.6 1.5 1.4 1.2 1 0.75
(2)按照贪心策略,每次选择性价比高的宝物放入:
第 1 次选择宝物 2,剩余容量 30−2=28,目前装入最大价值为 8。
第 2 次选择宝物 10,剩余容量 28−5=23,目前装入最大价值为 8+15=23。
第 3 次选择宝物 6,剩余容量 23−8=15,目前装入最大价值为 23+20=43。
第 4 次选择宝物 3,剩余容量 15−9=6,目前装入最大价值为 43+18=61。
第 5 次选择宝物 5,剩余容量 6−5=1,目前装入最大价值为 61+8=69。
第 6 次选择宝物 8,发现上次处理完时剩余容量为 1,而 8 号宝物重量为 4,无法全部
放入,那么可以采用部分装入的形式,装入 1 个重量单位,因为 8 号宝物的单位重量价值为
1.5,因此放入价值 1×1.5=1.5,你也可以认为装入了 8 号宝物的 1/4,目前装入最大价值为
69+1.5=70.5,剩余容量为 0。
(3)构造最优解
把这些放入的宝物序号组合在一起,就得到了最优解(2,10,6,3,5,8),其中最后
一个宝物为部分装入(装了 8 号财宝的 1/4),能够装入宝物的最大价值为 70.5。
伪代码详解
(1)数据结构定义
根据算法设计中的数据结构,我们首先定义一个结构体 three:
struct three{
double w; //每种宝物的重量
double v; //每种宝物的价值
double p; //每种宝物的性价比(价值/重量)
}
(2)性价比排序
我们可以利用 C++中的排序函数 sort(见附录 B),对宝物的性价比从大到小(非递增)
排序。要使用此函数需引入头文件:
#include <algorithm>
语法描述为:
sort(begin, end)// 参数 begin 和 end 表示一个范围,分别为待排序数组的首地址和尾地址
在本例中我们采用结构体形式存储,按结构体中的一个字段,即按性价比排序。如果不
使用自定义比较函数,那么 sort 函数排序时不知道按哪一项的值排序,因此采用自定义比较
函数的办法实现宝物性价比的降序排序:
bool cmp(three a,three b)//比较函数按照宝物性价比降序排列
{
return a.p > b.p; //指明按照宝物性价比降序排列
}
sort(s, s+n, cmp); //前两个参数分别为待排序数组的首地址和尾地址
//最后一个参数 compare 表示比较的类型
(3)贪心算法求解
在性价比排序的基础上,进行贪心算法运算。如果剩余容量比当前宝物的重量大,则可以放入,剩余容量减去当前宝物的重量,已放入物品的价值加上当前宝物的价值。如果剩余
容量比当前宝物的重量小,表示不可以全部放入,可以切割下来一部分(正好是剩余容量),
然后令剩余容量乘以当前物品的单位重量价值,已放入物品的价值加上该价值,即为能放入
宝物的最大价值。
for(int i = 0;i < n;i++)//按照排好的顺序,执行贪心策略
{
if( m > s[i].w )//如果宝物的重量小于毛驴剩下的运载能力,即剩余容量
{
m -= s[i].w;
sum += s[i].v;
}
else //如果宝物的重量大于毛驴剩下的承载能力
{
sum += m 乘以 s[i].p; //进行宝物切割,切割一部分(m 重量),正好达到驴子承重
break;
}
}
#include<iostream>
#include<algorithm>
using namespace std;
const int M=1000005;
struct three{
double w;//每个宝物的重量
double v;//每个宝物的价值
double p;//性价比
}s[M];
bool cmp(three a,three b)
{
return a.p>b.p;//根据宝物的单位价值从大到小排序
}
int main()
{
int n;//n 表示有 n 个宝物
double m ;//m 表示毛驴的承载能力
cout<<"请输入宝物数量 n 及毛驴的承载能力 m :"<<endl;
cin>>n>>m;
cout<<"请输入每个宝物的重量和价值,用空格分开: "<<endl;
for(int i=0;i<n;i++)
{
cin>>s[i].w>>s[i].v;
s[i].p=s[i].v/s[i].w;//每个宝物单位价值
}
sort(s,s+n,cmp);
double sum=0.0;// sum 表示贪心记录运走宝物的价值之和
for(int i=0;i<n;i++)//按照排好的顺序贪心
{
if( m>s[i].w )//如果宝物的重量小于毛驴剩下的承载能力
{
m-=s[i].w;
sum+=s[i].v;
}
else//如果宝物的重量大于毛驴剩下的承载能力
{
sum+=m * s[i].p;//部分装入
break;
}
}
cout<<"装入宝物的最大价值 Maximum value="<<sum<<endl;
return 0;
}
附:本题是个DP问题,用贪心法并不一定可以求得最优解,以后了解了动态规划算法后本题就有了新的解法
贪心算法示例点击打开链接