论贪心策略的选择 —— 对区间调度问题的再思考

论贪心策略的选择

—— 对区间调度问题的再思考

算法设计C4.1中引入了无权区间调度问题,这一问题的贪心策略为按照任务结束时间排序。

这一策略是如何得到的呢?教科书给出的方法是先想出若干可以进行排序的维度,然后通过举反例的方式去进行验证,最后删去错误的,留下正确的贪心策略。然而,这样的思考方式在面对实际问题的时候实际上很容易出错。其一,可能压根想不到正确的贪心策略(开始的策略集合中就没有正确策略);其二,可能无法举出反例否决错误的贪心策略。

那么,我们是否有其他方式来思考贪心策略呢?在此,我将尝试通过DP来反向得到贪心策略。启发这一思路的灵感来源于C6.1的有全区间调度问题。

在有权区间调度问题中,我们注意到,局部最优解未必能构成全局最优解,我们需要对每个最优子结构的结果进行组合,才能得到最后的全局最优解。又很容易注意到,无权区间调度问题实际上就是有全区间调度问题在区间权值全部为1的一个特例,或者说是一个弱化情况。很自然的可以想到,我们是否可以通过观察DP过程,反向得到正确的贪心策略呢?

这一问题的答案是肯定的。注意到,在有权区间调度问题中,我们很容易可以得到DP方程,而这一方程的前提是我们需要对任务进行预先排序。随后,我们可以通过把递归转换成循环,得到复杂度仅为O(n)的DP解(此处假设任务已经完成排序)。

那么如何对任务进行排序,以显示其先后关系呢?自然,我们需要使用任务的时间属性,即开始时间和结束时间。紧跟着的问题就是,我们应该如何用时间属性进行排序呢?单纯用开始时间,或结束时间,亦或是二者共同使用做多维度排序?对这一问题的解答,可以通过在脑海中模拟实际的算法流程得到,如下所述。

假设这样一个情景,已经通过某种顺序对任务进行了排序,现在对于任务列表中一个给定的任务,此处称作P(i),我们应如何找到其前方与其不冲突的任务呢?如果按照开始时间排序,可以想到,我们需要遍历此任务之前的所有任务,判定其结束时间,如果其结束早于开始则加入不冲突任务集合。如果按照结束时间排序,我们就可以倒序遍历,直到遇到任务P(j),其结束时间早于开始时间,我们就可以直接把P(j)的不冲突集合S(j)复制过来,然后加上自己,即当前任务P(i)。换句话说,只要我们发现这样的任务P(j),我们便可以知道,P(j)P(j)之前的任务,一定都与P(i)兼容。如果使用结束时间排序,我们可以通过串联前方的结果,来减小时间开销,但是这一串联操作在使用开始时间进行排序时是不可行的,因为开始时间并不能保证这一性质(找到P(j)后就能确定之前任务全部兼容)。由此,我们知道了,对于区间调度问题,按照结束时间排序有利于降低后续步骤的时间复杂度。
论贪心策略的选择 —— 对区间调度问题的再思考_第1张图片

然后,我们开始尝试从DP解法反推回贪心解法。先考虑DP解法,在排序之后,我们先从最后一个任务开始递归;改写成循环形式,则是我们从开头开始,对每个任务判断,是在结果集中加入任务值得,还是不加入值得。由此,我们可以得到循环中的DP方程:dp[i]=max{ dp[t[i]]+1, dp[i-1] },其中t[i]为用于保存最近的兼容任务(也即上文所述的P(j))。再回到贪心,我们注意到,第一个任务必然会被选择,而当权值为1的时候,dp[i-1]总会小于等于dp[t[i]]+1,所以我们相当于会从第一个任务开始,不断选择兼容任务,最后得到结果集。由此,我们得到了我们的贪心策略。

综上,本文描述了一种从DP出发得到贪心策略的思路,极大的降低了找不到正确贪心策略的风险。希望本文对您有所帮助。

你可能感兴趣的:(踩坑)