一、思想
贪心算法在每一步做出当时看起来最佳的选择,也就是说总是做出局部最优的选择,希望这样能得到全局最优解,贪心算法不一定能得到最优解,产生最优解的条件是:
1.最优子结构;
2.贪心选择性:当一个问题的全局解可以通过局部最优解得到,就称这个问题具有贪心选择性。
证明思路:假定首选元素不是贪心选择所要的元素,证明将首元素替换成贪心选择所需元素,依然得到最优解;数学归纳法证明每一步均可通过贪心选择得到最优解。
一定要和动态规划区分开!动态规划具有最优子结构和子问题重叠性的性质,适用贪心算法时,动态规划可能不使用,类似的,适用动态规划时,贪心算法可能不适用。
二、应用例题
【1】poj2393:已知每周加工1单元的奶酪需要C_i分,储存1单元的奶酪需要S分,奶酪不会变质,在第i周的时候需要向客户提供Y_i单元重量的奶酪,这些奶酪可以是本周生产的,也可能是前几周储存的,设计一个算法使其花费最小。
输入:周数N,储存单价:S;以及第i周的加工成本C_i和第i周的需求量Y_i;输出:最小的成本价格
思路:刚开始想这道题的时候想的太复杂,会想着某一周的牛奶可能一部分是本周生产的或一部分是前面几周生产的,但完全没必要,第i周的成本或者是C_i或者是前面第k周的成本加上储存的价格C_k+(i-k)*s,只需要选择其中的最小值,计算成本即可。
代码:
#include<cstdio>
const int size=10000;
int main()
{
/*提示输入周数和储存成本单价*/
int n,s;
printf("请输入周数:\n");
scanf("%d",&n);
printf("请输入储存成本单价:\n");
scanf("%d",&s);
/*提示输入每周的成本单价和需求量*/
int c[size];
int y[size];
printf("请输入成本单价和需求量:\n");
for(int i=1;i<=n;i++)
{
scanf("%d%d",&c[i],&y[i]);
}
/*选择每周的最小成本单价*/
for(int i=1;i<=n;i++)
{
for(int j=i;j>0;j--)
{
if(c[j]+(i-j)*s<=c[i])
{
c[i]=c[j]+(i-j)*s;
}
}
}
/*计算所有周的最小花费*/
long long cost=0;
for(int i=1;i<=n;i++)
{
cost+=c[i]*y[i];
}
printf("%d周需要的最小花费是:%lld",n,cost);
}
【2】poj1328:雷达装置,x轴上方是海洋,其中有n个小岛,x轴下方是陆地;在海岸线上放置雷达,所能探测到的最大距离是d,即可以探测到半径为d的圆,设计一种算法使得用最少的雷达探测到所有的小岛,其中小岛的位置用(xi,yi)坐标表示。输入:n(小岛的数目),d(雷达探测的距离)及小岛的坐标值,输出由测试用例号和雷达的数量构成。
思路:刚开始想这道题时觉得应该依次设雷达的位置为从最左边的点到最右边的点之间的横坐标,但这样是不对的,首先雷达的位置不一定是整数值,所以是不能直接暴力求解的,使用贪心算法,从左到右尽可能多的覆盖岛屿,以岛屿为圆心,探测距离为半径画圆,如果与横坐标轴无交点,说明该岛屿不能被覆盖到,则不能实现直接返回0;若有交点,则将与x轴的交点保存在数组point[i].sta和point[i].end,分别代表左右交点。首先将所有圆按照point[i].end进行排序,然后依次从左到右选择point[i].end对应的圆。
若point[i].end>point[i+1].sta,则第i个圆可以覆盖下一个岛屿,则更新岛屿,选择新的岛屿进行查看;否则需要建立新的雷达。
代码如下:
#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
const int size=1000;
//定义以小岛为圆心与x轴的交点的结构体数组
struct data
{
float sta,end;//sta即左交点坐标值,end是右交点坐标值
}point[size];
//定义按右交点从小到大排序的方法
bool cmp(data a,data b)
{
if(a.end<b.end)
{
return true;
}
else
{
return false;
}
}
int main()
{
/*输入小岛数目和雷达探测距离*/
int n,d;
cout<<"请输入小岛数目:"<<endl;
cin>>n;
cout<<"请输入雷达探测距离:"<<endl;
cin>>d;
/*输入各小岛的位置坐标*/
cout<<"请输入各小岛的坐标值:"<<endl;
int x[size];
int y[size];
int max_y=0;//定义小岛中最大的y值
for (int i=0;i<n;i++)
{
cin>>x[i]>>y[i];
if(y[i]>max_y)
{
max_y=y[i];
}
}
/*判断小岛y坐标值是否大于d,若是则无法实现*/
if(max_y>d||d<0)
{
cout<<"该问题值不合理无法实现。";
}
/*以小岛为圆心,d为半径做圆,得到该圆与x轴的交点*/
else
{
float m;
for(int i=0;i<n;i++)
{
m=sqrt(d*d*1.0-y[i]*y[i]*1.0);
point[i].sta=x[i]-m;
point[i].end=x[i]+m;
}
sort(point,point + n,cmp);
int number = 0; //定义雷达的数量
bool cover[size]; //标记岛屿是否被覆盖到
//memset(vis, false, sizeof(vis));
for(int i = 0; i < n; i ++)
{ // 类似的活动选择。
if(!cover[i])
{
cover[i] = true;
for(int j = 0; j < n; j ++)
if(!cover[j] && point[j].sta <= point[i].end)
cover[j] = true;
number ++;
}
}
cout<<"最少需要的雷达数目是:"<<endl;
cout<<number;
}
return 0;
}
【3】非0-1背包问题:给定n个物品,物品价值分别为P1,P2,...,Pn,物品重量分别W1,W2,...Wn,背包重量为M。每种物品可部分装入到背包中,输出X1,X2,...,Xn,0<=Xi<=1,使得PiXi的和最大,且WiXi的和不超过M,试设计一个算法求解该问题,分析算法的正确性。
思路:
0-1背包问题可以用动态规划来求解,每次对物品有选中和不选中两种选择,而该问题物品可以切割部分放入,则使用贪心算法。使用贪心算法时可以有多种选择的标准:
(1)按价值降序排序;(2)按重量升序排序;(3)按单位价值降序排序;(4)按单位重量排序。根据问题是要求价值最大,所以选择第三种排序方法,定义V(n,M)为n件物品,容量为M时的价值。
首先计算每种物品的单位价值,然后降序排序,即u[i];然后依次放入背包,若W[i]<=M,则可将该物品全部放入,则V(n,M)=p[i]+V(n-1,M-W[i]);
若W[i]>M,则将该物品部分放入,V(n,M)=P[i]*u[i],M=0.
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int size=100;
struct data
{
float u,w,p;
} t[size];
bool cmp(data a,data b)
{
if(a.u>b.u)
{
return true;
}
else
{
return false;
}
}
int main()
{
/*输入物品数量、背包容量*/
int n,m;
cout<<"请输入物品件数:"<<endl;
cin>>n;
cout<<"请输入背包容量:"<<endl;
cin>>m;
/*请输入每件物品的价值和重量*/
cout<<"请输入每件物品的价值和重量:"<<endl;
for(int i=0;i<n;i++)
{
cin>>t[i].p>>t[i].w;
}
/*计算每件物品的单位价值,并进行排序*/
for(int i=0;i<n;i++)
{
t[i].u=t[i].p/t[i].w;
}
sort(t,t+n,cmp);
/*定义每件物品放进去的量,初始化为0*/
float x[size];
for(int i=0;i<n;i++)
{
x[i]=0;
}
/*求解*/
float v[size];//定义总价值
for(int i=0;i<n;i++)
{
/*如果排好序的第i件物品重量小于m,则全部放入*/
if(t[i].w<=m)
{
x[i]=1;
m-=t[i].w;
v[i]=t[i].p;
}
else
{
x[i]=m/t[i].w;
m=0;
v[i]=t[i].p*x[i];
}
}
cout<<"物品按单位价值排序为:"<<endl;
for(int i=0;i<n;i++)
{
cout<<t[i].u<<" ";
}
cout<<endl;
cout<<"对应该单价排序的物品存放比例依次为:"<<endl;
for(int i=0;i<n;i++)
{
cout<<x[i]<<" ";
}
cout<<endl;
float value=0.0;
for(int i=0;i<n;i++)
{
value+=v[i];
}
cout<<"最大价值为:"<<value;
return 0;
}
【4】给定直线上2n个点的序列P{1,2,...,2n},每个点要么是白点要么是黑点,其中共有n个白点和n个黑点,相邻两个点之间距离均为1,请设计一个算法将每个白点与一黑点相连,使得连线的总长度最小。
思路:假设p[i]=0表示黑点,p[i]=1表示白点,d[i][j]表示从 第i个点到第j个点的距离,则有d[i][j]=0 (p[i]=p[j]); d[i][j]=j-i (p[i]!=p[j])。使用贪心算法,为每个白点加上标志:f[i]=0(p[i]=1)表示该点还没有和黑点连接,若被使用,则更新标志为1。每次找d[i][j](p[i]!=p[j])的最小值,最后将其值相加。使用栈进行操作。