学习笔记:计算任务并发数
今天我学习并实现了一个经典的任务调度问题:计算最大并发数。问题描述中给定了多个任务,每个任务都有开始时间和持续时间,要求计算在任何时刻,最多有多少个任务同时进行。这个问题涉及到如何高效地跟踪多个任务的时间段,并找出在某个时刻的并发最大值。
问题理解
我们有 ( n ) 个任务,每个任务由开始时间 ( x ) 和持续时间 ( y ) 组成。任务开始的时间为 ( x ),持续时间为 ( y ),因此任务的结束时间为 ( x + y )。我们的目标是计算出在同一时刻最多有多少个任务正在进行。换句话说,就是我们需要找出最大并发数。
例如,给定输入:
n = 4 array = [[1, 2], [2, 3], [3, 5], [4, 3]]
其中: 第一个任务在第1秒开始,持续2秒(1 - 3秒)。 第二个任务在第2秒开始,持续3秒(2 - 5秒)。 第三个任务在第3秒开始,持续5秒(3 - 8秒)。 第四个任务在第4秒开始,持续3秒(4 - 7秒)。
我们需要找到,在任何时刻,最多有多少任务重叠。正确答案是3(在时间段 [3, 4] 时,三任务重叠)。
思路与解法
这个问题可以通过事件驱动的方法来解决。我们将任务的开始和结束分别视为两个“事件”。对于每个任务,生成两个事件:
- 任务开始时,并发数增加(+1)。
- 任务结束时,并发数减少(-1)。
我们将所有事件按时间排序,时间相同的情况下,结束事件先处理(因为结束事件的并发数减少,开始事件的并发数增加)。
接着,我们通过遍历这些事件,维护当前的并发数,并在遍历过程中更新最大并发数。
步骤
- 生成事件:对每个任务,生成两个事件——一个是任务开始,另一个是任务结束。
- 排序事件:按时间排序事件,如果时间相同,则结束事件排在前面。
- 遍历事件:遍历所有事件,计算当前并发数,更新最大并发数。
代码实现
def solution(n, array): events = []
# 将每个任务的开始和结束时间分别作为事件
for task in array:
start, duration = task
end = start + duration
events.append((start, 1)) # 任务开始,增加并发数
events.append((end, -1)) # 任务结束,减少并发数
# 按照时间排序,时间相同则结束事件排在前面
events.sort(key=lambda x: (x[0], x[1]))
max_concurrent = 0 # 记录最大并发数
current_concurrent = 0 # 当前并发数
# 遍历事件,更新并发数
for event in events:
current_concurrent += event[1]
max_concurrent = max(max_concurrent, current_concurrent)
return max_concurrent
测试用例
if name == "main": # 测试样例 print(solution(2, [[1, 2], [2, 3]]) == 2) # 输出: 2 print(solution(4, [[1, 2], [2, 3], [3, 5], [4, 3]]) == 3) # 输出: 3 print(solution(5, [[1, 3], [3, 4], [2, 2], [6, 5], [5, 3]]) == 3) # 输出: 3
代码解析
-
生成事件:对于每个任务,
start表示任务的开始时间,duration是任务的持续时间。通过start + duration得到任务的结束时间。每个任务产生两个事件,一个是任务开始的(start, 1),表示并发数增加;另一个是任务结束的(end, -1),表示并发数减少。 -
排序事件:事件排序按照时间来排,时间相同的事件,结束事件会排在前面,因为结束事件是对并发数的减少操作。这样可以确保在同一时刻,任务结束不影响当前并发数的计算。
-
遍历事件:遍历所有排序后的事件,并更新当前的并发数
current_concurrent。每处理一个事件,就检查当前并发数是否超过历史最大值,并更新max_concurrent。 -
返回结果:最终返回最大并发数
max_concurrent。
复杂度分析
时间复杂度:排序需要 ( O(N \log N) ) 的时间复杂度,其中 ( N ) 是事件的数量,最多为 ( 2 \times n ),因此总体时间复杂度为 ( O(n \log n) )。
空间复杂度:需要存储所有的事件,因此空间复杂度为 ( O(n) )。
由于事件的排序和遍历是主要的时间开销,因此算法的时间复杂度可以认为是 ( O(n \log n) ),对于大规模数据输入也是高效的。
测试与验证
以下是几个测试用例,涵盖了常见的场景:
-
简单情况:
print(solution(2, [[1, 2], [2, 3]])) # 输出: 2
结果是2,因为第1秒到第2秒两个任务都在下载。
-
多个任务重叠:
print(solution(4, [[1, 2], [2, 3], [3, 5], [4, 3]])) # 输出: 3
结果是3,最多有三个任务在下载的时刻是[3, 4]。
-
不完全重叠:
print(solution(5, [[1, 3], [3, 4], [2, 2], [6, 5], [5, 3]])) # 输出: 3
结果是3,最多有三个任务在下载的时刻是[3, 5]。
学习总结
事件驱动法是解决这类时间区间重叠问题的有效方法,能够高效地处理并发数计算问题。 排序与处理事件:通过将所有任务的开始和结束时间转化为事件并排序,确保能够在遍历过程中正确计算最大并发数。 通过这种方法,时间复杂度得到了有效控制,适用于更大规模的任务调度问题。
下次我可以尝试通过其他的优化方式,进一步改进算法的效率,或尝试类似问题的变种。