排序算法之:基于最小平均等待时间调度(Shortest Average Wait Time Scheduling)的贪心策略证明与算法实现
题目描述:
假设有一个单处理器系统,需要处理一组任务。每个任务用一个整数数组 tasks 表示,其中 tasks[i] 是第 i 个任务的处理时间(非负)。任务按照到达顺序依次等待处理,但处理器可以自由选择接下来处理哪个任务(即可以重新安排顺序)。目标是最小化所有任务的平均等待时间。等待时间定义为从任务到达(假设所有任务同时到达,时间为0)到开始执行的时间。请设计一个算法,输出任务的最优执行顺序,并证明该顺序确实最小化了平均等待时间。
例如,任务处理时间数组为 [3, 1, 2]。
如果按原顺序执行,等待时间分别为:任务0(3)等待0,任务1(1)等待3,任务2(2)等待3+1=4,总等待时间 = 0+3+4=7,平均等待时间 = 7/3 ≈ 2.333。
如果按处理时间升序执行,即顺序为 [1, 2, 3],则等待时间分别为:0, 1, 1+2=3,总等待时间 = 0+1+3=4,平均等待时间 = 4/3 ≈ 1.333。显然更优。
解题过程循序渐进讲解:
第一步:问题形式化
设有 n 个任务,处理时间为 p₁, p₂, …, pₙ。假设所有任务在时间 0 同时到达(这是常见简化,若不同到达时间则问题更复杂,需考虑“最短剩余时间优先”等调度策略)。
若执行顺序是任务索引的一个排列 π₁, π₂, …, πₙ,则:
- 任务
π₁开始于时间 0,等待时间W₁ = 0。 - 任务
π₂开始于时间p₍π₁₎,等待时间W₂ = p₍π₁₎。 - 任务
π₃开始于时间p₍π₁₎ + p₍π₂₎,等待时间W₃ = p₍π₁₎ + p₍π₂₎。 - ……
- 任务
πₖ的等待时间Wₖ = Σⱼ=1..k-1 p₍πⱼ₎。
总等待时间 T = Σₖ₌₁ⁿ Wₖ。
平均等待时间 = T / n。
因为 n 固定,最小化平均等待时间等价于最小化总等待时间 T。
第二步:将总等待时间写成处理时间的函数
将 T 展开:
T = W₁ + W₂ + … + Wₙ
= 0 + p₍π₁₎ + (p₍π₁₎ + p₍π₂₎) + … + (p₍π₁₎ + … + p₍πₙ₋₁₎)
数一下每个处理时间在总和中出现的次数:
p₍π₁₎出现在W₂, W₃, …, Wₙ中,即出现n-1次。p₍π₂₎出现在W₃, …, Wₙ中,即出现n-2次。- …
p₍πₖ₎出现n-k次。- 最后
p₍πₙ₎出现 0 次。
因此:
T = (n-1)·p₍π₁₎ + (n-2)·p₍π₂₎ + … + 0·p₍πₙ₎
即 T = Σₖ₌₁ⁿ (n-k)·p₍πₖ₎。
第三步:贪心策略的猜想
观察公式:T 是处理时间的加权和,权重从 n-1 递减到 0。
为了最小化 T,显然应该将最小的处理时间赋予最大的权重,即:让最小的 p 对应最大的权重 n-1,次小的 p 对应次大的权重 n-2,依此类推。
这等价于将处理时间按升序排列,先执行最短的任务,再执行次短的,……,最后执行最长的。
这就是最短处理时间优先(Shortest Processing Time first, SPT)策略。
第四步:严格证明贪心策略最优
用交换论证法(exchange argument):
假设存在一个最优顺序,其中相邻两个任务 A 和 B,A 在 B 前面执行,但 p_A > p_B(即不是按升序)。
考虑交换 A 和 B 的位置,其他任务顺序不变。
交换前,A 和 B 对总等待时间的贡献(只考虑它们自身及它们之后的任务受它们影响的部分)为:
设 A 之前的任务总时间为 S,则:
A的等待时间 =S(对总等待时间贡献一次S,因为A自身等待时间计入总和)。B的等待时间 =S + p_A。- 在
B之后的任务,每个都多等了p_A + p_B的时间(因为它们在A和B之后)。
交换后,顺序变为 B 在 A 前:
B的等待时间 =S。A的等待时间 =S + p_B。- 在
A之后的任务,每个都多等了p_B + p_A的时间(与交换前相同,因为p_A + p_B = p_B + p_A)。
比较交换前后总等待时间的变化:
交换前,A 和 B 自身等待时间之和 = S + (S + p_A) = 2S + p_A。
交换后,B 和 A 自身等待时间之和 = S + (S + p_B) = 2S + p_B。
由于 p_B < p_A,交换后减少了 p_A - p_B > 0。
而其他任务的等待时间不变(因为 A 和 B 的总处理时间相同,只是顺序不同,不影响后面任务的开始时间)。
因此交换后总等待时间更小,与“最优”矛盾。
所以,最优顺序中任意相邻两个任务必须满足 p_A ≤ p_B,即整个序列按处理时间非递减排序。
这就证明了 SPT 顺序是唯一最优的(若处理时间相同,顺序可任意)。
第五步:算法实现
算法非常简单:
- 输入数组
tasks(处理时间列表)。 - 对
tasks进行升序排序。 - 输出排序后的顺序,即为最优执行顺序。
同时可计算最小总等待时间:
total_wait = 0
current_time = 0
for t in sorted_tasks:
total_wait += current_time # 当前任务开始前的等待时间
current_time += t
average_wait = total_wait / n
或直接按公式 T = Σₖ₌₁ⁿ (n-k)·tasks_sorted[k-1] 计算。
第六步:示例演算
对 tasks = [3, 1, 2]:
排序后为 [1, 2, 3]。
总等待时间 = (3-1)1 + (3-2)2 + (3-3)3 = 21 + 12 + 03 = 4。
平均等待时间 = 4/3 ≈ 1.333。
第七步:扩展讨论
- 若任务有不同到达时间,则问题变为“最短剩余时间优先(SRTF)”调度,需用优先队列模拟。
- 若考虑截止时间,则可能变为“最早截止时间优先(EDF)”等。
- 平均等待时间最小化等价于平均周转时间(等待时间+处理时间)最小化,因为总处理时间固定。
- 该贪心策略是调度理论中的一个经典结论,是“最小化平均等待时间”的标准解法。
总结:
本题展示了如何通过将总等待时间表示为加权和,发现权重递减规律,从而推导出 SPT 贪心策略,并用交换论证法严格证明其最优性。实现上只需一次排序,时间复杂度 O(n log n),空间 O(1) 或 O(n)。