贪心算法,顾名思义,就是做出对当前最有利的选择。贪心算法并不从整体最优考虑,而是一定意义上的局部最优解。当然在很多情况下,贪心算法得到的最优解也是整体上的最优解,如最短路径问题、最小生成树问题,一些情况下,贪心算法得到的即使不是全局最优解,其结果也是最优解的很好近似。当问题的精确求解变得非常困难时,使用贪心算法对问题进行求解,也是一种可行的办法。
但是需要注意的是贪心算法进行求解的问题是有条件的,只能对特定类型的问题使用,也就是组合优化问题。组合优化问题首先考虑的是这个问题能不能正面解决,也就是看它能否进行规约,将其变为一个个小问题。然后根据我们学过的知识,判断能不能进行分治法求解,继续观察原问题的结构,如果问题能够问题能继续分解为最优子结构,就意味着我们可以使用动态规划进行求解,这也是我们之前学过的分治法和动态规划的求解思路。在这两个求解思路上,如果问题不仅仅可以被规约成小的问题,并且它有最优子结构性质,同时还具备第三种性质:贪心选择,那此时可以采取第三种方法,贪心算法来进行问题的求解。
贪心算法的基本思路:给定一个初始的解出发慢慢逼近给定的目标,直至问题中的条件得到满足无法继续进行求解,显然选定的初始条件会对问题的求解精度产生很大的影响。
下面对一些常见的贪心算法的问题做一些总结归纳。
这是教科书上典型的贪心算法,延伸出来的活动安排等问题也是一样的。有N个课程集和一间教室,教室同时只能被一个课程占用,每个课程有开始时间 s i s_i si和结束时间 f i f_i fi,现要求尽可能多的使参加的活动最大化,也就是所占时间区间的最大化。
这道问题的贪心策略就是按照结束时间对所有课程排序,选择结束时间尽量早的课程,然后选择的下一个课程开始时间晚于当前课程的结束时间。算法得到的解为全局最优解,贪心选择的意义使剩余的可安排时间最大化,从而安排最多的相容活动。
具体代码为
//输入数组s、j为排好序的课程开始和结束数组,A为标记选择哪个课程的数组,初始化为0
void greedySelector(int n, vector& s, vector& f, vector& A){
A[1] = true;
int j = 1; //最近一次加入的活动
for(int i=2; i=f[j]){
A[i] = true;
j = i;
}
}
}
算法的时间复杂度与排序的时间复杂度相同,为 O ( n l o g n ) O(nlogn) O(nlogn)。
一群人乘船渡河,单个人的体重不会超过穿的载重,每条船最多载两人并且不能超出船的载重,求最少需要多少条穿才能让所有人都过河。
贪心策略为让体重大的人尽量和体重小的人凑对坐船
正确性证明:ABCD四个人是按照体重从大到小排序的, P A > P B > P C > P D P_A>P_B>P_C>P_D PA>PB>PC>PD,若不按照贪心策略,安排B和D坐一条船,此时A和C可能坐一条船,也可能坐不下;
若A和C可以坐一条船,那么四个人需要两条,分别为(A,C)和(B,D)。既然(A,C)可以,那么(A,D),(B,D)组合同样满足,此时贪心策略也是需要两条船。
若A和C不能坐上同一条船,那么四个人需要三条船才能满足条件,分别为A,C,(B,D),按照贪心策略,尽量保证(A,D)和(B,C)的匹配,若A、D能坐一条船,如果B、C能坐一条船,此时需要两条船,否则需要三条船。若A、D不能做一条船,那么A一条船,B、C一条船,D一条船,此时需要三条船
因此贪心策略的算法总是会产生最小的坐船数量。
具体代码为:
int minBoat(vector weight, int limit){
int n = weight.size();
sort(weight.begin(), weight.end());
int l = 0, r = n-1;
int result = 0;
while(l <= r){
if(weight[r] >= limit || weight[r] + weight[l] >= limit){
r--;
result++;
}
else{
l++;
r--;
result++;
}
}
return result;
}
算法的时间复杂度与排序的时间复杂度相同,为 O ( n l o g n ) O(nlogn) O(nlogn)。
将绳子剪为不定长不定数量的小段,使得所有数量的小段绳子乘积最大。
贪心策略为尽量裁剪长度为3的小段绳子。
正确性证明:当绳子长度小于5时,不用裁剪;数量继续增长,总能分解为2的累加,对于绳子拆分为2的累加情况,由于 2 ∗ 2 ∗ 2 < 3 ∗ 3 2*2*2<3*3 2∗2∗2<3∗3,因此将绳子拆分为3的小段所得乘积最大
算法代码为:
int maxRope(int len){
if(len < 5) return len;
int times3 = len%3 == 1? len/3-1: len/3;
int remainder3 = len - 3*times3;
return pow(3, times3)*remainder3;
}
计算时间复杂度为幂的计算时间复杂度,为 O ( l o g n ) O(logn) O(logn)。
一条直线上有N个位置,每个位置要么是猴子,要么是香蕉。每只猴子只能吃一根香蕉,猴子可以吃到离它左右两边最远K步远的香蕉,求被猴子吃掉的最多香蕉数。
贪心策略为猴子吃掉从左边第K步远到右边K步远的第一根香蕉
贪心策略的证明:第X个猴子吃X左边第K步远到右边K步远的第一根香蕉A,如果不吃,那么后面的猴子很有可能吃不到这根香蕉,并且后面的猴子可能没有香蕉吃,那么吃掉的香蕉数量肯定比贪心策略得到的数量小。
具体的代码为:
int maxBanana(vector line, int K){
int n = line.size();
int result = 0;
for(int i=0;in-1) continue;
if(line[j] == 'B'){
result++;
line[j] = "S";
break;
}
}
}
}
return result;
}
遍历整个数组,算法的时间复杂度为 O ( n ) O(n) O(n)。