一个贪心算法总是做出当前最好的选择,也就是说,它期望通过局部最优选择从而得到全局最优的解决方案。 ——《算法导论》
贪心算法秘籍
- 贪心策略(即选择当前状态下最优解决方案)
- 局部最优解
- 全局最优解
【使用贪心算法的例子:冒泡排序——贪心策略是每一次从剩下的序列中挑一个最大的数,把这些选出来的数放在一起,就得到了从大到小的排序结果。】
贪心算法应用
①加勒比海盗——最优装载问题
有一天,加勒比海盗们截获了一艘装满古董的货船,每件古董都价值连城,一旦打碎就失去价值。虽然海盗船足够大,但载重量为 C,每件古董的重量为 Wi ,海盗们该如何把尽可能多数量的古董装上海盗船?
【思考过程】要求装的古董数量尽可能多,而船的载重量固定,则优先装重量小的物品。
【算法设计】(1)当载重量固定为 C 时,Wi 越小,可装载的古董数量 n 越大。 (2)把 n 个古董的重量从小到大排序,根据贪心策略依次选出 i 个古董,直到海盗船装不下。
【代码实现】
#include <iostream>
#include <algorithm>
const int N = 1000005;
using namespase std;
double w[N]; //古董的重量数组
int main(){
double c;
int n;
cout<< "请输入载重量 c 及古董个数 n : "<<endl;
cin>>c>>n;
cout<<"请输入每个古董的重量,用空格分开: "<<endl;
for(int i=0;i<n;i++){
tmp+=w[i];
if(tmp<=c)
ans ++;
else
break;
}
cout<<"能装入的古董最大数量为 Ans= ";
cout<<ans<<endl;
return 0;
}
②阿里巴巴与四十大盗——背包问题
假设山洞里有 n 种宝物,每种宝物有一定重量 w 和相应的价值 v ,毛驴运载能力有限,只能运走 m 重量的宝物,一种宝物只能拿一样,宝物可分割。那么如何使毛驴运走最大价值的宝物?
【思考过程】如果选价值量最大的宝物,那重量不一定小,不行;如果选重量最小的宝物,那价值不一定高,也不行;每次选取单位重量价值最大的宝物,即每次选择性价比(价值/重量)最高的宝物,达到运载重量 m ,那一定是价值最大的。
【算法设计】(1)数据结构及初始化。将 n 种宝物的重量和价值存储在结构体 three(含重量、价值、性价比),按性价比从高到底排序。用 sum 来存储毛驴能够运走的最大价值,初始量为 0。 (2)根据贪心策略,按性价比从大到小选宝物,直到达到毛驴的运载能力。每次选性价比高的物品,判断是否小于 m ,若小于则放入,sum加上当前宝物的价值,m减去当前宝物的重量;若大于,则取该宝物的一部分 m*p[i] , m=0 ,程序结束。m减少到0,则sum得到最大值。
【代码实现】
#include <iostream>
#include <algorithm>
const int N = 1000005;
using namespase std;
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个宝物
double m; //m表示毛驴的承载能力
cout<< "请输入宝物数量 n 及毛驴的承载能力 m : "<<endl;
cin>>n>>m;
cout<<"请输入每个每个宝物的重量和价值,用空格分开: "<<endl;
for(int i=0;i<n;i++){
cin>>a[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;
sm+=s[i].v;
}
else{ //如果宝物的重量大于毛驴剩下的承载能力
sum+=m*s[i].p; //部分装入
break;
}
}
cout<<"装入宝物的最大价值Maximum value="<<sum<<endl;
return 0;
}
【问题拓展】0-1背包:物品不可分割的装载问题。
③高级钟点秘书——会议安排
“钟点秘书”,是指年轻白领女性利用工余时间为客户提供秘书服务,并按钟点收取酬金。“钟点秘书”为客户提供有偿服务的方式一般是:采用电话、电传、上网等“遥控”式服务,或亲自到客户公司处理部分业务。其服务对象主要有三类:一是外地前来考察商务经营、项目投资的商人或政要人员;二是前来开展短暂商务活动,或召开小型资讯发布会的国外客商;三是本地一些请不起长期秘书的企、事业单位。
某跨国公司总裁正分身无术,为一堆会议时间表焦头烂额,希望高级钟点秘书能做出合理的安排,能在有限的时间内召开更多的会议。
【思考过程】在会议安排中,每个会议 i 都有起始时间 bi 和结束时间 ei ,且 bi < ei ,即一个会议进行的时间为半开区间 [bi , ei)。如果 [bi , ei)和[bj , ej) 均在“有限时间内”,且不相交,则称会议 i 和会议 j 相容的。要让会议数最多,我们需要选择最多的不相交时间段。我们可以尝试贪心策略:
(1)每次选择开始时间最早且与已安排的会议相容的会议;(不行)
(2)每次选择持续时间最短且与已安排的会议相容的会议;(不行)
(3)每次选择结束时间最早且与已安排的会议相容的会议。(行)
【算法设计】(1)初始化,将n个会议的开始时间、结束时间存放在结构体数组中,然后按结束时间从小到大排序,结束时间相等的,按开始时间从大到小排序。 (2)选择第一个具有最早结束时间的会议, 用 last 记录刚选中会议的结束时间。 (3)依次从剩下未安排的会议中选择,若会议 i 开始时间大于等于最后一个选中的会议的结束时间 last ,那么会议 i 与已选中的会议相容,可以安排,更新 last 为刚选中会议的结束时间;否则,舍弃会议 i ,检查下一个会议是否可以安排。
【代码实现】
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
struct Meet{
int beg; //会议开始时间
int end; //会议结束时间
int num; //记录会议编号
}meet[1000]; //会议的最大个数为1000
class setMeet{
public:
void init();
void solve();
private:
int n,ans; //n:会议总数 ans:最大的安排会议总数
};
//读入数据
void setMeet::init(){
int s,e;
cout<<"输入会议总数:"<<endl;
cin>>n;
cout<<"输入会议的开始时间和结束时间,以空格分开:"<<endl;
for(i=0;i<n;++i){
cin>>s>>e;
meet[i].beg=s;
meet[i].end=e;
meet[i].num=i+1;
}
}
bool cmp(Meet x,Meet y){
if(x.end == y.end)
return x.beg > y.beg;
return x.end < y.end;
}
void setMeet::solve(){
sort(meet,meet+n,cmp); //对会议按结束时间排序
cout << "排完序的会议时间如下:"<<endl;
int i;
cout <<"会议编号"<<" 开始时间 "<<" 结束时间"<<endl;
for(i-0;i<n;i++){
cout<<" "meet[i].num <<"\t\t"<<meet[i].beg<<"\t"<<meet[i].end<<endl;
}
cout<<"————————————————————————————————————"<<endl;
cout<<"选择的会议的过程:"<<endl;
cout<<" 选择第"<< meet[0].num<<"个会议"<<endl; //选中了一个会议
ans=1;
int last = meet[0].end; //记录刚刚被选中会议的结束时间
for(i=1;i<n;++i){
if(meet[i].beg>=last){ //如果会议i开始时间大于等于最后一个选中的会议的结束时间
ans++;
last = meet[i].end;
cout << " 选择第"<<meet[i].num<<"个会议"<<endl;
}
}
cout<<"最多可以安排"<<ans<<"个会议"<<endl;
}
int main(){
setMeet sm;
sm.init(); //读入数据
sm.solve(); //贪心算法求解
return 0;
}
未完待续,预知后事如何,请听下回分解。