一、要点
贪心算法的基本要素
1、贪心选择性质
整体最优解是一系列局部最优解的选择,同时也是贪心与动态规划最主要的区别。
贪心自上而下解决问题,动态规划自下而上解决问题。
动态规划要求解所有子问题,而贪心只需要局部最优解。
2、最优子结构性
当一个问题的最优解包含其子问题的最优解时,我们称问题具有最优子结构性。
3、只有一个FOR循环
二、问题
活动安排问题
问题:设有一系列活动需要使用一个报告厅,而在同一时间内,只有一个活动可以使用这个报告厅。每个活动的开始时间和结束时间已知,问如何安排活动,使报告厅的利用率最大化?
核心思想:我们开两个数组s[],e[]分别存放活动的开始时间和结束时间。数组的排列顺序按照活动的结束时间排序,即结束时间早的排在前面,结束时间晚的排在后面。一开始,我们安排活动1使用报告厅,然后依次比较剩下活动的开始时间,开始时间在活动1的结束时间之前的活动直接PASS。如果开始时间在活动1结束时间之后,就将它安排成下一个,让后再用它的结束时间和后面的活动进行比较。
整个过程就是这么简单,这里有小伙伴可能就会问了,你这样安排一定可以保证是最优的吗?
事实上,贪心算法往往只关心当前最优解,而不考虑整体最优,所以很多情况下,使用贪心算法都不能保证得到的是全局的最优解,但对于活动安排问题,我们使用贪心法一定可以得到全局最优解,这一点可以用数学归纳法证明,这里就不做进一步的证明了。
//s[]存开始时间,e[]存结束时间,A[]存安排的活动
//e[]按照由小到大排列,先结束的排在前面
void GreedySelector(int n ,type s[] ,type e[] ,bool A[] )
{
A[I]=true;//活动1默认安排
int j =1;
for(int i =1;i<=n;i++)
{
if(s[i]>=e[j])//如果下一个活动的开始时间晚于当前活动的结束时间,将下一个活动置为true;
{
A[i]=true;
j=i;
}
else
A[i]=false;
}
}
背包问题
贪心法其实并不能够解决0-1背包问题,因为在0-1背包问题中,物品是不可拆分的,如果我们假设物品可以拆分,即可以装入1/2个物品或者1/4个物品等,那么问题就可以用贪心法来解决了。
贪心法解决背包问题的思路很简单,我们用物品价值/物品重量,得到物品的单位价值,然后按单位价值排序,再往里面按顺序装就行了,如果最后一个物品装不下了,就把物品拆分,把背包的空装满就行。
public class test{
public void tanxin()
{
float rest= bag;
int j=0;
float b=0;
for(int i=2;i>=0;i--)
{
if(goods[i].weight<=rest)
{
goods[i].bn=goods[i].weight;
goods[i].an=goods[i].value;
rest=rest-goods[i].weight;
b=b+goods[i].value;
j++;
}
else if(rest>0){
float a =(float)rest/goods[i].weight;
goods[i].an=goods[i].value*a;
goods[i].bn=goods[i].weight*a;
b=b+(goods[i].value*a);
rest=0;
}
}
单源最短路径(DijKstra算法)
大名鼎鼎的DijKstra算法其实就是单源最短路径问题的贪心法解法。其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到所有其他顶点之间的最短路径长度。
算法复杂度为:n平方