1、Greedy Algorithm,2010.7.9,贪心算法,学习要点 理解贪心算法的概念。 掌握贪心算法的基本要素 (1)最优子结构性质 (2)贪心选择性质 理解贪心算法与动态规划算法的差异 理解贪心算法的一般理论 通过应用范例学习贪心设计策略。 (1)最优装载问题; (2)活动安排问题; (3)多机调度问题; (4)区间覆盖问题; (5)哈夫曼编码; (6)其它问题。,概念,贪心算法不是具体的算法,应该是贪心思想。贪心算法总是作出在当前看来最好的选择。贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对
2、所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。,贪心算法的基本素,对于一个具体的问题,怎么知道是否可用贪心算法解此问题,以及能否得到问题的最优解呢?这个问题很难给予肯定的回答。但是,从许多可以用贪心算法求解的问题中看到这类问题一般具有2个重要的性质:贪心选择性质和最优子结构性质。,贪心算法的基本要素(1),贪心选择性质,指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。动态规划
3、算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。 对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。,贪心算法的基本要素(2),当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。,最优子结构性质,贪心算法与动态规划算法的差异,贪心算法和动态规划算法都要求问题具有最优子结构性质,这是两类算法的一个共同点。但是,对于具有最优子结构的问题应该选用贪心算
4、法还是动态规划算法求解?是否能用动态规划算法求解的问题也能用贪心算法求解?下面研究2个经典的组合优化问题,并以此说明贪心算法与动态规划算法的主要差别。(背包问题与0-1背包问题),贪心算法与01背包问题的差异,0-1背包问题: 给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大?,在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。,贪心算法与01背包问题的差异,部分背包问题(适合贪心算法): 与0-1背包问题类似,所不同的是在选择物品i装入
5、背包时,可以选择物品i的一部分,而不一定要全部装入背包,1in。,这两类问题都具有最优子结构性质,极为相似,但背包问题可以用贪心算法求解,而0-1背包问题却不能用贪心算法求解。,贪心算法与01背包问题的差异,首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。,用贪心算法解决部分背包问题的方法,贪心算法与01背包问题的差异,对于0-1背包问题,贪心选择之所以不能得到最优解是因为在这种情况下,它
6、无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择。由此就导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。,实例分析,1.最优装载,有一批集装箱要装上一艘载重量为c的轮船。其中集装箱i的重量为Wi。最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。算法描述:最优装载问题可用贪心算法求解。采用重量最轻者先装的贪心选择策略,可产生最优装载问题的最优解。评:相当于一个背包问题,船的体积不限制,重量有限制。每次选择最轻的即可。例题
7、:poj 1042,2.活动安排问题,已知N个事件的发生时刻和结束时刻(见下页表,表中事件已按结束时刻升序排序)。一些在时间上没有重叠的事件,可以构成一个事件序列,如事件1,6,11。事件序列包含的事件数目,称为该事件序列的长度。请编程找出一个最长的事件序列。,2.活动安排问题(1),例:设待安排的11个活动的开始时间和结束时间按结束时间的非减序排列如下:,如左图所示。图中每行相应于算法的一次迭代。阴影长条表示的活动是已选入集合A的活动,而空白长条表示的活动是当前正在检查相容性的活动。,图解1,图解2(另一种图解方式),最大相容活动相容子集:(1,4,8,11). 也可以表示为等长的数组: 1
8、,0,0,1,0,0,0,1,0,0,1.,活动安排问题(4),若被检查的活动i的开始时间si小于最近选择的活动j的结束时间fi,则不选择活动i,否则选择活动i加入集合A中。 贪心算法并不总能求得问题的整体最优解。但对于活动安排问题,贪心算法greedy Selector却总能求得的整体最优解,即它最终所确定的相容活动集合A的规模最大。结论可以用数学归纳法证明。,例题:poj 1328 1065,3.多机调度问题(1),多机调度问题 :要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。约定: 每个作业均可在任何一台机器上加工处理,但未完工前不允许中断处理。作业
9、不能拆分成更小的子作业这个问题是NP完全问题,到目前为止还没有有效的解法。对于这一类问题,用贪心选择策略有时可以设计出较好的近似算法。,多机调度问题(2),采用最长处理时间作业优先的贪心选择策略可以设计出解多机调度问题的较好的近似算法。按此策略,当 nm时,首先将n个作业依其所需的处理时间从大到小排序。然后依此顺序将作业分配给空闲的处理机。算法所需的计算时间为O(nlogn)。,多机调度问题(3),例如:设7个独立作业1,2,3,4,5,6,7由3台机器M1,M2和M3加工处理。各作业所需的处理时间分别为2,14,4,16,6,5,3。按算法greedy产生的作业调度如下图所示,所需的加工时间
10、为17。,2010-7-9,4.区间覆盖问题,用i来表示x轴上坐标为i-1,i的区间(长度为1),并给出M(1=M=200)个不同的整数,表示M个这样的区间。现在让你画几条线段覆盖住所有的区间,条件是:每条线段可以任意长,但是要求所画线段之和最小,并且线段的数目不超过N(1=N=50)。例如:M=5个整数1、3、4、8和11表示区间,要求所用线段不超过N=3条 0 1 2 3 4 5 6 7 8 9 10 11,2010-7-9,算法分析:,如果N=M,那么显然用M条长度为1的线段可以覆盖住所有的区间,所求的线段总长为M。 如果N=1,那么显然所需线段总长为: 如果N=2,相当于N=1的情况下
11、从某处断开(从哪儿断开呢?)。 如果N=k呢?,5.哈夫曼编码,哈夫曼编码:是广泛地用于数据文件压缩的十分有效的编码方法。其压缩率通常在20%90%之间。哈夫曼编码算法用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。给出现频率高的字符较短的编码,出现频率较低的字符以较长的编码,可以大大缩短总码长。前缀码 对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其它字符代码的前缀。,哈夫曼编码的算法思路,算法思路:1)以n个字母为结点构成n棵仅含一个点的二叉树集合,字母的频率即为结点的权 2)每次从二叉树集合中找出两个权最小者合并为一棵二叉树: 增加一个根结点将
12、这两棵树作为左右子树.新树的权为两棵子树 的权之和. 3) 反复进行步骤2)直到只剩一棵树为止.,哈夫曼编码算法过程,在书上给出的算法huffmanTree中,编码字符集中每一字符c的频率是f(c)。以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。经过n1次的合并后,优先队列中只剩下一棵树,即所要求的树T。算法huffmanTree用最小堆实现优先队列Q。初始化优先队列需要O(nlogn)计算时间,由于最小堆的removeMin和put运算均需O(logn
13、)时间,n1次的合并总共需要O(nlogn)计算时间。因此,关于n个字符的哈夫曼算法的计算时间为O(nlogn) 。,图解过程,a,b,c,d,e,f,6 2 3 3 4 9,a,b,c,d,e,f,6 2 3 3 4 9,5,a,b,c,d,e,f,6 2 3 3 4 9,5,7,a,b,c,d,e,f,11,7,5,6,2,3,3,4,9,a,b,c,d,e,f,11,16,5,6,2,3,3,4,9,7,a,b,c,d,e,f,11,16,5,6,2,3,3,4,9,7,27,1,1,1,1,1,0,0,0,0,0,假若这样写的话 a 的编码是:11; b的编码是:101; c 的编码是
14、:100;d的编码是:011 e 的编码是:010; f的编码是:00 例题:poj:1784 (推荐理解),6.其它问题,1.最小生成树 算法一:(普里姆算法) 算法二:(克鲁斯卡尔算法) 2.单源最短路径 迪杰斯特拉算法,总结,贪心算法的基本步骤 从问题的某个初始解出发。 采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个部分解,缩小问题的范围或规模。 将所有部分解综合起来,得到问题的最终解。 另外,前面的题目都比较简单,但不说明贪心思想只是解决显而易见的问题,其实很多问题要经过变化之后才能发现它的贪心本质,这时就比较麻烦。但一旦证明贪心方法正确之后就可以很容易地写出程序,并且程序效率一般较高。,一些贪心题目: hdoj: 1009(FatMouse Trade,容易) 1050(搬桌子,容易,可不做) 2037(今年暑假不AC,活动安排问题,容易) 1051(同poj 1065,不难) 1053(Huffman编码,其实不需要建树,不难) 1052(田忌赛马,较难,理解分析的思想) poj:(去年的题目,有时间再看吧) 1230(比较麻烦) 1505(用二分+贪心) 1477 (较易) 1922(计算型的的贪心) 1716 (典型贪心) 1017 (比较麻烦),