1、程序设计实践,程序设计方法之 贪心法,贪心法,概念:贪心法( Greedy algorithm),又称贪心算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法 每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的 一般说来,时间复杂度较低,算法实现也比较容易,问题1:找零钱,http:/202.112.113.45/JudgeOnline/showproblem?problem_id=1026 找零钱找给顾客。手头有quarters ($0.25), dimes ($0.10), nickels ($0.05) and
2、pennies ($0.01),将钱找给顾客,但是需要你找给顾客的钱币数量最少。 解法: 枚举法:枚举所有可能,从中找出钱币数量最少的方案 贪心法:尽可能的先找quarters,然后是dimes, nickels, pennies 这样找出的钱币的个数一定最少么?,贪心法证明:,方便起见上述硬币表示为25美分,10美分,5美分,1美分,待找钱为n美分。引理1: 若n是正整数,则用25美分、10美分、5美分和1美分等尽可能少的硬币找出的n美分零钱中,至多有2个10美分、至多有1个5美分、至多有4个1美分硬币,而不能有2个10美分和1个5美分硬币。用10美分、5美分和1美分硬币找出的零钱不能超过2
3、4美分。,贪心法证明(续),反证法: 假设存在正整数n,使得有办法将25美分、10美分、5美分和1美分硬币用少于贪心算法所求出的硬币去找n美分零钱。 首先,在这种找n美分零钱的最优方式中使用25美分硬币的个数q,一定等于贪心算法所用25美分硬币的个数。 为了说明这一点,注意贪心算法使用尽可能多的25美分硬币,所以qq。但是q也不能小于q。假如q小于q,需要在这种最优方式中用10美分、5美分和1美分硬币至少找出25美分零钱。而根据引理1,这是不可能的。 这两种方式中有同样多的25美分硬币,所以在这两种方式中10美分、5美分和1美分硬币的总值一定相等,并且这些硬币的总值不超过24美分 10美分硬币
4、的个数一定相等,因为贪心算法使用尽可能多的10美分硬币。而根据引理1,当使用尽可能少的硬币找零钱时,至多使用1个5分硬币和4个1分硬币,所以在找零钱的最优方式中也使用尽可能多的10美分硬币。类似地,5美分硬币的个数相等;最终,1美分的个数相等。,找零钱问题(续),找零钱找给顾客。手头有quarters ($0.25), dimes ($0.10), nickels ($0.05) and pennies ($0.01),将钱找给顾客,但是需要你找给顾客的钱币数量最少。 如果缺少nickels($0.05)的硬币,如何找给顾客,才能使钱币数量最少? 如果要找给顾客0.3元钱,最优方案? 贪心法方
5、案:0.25 +0.01 * 5 0.1 * 3,问题2:均分纸牌,均分纸牌(1040)有 N 堆纸牌,编号分别为 1,2,, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若于张纸牌,然后移动。 移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。 现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。 例如 N=4,4 堆纸牌数分别为: 9 8 17 6 移动3次可达到目的: 从 取 4 张牌放到 (9 8 13 10) - 从 取 3
6、 张牌放到 (9 11 10 10)- 从 取 1 张牌放到(10 10 10 10)。,解题思路,确定最终每堆纸牌上的纸牌数目 从一个端点出发沿着一个方向移动纸牌,每次移动后确保当前这堆纸牌上的数目是最终每堆纸牌数目 注意:可以移动负数 最优方案,证明?代码略,问题3:石子移动,有N堆石子排成一个圈,第i堆石子有ai个石子。每堆石子均可被移动到相邻的堆,请问要使得所有堆石子的个数均相等,最少需要移动多少个石子。 输入数据保证最后可以达到每堆石子的个数相等,解题思路,本题模型与均分纸牌很相似 均分纸牌:一条直线 石子移动:环 参考上一题的方法,尝试从任何一堆纸牌出发,求得当前的最小移动的石子数
7、 从不同的石子堆出发最小移动石子数一样么? 如何得到全局最优解 枚举每个出发点,从中找一个移动石子数目最小的。,参考代码,#include #include #include #include #include using namespace std; #define MAXHEAP 1100int main() int n, heapMAXHEAP, tmpHeapMAXHEAP;/ minMoves表示最小移动的牌数,初始化为最大整数int nTotal = 0, nAverage = 0, minMoves = 0x7FFFFFFF, nMoves;int i, j;cin n;,参考代
8、码(续),/读入数据,并得到最终每堆石子的数量nAverage;for (i = 0; i heapi;nTotal += heapi; nAverage = nTotal / n;int now, next; /当前的石子堆now,now右边的石子堆nextint nStart = 0; /记录得到最少移动牌数的方案中起点位置,for (i = 0; i = n ) now = now - n;next = now + 1;if ( next = n ) next = 0;/now堆上的石子移到next堆上,使now堆上石子数符合要求nMoves += labs(tmpHeapnow - n
9、Average); tmpHeapnext = tmpHeapnext + (tmpHeapnow - nAverage);tmpHeapnow = nAverage;if ( nMoves minMoves ) minMoves = nMoves; nStart = i;cout minMoves endl; ,问题4:区间选择问题,有M个闭区间,形如a,b(a和b都是整数),请你从中选出最少个的整数点,使得所有闭区间中都至少有一个整数点被选到。 问题分析:,解题思路,首先,处理一下M个区间,去掉那些包含的其他区间的区间 剩下的区间按照区间的左端点从小到大排序 考虑最左边的区间,在任意一个最
10、优方案中这个区间内一定会有一个点被选择,将该点调整到区间最右边那个点 去掉已经包含选择点的区间,剩下的区间递归考虑,数据结构设计,区间结构体变量保存interval arymaxn; /结构体数组存放所有的区间 int n; /n是输入区间的个数,struct intervalint l, r; /分别是左右边界 ;,模块化设计,区间排序 使用库函数 sort qsort 自己实现 去掉包含其他区间的区间 遍历排序后的数组,去掉包含其他区间的区间 剩下区间,按照左端点从左到右,每个区间贪心地选做右端的点,算法设计:区间排序,使用库函数sort 排序原则: 左端点从小到大排序,左端点一样的,右端
11、点大的排在前,bool cmpinterval(interval x, interval y) if (x.l y.l) return false; /左边界大的排在后if (x.r y.r) return true; /左边界相等的情况,右边界大的排在前return false; ,sort(ary, ary + n, cmpinterval); /注意需要#include ,算法设计:区间去重,区间按照前面的原则排序后,区间去重就相对容易。 判断当前的区间i是否包含其他区间,只需看区间i到区间n-1中,是否存在一个区间j,j的右端点是否小于等于区间i的右端点,如果是则i至少包含区间j,可以
12、去掉;否则区间i不能包含其他任何区间。 遍历数组,依次判断每个区间即可。,算法设计:贪心选点,去重后剩下的区间,按照左端点从左到右,每个区间贪心地选做右端点,已有点的区间不再考虑了。 第一点选择排序数组中第一个区间的右端点; 遍历排序的区间数组,如果当前区间被上次选中的点覆盖,那么不用再考虑在该区间中选点了,否则选择当前区间的右端点,并将选点的数目加1。,总结,贪心算法通过一系列的选择来得到一个问题的解。 它所作的每一个选择都是当前状态下某种意义的最好选择,即贪心选择 希望贪心选择导致最终结果是问题的一个最优解,然而在许多情况下确能达到预期的目的。,贪心法的基本要素,最优子结构性质 当一个问题的最优解包含着它的子问题的最优解时,称此问题具有最优子结构性质。贪心选择性质 所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。 这是贪心法与动态规划算法的主要区别,