贪心算法-动态规划与贪心的区别

引言

在学习完分治和动态规划之后,我们来学习贪心算法。解决问题的观察思路和解决方法的选择可如下图所示。
在这里插入图片描述
可分可以使用分治思想,如果是最优化问题,并且可以多步决策、有最优子结构,则可以使用动态规划,再进一步,如果还具有贪心选择的性质,则可以使用贪心算法。

贪心算法概念

贪心算法和动态规划很像,贪心算法主要是在动态规划上多了贪心选择性质,贪心算法是在多步决策每一步都要直接最优,而不是通过动态规划那样枚举,最后这些局部最优解组成了全局最优。

排课问题

我们利用排课问题来具体阐述贪心算法与动态规划的差别。

问题描述&分析

一间教室,多门课程要在不同时段使用这间教室,给定第i节课 A i A_i Ai S i S_i Si时间开始上,到 F i F_i Fi时间结束,上第i门课的学生数量是 W i W_i Wi,如何排课能让尽可能多的学生上课?
在这里插入图片描述
因为课程时间段可能相交产生冲突,所以排课必须是时间不相交的(如上图两个示例solution),且要使得上课学生数量最大。

解决方法

1.动态规划

首先,最简单的情况课程数n=1的时候,直接安排上唯一的课就行了,n=2的时候就会产生时间冲不冲突的问题,导致安排与否,不冲突则都安排,冲突选W大的。当n再多的时候,问题就复杂了,如何最优?

我们来按照多步决策分析一下,我们选择从后往前,看一下安不安排 A 9 A_9 A9

  • 安排:新的可选时间段缩短到 A 9 A_9 A9上课前,递归调用剩下的所有在时间段内课程

在这里插入图片描述

  • 不安排:时间段不变,去除 A 9 A_9 A9,递归调用 A 1 . . . A 8 A_1...A_8 A1...A8
    在这里插入图片描述

剩下的决策过程都是一样的,但是这里有一个小问题,如何快速找出所有在 A 9 A_9 A9上课前的所有课程?如果在选定 A 9 A_9 A9上了之后,再循环遍历挑出来,那每一次决策都要遍历就太麻烦来。所以预先对所有课程按照下课时间排序,这样我们很容易找到下课在 A 9 A_9 A9上课前的课程。

这样我们就可以写出动态规划的转移方程式了。

