排序算法之:基于“最小平均等待时间调度”的贪心策略证明与算法实现
题目描述
在操作系统的作业调度中,我们常常需要处理一系列任务,每个任务有一个持续时间(或服务时间)。假设有 n 个任务,任务 i 的服务时间为 t_i。如果任务按照某个顺序依次处理,那么一个任务的完成时间就是它自己以及它之前所有任务的服务时间之和。一个任务 i 的等待时间 W_i 被定义为其完成时间减去其自身的服务时间(即,在它开始执行之前,它在队列中等待的总时间)。所有任务的平均等待时间为 (∑ W_i) / n。我们的目标是找到一个处理这些任务的顺序,使得平均等待时间最小。请设计一个算法,证明其正确性,并分析其复杂度。
解题过程
这个问题是“最短作业优先”(Shortest Job First, SJF)调度策略的典型应用。我们将循序渐进地解析。
第一步:问题重述与数学建模
我们有 n 个任务,每个任务 i 的服务时间为 t_i (t_i > 0)。
假设我们选择一个处理顺序的排列 P = (p1, p2, ..., pn)。
那么:
- 任务 p1 的完成时间 C_{p1} = t_{p1},等待时间 W_{p1} = 0。
- 任务 p2 的完成时间 C_{p2} = t_{p1} + t_{p2},等待时间 W_{p2} = t_{p1}。
- 任务 p3 的完成时间 C_{p3} = t_{p1} + t_{p2} + t_{p3},等待时间 W_{p3} = t_{p1} + t_{p2}。
- 依此类推,任务 pk 的等待时间 W_{pk} = ∑{j=1}^{k-1} t{pj}。
因此,总等待时间 T_W = W_{p1} + W_{p2} + ... + W_{pn} = 0 + t_{p1} + (t_{p1}+t_{p2}) + ... + (t_{p1}+t_{p2}+...+t_{p_{n-1}}) = (n-1) * t_{p1} + (n-2) * t_{p2} + ... + 1 * t_{p_{n-1}} + 0 * t_{pn} = ∑{k=1}^{n} (n - k) * t{pk}。
第二步:猜想贪心策略
观察总等待时间 T_W 的表达式:T_W = ∑{k=1}^{n} (n - k) * t{pk}。
系数 (n-k) 是一个递减序列:处理顺序中靠前的任务(k 小),其服务时间 t_{pk} 被乘的系数更大。也就是说,放在前面处理的任务,其服务时间对整个总等待时间的“贡献权重”更大。
为了最小化 T_W,一个直观的想法是:将服务时间最短的任务放在最前面处理,因为这样可以让“大权重”乘以“小数值”。换句话说,我们应该按照服务时间 t_i 从小到大的顺序(升序)来处理任务。这就是“最短作业优先”(SJF)策略。
第三步:贪心策略的严格正确性证明
证明方法:交换论证法(Exchange Argument)。
- 假设:存在一个最优调度顺序 O(一个排列),它不是按服务时间升序排列的。即,在这个顺序中,必然存在某两个相邻的任务 A 和 B,其中 A 在 B 之前执行,但 t_A > t_B。
- 考虑交换:我们现在将 A 和 B 在调度顺序中的位置互换,得到一个新的顺序 O‘。其他任务的位置保持不变。
- 分析影响:我们只需要分析这次交换对总等待时间 T_W 的影响,因为只有 A, B 以及它们之后任务的等待时间发生了变化,而它们之前的任务等待时间不变。
- 在原始顺序 O 中,设 A 和 B 开始之前,已经流逝的时间为 S。则:
- A 的等待时间:W_A = S,完成时间:C_A = S + t_A。
- B 的等待时间:W_B = S + t_A,完成时间:C_B = S + t_A + t_B。
- 在交换后的顺序 O’ 中:
- B 的等待时间:W_B' = S,完成时间:C_B' = S + t_B。
- A 的等待时间:W_A' = S + t_B,完成时间:C_A' = S + t_B + t_A。
- 在原始顺序 O 中,设 A 和 B 开始之前,已经流逝的时间为 S。则:
- 计算等待时间的变化(ΔT_W):
- 对于任务 A 和 B 本身:
- ΔW_A+B = (W_A' + W_B') - (W_A + W_B) = [(S+t_B) + S] - [S + (S+t_A)] = t_B - t_A。
- 对于 A 和 B 之后的所有任务(假设有 m 个),它们的等待时间都增加了 (t_B - t_A),因为执行完 A 和 B 的总时间从 (t_A + t_B) 变成了 (t_B + t_A)?这里需要注意!
实际上,A 和 B 的总处理时间没有变,还是 t_A + t_B。但是,B 和 A 的完成时间点发生了变化。在 O 中,B 在时刻 S+t_A+t_B 完成;在 O‘ 中,A 在时刻 S+t_B+t_A 完成。这两个时刻是相等的!这意味着,对于 A 和 B 之后的任务,它们的开始时间并没有因为这次交换而改变。因此,之后所有任务的等待时间保持不变。 - 所以,总等待时间的变化 ΔT_W = (t_B - t_A)。
- 对于任务 A 和 B 本身:
- 得出结论:因为我们假设 t_A > t_B,所以 t_B - t_A < 0。这意味着交换后,总等待时间 T_W 减少了。
- 矛盾与归纳:我们假设 O 是最优的,但通过交换一对不符合升序的相邻任务,我们得到了一个总等待时间更小的调度 O‘,这与 O 的最优性矛盾。因此,任何最优调度中,不存在服务时间更长的任务排在服务时间更短的任务之前的情况。换句话说,最优调度必然是按照服务时间非递减(升序)的顺序排列的。这个顺序就是我们提出的贪心策略——SJF。
第四步:算法实现与步骤
- 输入:一个包含 n 个正数的数组
times,其中times[i]表示任务 i 的服务时间。 - 排序:对数组
times进行升序排序。这是算法的核心,它直接实现了 SJF 策略。 - 计算总等待时间和平均等待时间(可选,用于验证和输出结果):
- 初始化
total_wait_time = 0,current_time = 0。 - 遍历排序后的
times数组:total_wait_time += current_time。// 当前任务的等待时间就是它开始前已经流逝的时间。current_time += t。 // 处理当前任务,更新时间。
- 平均等待时间
avg_wait_time = total_wait_time / n。
- 初始化
- 输出:排序后的任务顺序(即按服务时间升序排列的任务索引或服务时间列表),以及计算出的总/平均等待时间。
第五步:复杂度分析
- 时间复杂度:算法的核心是对 n 个服务时间进行排序。使用基于比较的排序算法(如快速排序、归并排序)的平均/最好时间复杂度为 O(n log n)。计算总等待时间的步骤是 O(n)。因此,总体时间复杂度为 O(n log n)。
- 空间复杂度:如果排序是原地的(如快速排序),则空间复杂度为 O(1)(不考虑递归栈则为 O(log n))。如果使用归并排序等需要额外空间的算法,则为 O(n)。我们通常说 O(1) 或 O(n),取决于使用的排序算法。
总结
本题的关键在于:
- 将最小化平均等待时间问题,转化为对任务按服务时间升序排序的问题。
- 使用交换论证法严谨地证明了“最短作业优先”(SJF)策略的最优性。交换论证是证明贪心选择性质(Greedy Choice Property)的强大工具。
- 算法实现极其简单,核心就是一次排序操作,其效率由排序算法本身决定。
这个算法模型不仅限于操作系统调度,也适用于任何需要最小化“总延迟”或“总等待成本”的场景,只要每个任务的“成本权重”是递减的(如本题中的系数 (n-k))。