1、第四章 贪心方法,什么是贪心方法 背包问题 单源点最短路径 最优归并模式 活动安排问题,实例: 找硬币 假设有三种硬币,面值分别为5分, 2分和1分, 给顾客找零钱时希望拿出的硬币个数最少,找零1角2分: 2个5分, 1个2分2个5分, 2个1分6个2分,找零算法: 先找出一个面值不超过1角2分的最大硬币(5分) 从1角2分中减去5分, 剩7分 再找出一个面值不超过7分的最大硬币(5分) 从7分中减去5分, 剩2分 最后找出一个面值不超过2分的最大硬币(2分),贪心算法的思想: 贪心算法总是作出在当前看来最好的选择。贪心算法并不从整体最优考虑,因此它所作出的选择是 在某种意义上的局部最优选择,
2、贪心算法通常包含一个用以寻找局部最优解的迭代过程对于某些实例, 这些局部最优解转变为全局最优解而对于另外一些实例, 贪心算法不能找到全局最优解,贪心算法求解问题的速度比较快, 因为算法的每一步都是在少量信息的基础上考虑局部最优解, 这样使得每一步的计算量比较小, 所以算法效率比较高,4.1 什么是贪心方法,某一问题的n个输入,该问题的一种解(可行解),是A的一 个子集,满足一定 的条件,约束条件,目标函数,取极值,最优解,4.1 什么是贪心方法,根据题意,选取一种量度标准,然后按量度标准对n个输入排序,按顺序一次输入一个量。如果这个输入和当前已构成在这种量度意义下的部分最优解加在一起不能产生一
3、个可行解,则不把此输入加到这部分解中,这种能够得到某种量度意义下的最优解的分级处理方法就是贪心方法。,量度标准,量度标准意义下的部分最优解,原问题的 n 个输入,排序后的 n 个输入,A(j),贪心方法的抽象化控制,int *GREEDY(int *A,int n) solution=;for (i=1 ;i= n;i+) x=SELECT(A)if (FEASIBLE(solution,x)=1)solution=UNION(solution,x);return (solution); ,按某种最优量度标准从集合A中选择一个输入赋给x,并从A中除去,判断x是否可以包含在解向量中,将x与解集合
4、合并并修改目标函数,4.1 背包问题的贪心算法,问题描述 已知有n种物品和一个可容纳M重量的背包,每种物品i的重量为wi,假定将物品i的某一部分xi放入背包就会得到pixi的效益(0xi1, pi0) ,采用怎样的装包方法才会使装入背包物品的总效益为最大呢? 问题的形式描述 极大化 pixi 约束条件 wi xi M0xi1, pi0, wi0, 1in,1in,1in,目标函数,背包问题实例,其中的4个可行解:,有3个物品, 即n=3, 背包能容纳的最大重量为20, 即c=20 物品的价值和重量: (v1,v2,v3)=(25,24,15), (w1,w2,w3)=(18,15,10),最优
5、解,贪心算法求解背包问题,确定贪心选择策略, 选择价值高的物品放入背包,将物品按价值的降序排列: (v1,v2,v3)=(25,24,15) 按该次序将物品一件件放到背包中, 先装物品1, 即x1=1, w1=18, v1x1 =25; 再装物品2, 因约束条件为wi xi c=20, 所以物品2只能装入重量为2的一小部分, 即x2=2/15; 最后得到总价值为vixi = 25+24*2/15=28.2,实例:n=3, c=20 (v1,v2,v3)=(25,24,15) (w1,w2,w3)=(18,15,10),这是一个次优解,原因是背包容量消耗的过快,确定贪心选择策略, 选择重量轻的物
6、品放入背包,将物品按重量的升序排列: (w3,w2,w1)=(10,15,18) 按该次序将物品放到背包中, 让容量尽可能慢的被消耗 先装物品3, 即x3=1, w3=10, v3x3 =15; 再装入物品2, 因约束条件为wixic=20, 所以物品2 只能装入重量为10的一部分, 即x2=10/15=2/3; 最后得到总价值为vixi =15+24*2/3=31,这仍是一个次优解, 原因是容量在慢慢消耗的过程中, 效益值却没有迅速的增加,实例:n=3, c=20 (v1,v2,v3)=(25,24,15) (w1,w2,w3)=(18,15,10), 选择单位重量价值高的物品放入背包,将物
7、品按vi/wi比值的降序排列: (v2/w2 , v3/w3 , v1/w1 )=(24/15, 15/10, 25/18) 即每一次装入的物品使它占用的单位重量获得当前最大的单位价值 先装入物品2, 即x2=1, w2=15, v2x2 =24; 再装入物品3, 因约束条件为wi xi c=20, 所以物品3只能装入重量为5的一部分, 即x3=5/10=1/2; 最后得到总价值为vixi =24+15*1/2=31.5,这是最优解, 因为每一单位重量的增加将获得最大的单位价值,确定贪心选择策略,实例:n=3, c=20 (v1,v2,v3)=(25,24,15) (w1,w2,w3)=(18
8、,15,10),贪心算法求解背包问题的步骤,计算每个物品的单位重量的价值, 即vi/wi 按vi/wi的值对物品进行降序排列 按贪心选择策略将物品依次放入背包按顺序依次选择单位重量价值高的物品i , 若wi当前背包的剩余容量, 则放入该物品, 并从背包的剩余容量中减去wi (即背包的剩余容量减小)若wi当前背包的剩余容量, 则放入该物品的一部分, 即放入剩余容量/wi,void Knapsack(int n,float M,float v ,float w ,float x ) Sort(n,v,w);int i;for (i=1; i=n; i+) xi=0;float c=M;for (i
9、=1; i=n; i+) if (i=n) ,求解背包问题的贪心算法描述,/ 按v/w的值对物品进行排序,/ 设置初始值,/ M为背包容量, c为当前背包的剩余容量,/ 依次对物品进行处理,/ 若wic, 则物品i不能全部放入背包,/ 若wic, 则物品i能放入背包,/ 修改当前背包的剩余容量,/ 将物品i的一部分放入背包,if (wic) break;,xi=1;,c=c-wi;,xi= c/wi;,算法Knapsack的主要计算时间在于将各种物品依其单位重量的价值从大到小排序。因此算法的时间上界为O(nlogn),算法分析,贪心算法可以求解背包问题, 并得到最优解,课后思考题,0/1背包问
10、题:在杂货店比赛中你获得了第一名,奖品是一车免费的商品。店中有n种不同的货物。规则规定从每种货物中最多只能拿一件,车子的容量为C,物品i需占用的空间为Wi,价值为Pi。你的目标是使车中装载的物品价值最大。(注意:所装货物不能超过车的容量,且同一种物品不得拿走多件。)利用贪心策略,如何选择量度标准才能找到最优解?,4.4 单源点最短路径,问题描述: 给定带权有向图G=(V,E),其中每条边的权是非负实数。另外给定V中的一个顶点,称为源点。现要计算从源点到所有其它各顶点的最短路长度(路的长度是指路上各边权之和)。该问题通常称为单源点最短路径问题。,求解单源点最短路径的算法 在数据结构中我们学习过迪
11、杰斯特拉(Dijkstra)算法, 该算法实际是求解单源点最短路径的一个贪心算法。,Dijkstra算法的基本思想:,设置一个顶点集合S, 并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源点到该顶点的最短路径长度已知。 初始时S中仅含有源点。 设u是G的某一个顶点, 把从源点到顶点u且中间只经过S中顶点的路称为从源点到顶点u的特殊路径, 并用数组dist记录当前每个顶点所对应的最短特殊路径长度。 算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中, 同时对数组dist作必要的修改。 一旦S包含了V中所有的顶点,dist就记录了从源点到所有其它顶点之间的最短路径长度
12、。,10,实例计算,2,1,2,1,2,4,1,2,4,3,1,2,4,3,5,4,10 60 30 100,10 50 30 90,10 50 30 60,10 50 30 60,3,5,60,50,30,算法求出了从源点到其他顶点的最短路径长度, 如果要求出相应的最短路径, 还需要借助一个数组prev来记录从源点到顶点i的最短路径上i的前一个顶点, 这样可以根据数组prev的信息求出最短路径,Dijkstra算法描述,void Dijkstra(int n, int v, Type dist, int prev, Type *c) int i, j; bool sn;for(i=1; i=
13、n; i+) disti=cvi;si=false;if(disti=maxint) previ=0;else previ=v;distv=0;sv=true;,初始化工作,/ si的值为true, 表示顶点i属于集合S,设源点v=1,修改源点的初始值,0,true,for(i=1; in; i+) int temp=maxint;int u=v;for(j=1; j=n; j+)su=true;for(j=1; j=n; j+)if(!sj) ,/ 进行迭代计算,找出dist的最小值,/ 将顶点u加入到集合S中,重新计算加入顶点u之后的dist的值,如果新的dist的值小于原来的值,则修改数
14、组dist和prev中的数据,if(!sj) ,if(newdistdistj) distj=newdist;prevj=u; ,for(i=1; in; i+) int temp=maxint;int u=v;for(j=1; j=n; j+)if(!sj) ,/ 进行迭代计算,找出dist的最小值,/ 将顶点u加入到集合S中,重新计算加入顶点u之后的dist的值,如果新的dist的值小于原来的值,则修改数组dist和prev中的数据,实例计算过程:,i=1时 temp=maxint; u=v=1;,设源点v=1,for(j=1; j=5; j+)j=1, !sj为假, if条件不满足j=2
15、, u=2; temp=dist2=10; j=3, !sj为真,但dist3temp为假j=4,5的情况与j=3时一样,s2=true;,for(j=1; j=n; j+)j=1,2时, !sj为假, if条件不满足j=3, 满足条件(!s3) ,true,60,2,j=4,5时, c24=c25=maxint, if条件不满足,实例计算过程:,i=2时 temp=maxint; u=v=1;,设源点v=1,for(j=1; j=5; j+)j=1,2时, !sj为假, if条件不满足j=3, u=3; temp=dist3=60; j=4, u=4; temp=dist4=30; j=5,
16、 !sj为真,但dist5temp为假,s4=true;,for(j=1; j=n; j+)j=1,2时, !sj为假, if条件不满足j=3, 满足条件(!s3) ,true,50,4,j=4时, !sj为假, if条件不满足 j=5, 满足条件(!s5) ,90,4,经计算最后得到:,由数组prev的值可以得到由源点1到其他各个顶点的最短路径:,最优归并模式,X1,X2,X3,X4,第二章中学习了如何用分治法将两个分别包含n个和m个记录的已排序的数据在O(n+m)时间内归并成一个包含(n+m)个数据的有序文件。下面是两种将4个文件进行归并的方法。不同的配对法要求不同的计算时间。,什么是归并
17、模式,给出n个文件,则有许多种把这些文件成对地归并成一个单一分类文件的方法 不同的配对方法要求不同的计算时间 问题的关键是:确定一个把n个已分类文件归并在一起的最优方法(即需要最少的比较的方法),例3.5 :X1,X2和X3是各自有30个数据、20个数据和10个数据的三个已排序文件,归并X1和X2需要50次数据比较,再与X3归并则还要60次比较,其所需要的数据比较次数总量为110。 如果首先归并X2和X3,需要30次比较,然后再归并X1,需要60次比较,则所要比较数据的总量为90。 因此,第二个归并模式比第一个要好。,最优归并模式的实现,由于归并一个具有n个数据的文件和一个具有m个数据的文件可
18、能需要m+n次数据比较,因此对于量度标准的一种很明显的选择是:每一步都归并尺寸比较小的两个文件 例如有五个文件长度分别是(F1,F5)=(20,30,10,5,30),5,F4,10,F3,二元归并树,二路归并模式,上面所描述的归并模式称为二路归并模式。二路归并模式可以用二元归并树来表示。 二元归并树中叶结点被画成方块,表示五个已知的文件。这些结点称为外部结点。 剩下的结点被画成圆圈,称为内部结点。每个内部结点恰好有两个儿子,表示它是将两个儿子所表示的文件归并在一起而得到的文件。 每个结点中的数字都是那个结点所表示文件的长度(即记录数)。,最优归并模式的实现,带权外部路径长度 如果di表示从根
19、到代表文件Fi的外部节点的距离,qi表示Fi的长度,则这棵二元归并树的记录比较总量是:diqi这个和数叫做这棵树的带权外部路径长度 一个最优二路归并模式与一棵具有最小外部路径的二元树相对应。,i=1,n,二元归并树算法,二元归并树算法把n个树的表L作为输入。树中的每一个结点有三个信息段(LCHILD,RCHILD,WEIGHT)。 起初,L中的每一棵树正好有一个结点。这个结点是一个外部结点,而且其LCHILD和RCHILD信息段为0,而WEIGHT是要归并的n个文件之一的长度。 算法运行期间,对于L中的任何一棵具有根结点T的树, WEIGHT(T)表示要归并的文件的长度。 WEIGHT(T)
20、= 树T中外部结点的长度和,二元归并树算法,Struct node * TREE(Struct node * L,int n) for( i=1; in ;i+) GETNODE(T);LCHILD(T)LEAST(L)RCHILD(T)LEAST(L)WEIGHT(T)WEUGHT(LCHILD(T)+WEUGHT(RCHILD(T)INSERT(L,T)return (LEAST(L) ,每个节点有三个信息:LCHILD,RCHILD,WEIGHT,构造一个新的根结点T,找出L中一棵具有最小WEIGHT的树,并删除 将其加入T的左子树,把根为T的树插入到表L中,最后留在L中的树就是最优归并
21、树,二元归并树算法的实例,例:L最初有6个文件,长度分别为:2 , 3 , 5 , 7 , 9 , 13 L=2 , 3 , 5 , 7 , 9 , 13,生成一个新的根结点,选长度最小文件的作为新树的左右子女,并从L中删除 将新生成的树加入到L中, L=5, 5 , 7 , 9 , 13,2,3,5,二元归并树算法的实例,L=5, 5 , 7 , 9 , 13,再生成一个新的根结点,选长度最小文件的作为新树的左右子女,并从L中删除 将新生成的树加入到L中, L= 10, 7 , 9 , 13,5,10,二元归并树算法的实例,L= 10, 7 , 9 , 13 ,再生成一个新的根结点,选长度最
22、小文件的作为新树的左右子女,并从L中删除 将新生成的树加入到L中, L= 10, 16 , 13,7,9,16,二元归并树算法的实例,L= 10, 16 , 13 ,再生成一个新的根结点,选长度最小文件的作为新树的左右子女,并从L中删除. 将新生成的树加入到L中, L= 23, 16,13,23,二元归并树算法的实例,L= 23, 16 ,再生成一个新的根结点,选长度最小文件的作为新树的左右子女,并从L中删除. 将新生成的树加入到L中, L= 39,39,二元归并树算法分析,Struct node * TREE(Struct node * L,int n) for( i=1; in ;i+)
23、GETNODE(T);LCHILD(T)LEAST(L)RCHILD(T)LEAST(L)WEIGHT(T)WEUGHT(LCHILD(T)+WEUGHT(RCHILD(T)INSERT(L,T)return (LEAST(L) ,主循环执行n-1次,如果L中WEIGHT的值为非降次序,则LEAST(L)只需要O(1)的时间,INSERT(L,T)在O(n)时间内被执行,此算法的计算是总量为:O(n2),活动安排问题,实例: 毕业设计答辩会场安排2006年本科毕业生分为8个小组进行毕业答辩, 答辩小组都想使用有投影设备和空调的会议室, 因为各答辩小组教师的上课时间不同, 因此他们选择不同的时间
24、段进行工作, 请在一天当中安排尽可能多的答辩小组,安排方案 第一天: 4, 1, 7, 6 第二天: 2, 3, 5, 8,设有n个活动的集合E=1, 2, n, 其中每个活动都要求使用同一资源, 而在同一时间内只有一个活动能使用这一资源 每个活动i都有一个使用该资源的起始时间si和结束时间fi, 且sifi 。 如果选择了活动i , 则它在半开时间区间si, fi)内占用资源 若区间si, fi)与区间sj, fj)不相交, 则称活动i与j是相容的 也就是说, 当sifj或sjfi时, 活动i与活动j相容 活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合,活动安排问题描述:,活动
25、安排问题的贪心算法,活动的起始时间和结束时间分别存储在数组s和f中按结束时间的非减序, 即f1f2fn对活动进行排列选择排在最前面的活动, 将其加入到集合A中依次检查活动i是否与最近加入集合A的活动j相容, 若相容, 则活动i成为最近加入集合A的活动否则将不选择活动i,实例: 毕业设计答辩会场安排,8 9 10 11 12 13 14 15 16 17 18 19,1,2,3,4,5,6,7,8,黄色标出的活动安排在第一天 兰色标出的活动安排在第二天,2,4,6,8,void GreedySelector(int n, Type s , Type f , bool A ) A1=true;int j=1;for (int i=2; i=n; i+) else Ai=false; ,活动安排问题的贪心算法GreedySelector描述,/ 选择第1个活动,首先将活动按结束时间的非减序f1f2fn排列,/ 变量j用来记录最近一次加入到A中的活动,/ 依次检查活动i,/ 活动i与A中的活动相容,/ 活动i加入A中,/ 活动i取代活动j, 成为最近加入A的活动,/ 活动i与A中的活动不相容,if (si=fj) Ai=true; j=i; ,