设对课程 A 1 , . . . , A i A_1,...,A_i A1,...,Ai的最优选择结果是OPT(i),在第i节课上课前的最近下课课程编号是pre(i)(例如pre(9)=7 )。方程式如下:
O P T ( i ) = { O P T ( p r e ( i ) ) + W i ; O P T ( i − 1 ) ; OPT(i) = \begin{cases} OPT(pre(i)) +W_i ; \\ OPT(i-1) ; \end{cases} OPT(i)={ OPT(pre(i))+Wi;OPT(i1);
对于每步决策有两个选项(选/不选),然后对应每个选项再递归调用。

伪代码如下:
在这里插入图片描述

2.贪心算法

上面的动态规划方法已经解决了这个问题,我们再看看在DP的基础上能否找出贪心规则/性质进一步使用贪心算法。我们前面动态规划的思路是对每门课进行多步决策(选不选这门课),实际上最终的解可以表示成X=[1,0,0,1,…,1]的形式,其中1表示选,0表示不选。
在这里插入图片描述
如果我们从另一个角度思考,只考虑那些要选的课,那么决策就转换成了,我每次要选哪一门课。这有9个选项,对于每一个选项,我在对除去该课和与该课冲突的课程的子集中继续选择。
在这里插入图片描述
这样转移方程式就变成了:
O P T ( S ) = max ⁡ A i ∈ S { O P T ( S ′ ) + W i } OPT(S) = \max_{A_i\in S}\{OPT(S')+W_i\} OPT(S)=AiSmax{ OPT(S)+Wi}
S是当前可选课程集合,S’是去除 A i A_i Ai和与该课程时间冲突的课程后的集合。每一轮去枚举求当前可选集合里的最大解。
伪代码如下:时间复杂度 O ( 2 n ) O(2^n) O(2n)
在这里插入图片描述
这相当于把所有的可能组合都枚举了出来,而对于集合元素数n的可能组合有 2 n 2^n 2n种,因此时间复杂度为 O ( 2 n ) O(2^n) O(2n),这与平常动态规划的时间复杂度有所不同,主要是因为没有存储动态规划数组计算的结果,例如我先选1,再选2和先选2再选1应该是一样的效果,这里出现了冗余计算。
为了引入贪心算法,下面我们考察一个简单的特例:
假设每门课均只有一个人上,上面的例子转换后如下所示:
在这里插入图片描述
在贪心中直接选择最早下课的那门 A 1 A_1 A1,因为它肯定会出现在最优解中,不需要枚举其他的情况。

下面是简单的证明:
在这里插入图片描述- 反证法
假设存在一个最优解不包括 A 1 A_1 A1 ,例如该最优解是 A 2 , A 4 , A 7 , A 8 A_2,A_4,A_7,A_8 A2,A4,A7,A8,显然我们可以用 A 1 A_1 A1替换掉 A 2 A_2 A2 ,构造新的最优解 A 1 , A 4 , A 7 , A 8 A_1,A_4,A_7,A_8 A1,A4,A7,A8,这里人数未发生变化(因为 W i = 1 Wi=1 Wi=1),新的最优解中就有了 A 1 A_1 A1

这样就相当于第一次原本要考虑1-9,枚举一遍,现在直接不需要计算2-9,直接选中了1,瞬间减少了计算。而在第1门课被选定之后,在剩下的课程中,我们依然每次都找最早下课的,但要求选的课不能和已选的冲突(上课时间要晚于最近选择的下课时间)。这样,我们相当于只用做一次循环遍历就可以找出最优解。
在这里插入图片描述

解释:首先按照下课时间对课程排序,循环遍历,发现B最早下课,将B安排上,然后到C,C虽然是新最早下课,但与B冲突,不安排,A也同理,一直到E,安排上,D,F,G与E冲突,不安排,H可行,安排上。

伪代码如下所示:时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
在这里插入图片描述
原本的排课问题只能通过传统的动态规划方法快速解决,无法直接使用贪心算法,在改变了一些条件之后(每个课的学生数量W相同),我们就可以用贪心解决,因为每一次只用找最早下课的即可(局部最优)。贪心算法相当于解决了原问题的一个简化版本,这时候贪心的算法优点代码量少,且贪心不需要额外的存储空间(动态规划数组)。

总结

贪心算法与动态规划之间的相似点:

  • 都针对最优化问题,因此看到最优化问题,首先想到动态规划与贪心
  • 最优子结构,动态规划与贪心都要寻求最优子结构,即子问题的最优解可用于组合成原问题的最优解
  • 所有的贪心算法都可以找到一个笨拙动态规划的版本

不同点:

  • 动态规划需在每一步决策时枚举所有可能的选项,只有子问题解决后,决策才能做出判断。例如 A 9 A_9 A9选与不选先要依赖前面最优选择情况,文中例子是选的情况询问 A 1 . . . A 7 A_1...A_7 A1...A7 ,不选的情况询问 A 1 . . . A 8 A_1...A_8 A1...A8
  • 贪心算法则不必枚举所有可能情况,它在当前决策下就直接选定了局部最优,而不考虑子问题的选择。减少冗余计算,但它也需要条件(在排课问题中就是每门课学生人数都要相等),这也是贪心选择的性质/规则

如何设计一个贪心算法?

  • 先设计一个动态规划算法,再看看能不能greedy-selection不枚举
  • 试错,将解的生成过程描述为一系列的选择,并尝试不同的greedy-selection规则(排课问题中,选开课时间最早的课/选时间上课最短的课/选与其他冲突最少的课/选最晚上课的课/选最早下课的课)。

猜你喜欢

转载自blog.csdn.net/qq_32505207/article/details/108295531