Greedy Algorithm
Greedy Algorithm
怎么贪才不会翻车?也是一种艺术。
概述
并非所有问题用贪心都能找到最优解。需要满足两个性质:1.贪心选择性质:问题的整体最优解可以通过一系列局部最优的选择来达到,由数学归纳法证明。2.最优子结构性质:问题的最优解包含其子问题的最优解,由反证法证明。
解题要点:正确找出贪心的原则。一般是最值优先,因此经常需要排序或者使用优先级队列。
活动安排问题
无脑列出四个最值:1.最早开始时间优先;2.最晚开始时间优先;3.最早结束时间优先;4.最晚结束时间优先。第2和第4显然不对。考虑方案1和方案3。对于模棱两可的决策,直接考虑对应的极端情况。若有2个活动A:A1[0,24] A2[1,2];若用方案1则最终只能安排一个活动,若用方案2则能完成两个活动。因此这里的贪心原则为:最早结束时间优先。定义一个活动结构体,根据end属性排序即可。
1 |
|
背包问题
1.最高价值优先;2.最低重量优先;3.最高性价比优先。无脑3.
1 |
|
田忌赛马
田忌与齐威王赛马,每一局的赢家+1积分 输家-1积分 平局无事发生,田忌如何安排才能获得最多的积分?田忌和齐威王的马们可以用分别用两个数组表示,下标为马的编号,元素值为对应马的速度。先对两组马速度排序,两组双指针,分别指向本组最速のhorse和最慢的马。那么有4种情况:
1.田最速の马>齐最速の马 (这种情况会出现在齐浪费了他本来最速の马,导致他当前最速の马不如田的)
此时直接让这两匹马比赛,田田+1.
2.田最速の马<齐最速の马
此时让田最拉の马和齐最速の马比,田田-1.
3.田最速の马=齐最速の马
这时狗一下,考虑最拉の马:
3.1 田最拉の马>齐最拉の马
此时直接让这两匹马比赛,田田+1.
3.2 田最拉の马<=齐最拉の马
此时让田最拉の马和齐最速の马比!田田-1
1 |
|
多机调度问题
n个作业 m台相同的机器 每个作业都有各自的处理用时 并且作业和作业的处理都是原子化的 怎么安排才能在最短时间内完成所有作业的加工?1.最短作业优先 2.最长作业优先 我一开始竟然还认为是最短作业!但其实不是啊,应该是长作业优先!
分析:给所有作业按执行时长降序排序,然后按照贪心原则依次分配给机器们,这里就考虑到了,倘若机器数大于作业数,那直接将作业分别分配给每个机器就行,如果机器数少于作业数才需要慢慢做,如果将作业,机器,执行时长独立起来将会很乱!也不便于叙述,不如直接将他们对应绑定作为一个数据结构:分配方案allocation。那么最初的排序也要改动成为对allocation数组按照属性time降序排序,然后每一次处理新作业,就将机器编号+1,但是这就考虑到了,如果已经分配完了所有机器,而后续作业将分配给率先完成的那些之前分配的短作业所在的机器号,那么这里的处理就不必+1,而是直接将完成的allocation的机器编号赋值给当前待处理的作业即可。有点乱,整理后,清晰的思路如下:
思路:数据结构:先定义一个数据结构表示分配方案:作业编号-作业执行时长-为其分配的机器的编号。将分配方案按作业执行时长降序排序。执行算法:先判断作业数量和机器数量的大小关系,如果机器数量更多,可以直接将每个作业分配给这些机器,此情况最终的执行时长就是max i in all(allocation[i].time);如果作业更多,那就要先按贪心原则排好序,再一一入队(从0开始,遍历到machineAmount-1),此过程不需要考虑别的,第一轮分配完毕后,为了分配其余的作业(从machineAmount开始,遍历到jobAmount),考虑已经执行完毕了的短作业,最短的作业最先执行完,因此优先出队短作业(这里的队列就要设计成小根堆了),并做相应的数据处理(分析中提到的机器编号的处理),数据处理完毕后再让一个待处理作业入队即可…
三部曲:1.定义数据结构和compare函数 2.排序和必要处理 3.展示结果
1 |
|
Huffman 编码
字符集{d1…dn} 他们的对应的频率:{f1…fn}。求最优编码方案。
分析:先根据初始数据构造huffman树,再根据树求出编码。离散数学和数据结构中都学了huffman树的生成过程:每次选择两个最小频率元素作为叶子,再将它们的和作为根,将这个根作为一个叶子,而构造成此叶子的两个元素不再考虑。循环此过程。根据huffman树的生成过程可以发现贪心策略是:优先考虑最低频率的元素作为叶子节点。可以设置huffman树的节点数据结构TreeNode:包含weight,LC,RC,Parent,aChar。字符aChar和它的权值是最初就能初始化的,其它数据的初始化需要考虑建树的过程,最开始会将三个树结构属性全置为-1。在构造树的过程中再详细赋值。考虑树的构造过程,实际上会频繁比较,如果用排序会很低效,每次构造一个新节点后就排序,而且还要除去之前的两个节点,显然, 用优先级队列可以很轻松地解决,重载<使得按照weight小者优先的 原则进行。首先将所有节点进队(0~n-1),然后构造(n~2*n-1),每次构造一个新节点,就将两个节点出队,处理好数据后将新节点入队,就不再需要手动排序了。至此,树就构造好了,接下来就要根据树求编码,对于每一个叶子节点,从它开始依次找到根节点,每当寻根的过程中作为了左孩子,就将编码链接1,否则链接0,最终将这个节点的aChar和构造出的string编码建立映射关系,存入map中,循环如此求出所有字符的编码。最后分别输出。还可以求出wpl,wpl=每个叶子*他的深度。
思路:
0.数据结构定义:TreeNode { aChar, weight, LC, RC, Parent}和 NodeType{ no, aChar ,weight, override <}
1.初始化:TreeNode nodes[N]{xxxx};for(nodes 0 to 2n-1){LC=RC=Parent=-1}
2.建树:for(leaves 0 to n-1){setData push} for(constructor Nodes n to 2n-1){e1pop; e2pop; e1&e2 setData e; push e}
3.求编码:for(leaves 0 to n-1){ judge(as LC:1 or 0) =>until(root)}, set Map(char,string)}
4.求wpl:for(leaves 0 to n-1){Sum leafWeight*height }
5.输出结果:display
1 |
|
流水线作业调度
一批作业,他们都需要先在机器A上执行,然后再到B上执行,不同作业在不同机器上执行时间不同。该如何贪心呢?假设只有两个作业JobA JobB 他们在AB上的执行时间分别是(a1,b1)和(a2,b2)
作业A先执行
作业B不必等待=>总时间为a1+a2+b1+b2-b1=>不等待是因为a2>b1
作业B需要等待=>总时间为a1+a2+b1+b2-a2=>等待是因为a2<b1
因此:A先执行的最短时间:a1+a2+b1+b2-min(b1,a2)
作业B先执行
作业A不必等待=>总时间为a1+a2+b1+b2-b2=>不等待是因为a1>b2
作业A需要等待=>总时间为a1+a2+b1+b2-a1=>等待是因为a1<b2
因此:B先执行的最短时间:a1+a2+b1+b2-min(b2,a1)
综上:最短时间为:a1+a2+b1+b2+max( min(b1,a2) , min(b2,a1) ).根据此思路推导出贪心策略:
1.若a>b 则让b较大的先执行
2.若a<=b 则让a较小的先执行
Johnson算法:
1.将所有作业按照a时间和b时间的大小关系分为两组,一组的作业a<=b,记为G1, 另一组作业a>b,记为G2;
2.G1按a升序排序(最先执行最小a的作业),G2按b降序排序(最先执行最大b的作业);
3.先执行完毕所有G1组,再执行完所有G2组。
(2.确定组内顺序,3.确定组间顺序,确定顺序后直接执行就能得到最佳调度方案)
1 |
|