贪婪法(Greedy)又叫登山法,它的根本思想是逐步到达山顶,即逐步获得最优解,是解决最优化问题时的一种简单但适用范围有限的策略。“贪婪”可以理解为以逐步的局部最优,达到最终的全局最优。
贪婪算法没有固定的算法框架,算法设计的关键是贪婪策略的选择。一定要注意,选择的贪婪策略要具有无后向性,即某阶段状态一旦确定以后,不受这个状态以后的决策影响。也就是说某状态以后的过程不会影响以前的状态,只与当前状态有关,也称这种特性为无后效性。因此,适应用贪婪策略解决的问题类型较少,对所采用的贪婪策略一定要仔细分析其是否满足无后效性。
说明:
贪婪算法策略在《数据结构》课程中的算法也有广泛的应用,如霍夫曼树、构造最小生成树的Prim算法和Kruskal算法的决策过程,都是使用的贪婪算法策略。
一、贪婪策略算法设计框架
1、贪心法的基本思路
从问题的某一个初始解出发逐步逼近给定的目标,每一步都作一个不可回溯的决策,尽可能地求得最好的解。当达到某算法中的某一步不需要再继续前进时,算法停止。
2、该算法适用的问题
贪婪策略面对问题只须考虑当前局部信息就要做出决策,也就是说适用贪婪算法的前提是”局部最优策略能导致产生全局最优解“。
3、该策略下的算法框架
从问题的某一初始解出发; while(能朝给定总目标前进一步) 利用可行的决策,求出可行解的一个解元素; 由所有解元素组合成问题的一个可行解。4、贪婪策略选择
贪婪算法的原理是通过局部最优来达到全局最优,采用的是逐步构造最优解的方法。在每个阶段,都作出一个看上去最优的(在一定的标准下),决策一旦作出,就不可再更改。用贪婪算法只能解决通过局部最优的策略能达到全局最优的问题。因此,一定要注意判断问题是否适合采用贪婪算法策略,找到的解是否一定是问题的最优解。
贪婪算法是依靠经验或直觉,确定一个找最优解的决策,逐步决策获得问题的最优解。在一般情况下,选出最优决策标准是使用贪心设计求解问题的核心。要选出最优决策并不是一件容易的事情,因为最优决策对问题的适用范围可能是非常有限的。
因此,贪婪策略一定要精心确定,在使用之前,最好对策略的可行性进行数学证明。这里,不可能告诉大家一个通用的制定最优解的决策的标准,所以贪婪算法要谨慎使用。
二、可绝对贪婪问题
1、设计一个算法,把一个真分数表示为埃及分数之和的形式。所谓埃及分数,是指分子为1的分数。比如7/8 = 1/2 + 1/3 + 1/24。
数学模型:
将一个真分数F表示为A/B;做BA的整除运算,商为D,余数为K(0 < K < A),它们之间的关系及导出关系如下:
B = AD + K
B/A = D + K/A < D + 1
A/B > 1/(D + 1)
记C = D + 1,这样就找到了分数F所包含的“最大的”埃及分数就是1/C。进一步计算:
A/B - 1/C = (AC - B)/(BC)
也就是说继续要解决的是有关分子为A = AC - B,分母为B = BC的子问题。
算法设计:
(1)设某个真分数的分子为A(),分母为B;
(2)把B除以A的商的整数部分加1后的值作为埃及分数的一个分母C;
(3)输出1/C;
(4)将A乘以C减去B作为新的A;
(5)将B乘以C作为新的B;
(6)如果A大于1且能整除B,则最后一个埃及分数的分母为B/A;
(7)如果A = 1,则最后一个分母为B;否则转到步骤(2)。
例子思想:
C = 8/7 + 1 = 2 // 说明7/8 > 1/2
打印1/2,
A = 72 - 8 = 6 // 在计算7/8 - 1/2 = (72 - 8)/(72) = 6/16 = A/B
B = BC = 16
C = 16/6 + 1 = 3 // 说明16/6 > 1/3
打印1/3,
A = 63 - 16 = 2, // 在计算6/16 - 1/3 = (63 - 16)/(163) = 2/48 = A/B
B = BC = 163 = 48
A > 1但B/A为整数24,打印1/24结束。
算法实现:
#include<stdio.h> void main() { int a, b, c; printf("input element:\n"); scanf("%d", &a); printf("input denominator:\n"); scanf("%d", &b); if(a >= b) printf("input error"); else if((a == 1) || (b % a) == 0) printf("%d/%d = 1/%d", a, b, b/a); else while(a != 1) { c = b/a + 1; a = a*c -b; b = b*c; printf("1/%d", c); if(a > 1) printf(" + "); if(b % a == 0 || a == 1) { printf("1/%d", b/a); a = 1; } } printf("\n"); }结果输出:
三、相对或近似贪婪问题
1、币种统计问题
某单位给每个职工发工资(精确到元)。为了保证不要临时兑换零钱,且取款的张数最少,取工资前要统计出所有职工的工资所需各种币值(100,50,20,10,5,2,1元共7种)的张数。
算法设计:
(1)合理的情况每个的工资应该由文件读入,为了突出算法思想,还是从键盘输入每个的工资。
(2)为了能达到取款的张数最少,且保证不要临时兑换零钱,应该对每一个人的工资,用“贪婪”的思想,先尽量多地取大面额的币种,由大面额到小面额币种逐渐统计。
(3)7中面额无规律可循,而统计每个职工的工资的计算都要用到这7种币值,为了能构造出循环不变式,将7种币值存储在数组B。这样,7种币值就可表示为B[i],i = 1, 2, 3, 4, 5, 6, 7。为了能实现贪婪策略,7种币值应该从大面额的币种到小面额的币种依次存储。
(4)算法需要统计7种面额的数量,设置一个有7个元素的累加器数组S,这样操作就可以通过循环顺利完成了。
算法实现:
#include<stdio.h> void main() { int i, j, n, GZ, A, B[8] = {0, 100, 50, 20, 10, 5, 2, 1}, S[8] = {0, 0, 0, 0, 0, 0, 0, 0}; scanf("%d", &n); for(i = 1; i <= n; i++) { scanf("%d", &GZ); for(j = 1; j <= 7; j++) { A = GZ/B[j]; S[j] = S[j] + A; GZ = GZ - A * B[j]; } } for(i = 1; i <= 7; i++) printf("%d: %d\n", B[i], S[i]); }结果输出:
说明:
(1)每求出一种面额所需的张数后,要把这部分金额减去“GZ = GZ - A * B[j];”,否则将会重复计算。
(2)在用贪婪算法策略时,最好能用数学方法证明每一步的策略是否能保证得到最优解。
经典算法宝典——贪婪思想(五)(1),布布扣,bubuko.com
原文地址:http://blog.csdn.net/ssw_1990/article/details/25491891