实验七 最佳调度问题的回溯算法
1. 实验目的
- 理解最佳调度问题的定义和要求。
- 学习回溯算法的基本原理和应用。
- 熟悉使用回溯算法解决最佳调度问题的步骤和思路。
- 实践编写回溯算法的代码,解决最佳调度问题。
- 熟练运用剪枝技巧优化回溯算法,提高算法效率。
2. 实验要求
设有 n 个任务由 k 个可并行工作的机器来完成,完成任务 i 需要时间为 。试设计一个算法找出完成这 n 个任务的最佳调度,使完成全部任务的时间最早。(要求给出调度方案)。
程序输入:
从 test 系列文件获取数据。
第一行为任务数 n 和机器个数 k。
第二行为完成任务 i 需要的时间 t i ,包含 n 个数据,以空格间隔。
例如:
3 2
2 3 4
表示有 3 个任务,2 个机器。完成 3 个任务的时间分别为 2, 3, 4。
程序输出:
输出三个测试案例所有任务完成的总时间,及调度方案。
例如:针对上述数据,耗费的总时间为 5,调度方案为机器 1(任务 1,任务 2),机器 2(任务 3)。
3. 实验内容
3.1 读取文件
# 从文件读取数据
with open('test1.txt', 'r') as file:
data = file.readline().strip().split()
n, k = int(data[0]), int(data[1]) # 任务数和机器数
times = list(map(int, file.readline().strip().split())) # 每个任务的完成时间
3.2 回溯函数
# 定义回溯函数
def backtrack(schedule, next_task):
nonlocal best_time, best_schedule # 使用非本地变量来修改外部函数中的变量
if next_task == n: # 如果所有任务都已经分配完成
completion_times = [0] * k # 用于记录每台机器的完成时间
for i in range(n):
machine = schedule[i] # 获取当前任务分配的机器
completion_times[machine] += times[i] # 更新该机器的完成时间
total_time = max(completion_times) # 计算总完成时间
if total_time < best_time: # 如果总完成时间比当前最佳完成时间短
best_time = total_time # 更新最佳完成时间
best_schedule = schedule[:] # 更新最佳调度方案
return
for machine in range(k): # 遍历每台机器
schedule[next_task] = machine # 将下一个任务分配给当前机器
backtrack(schedule, next_task + 1) # 递归进行下一个任务的分配
该回溯算法存在一个问题:该算法效率过低。
因此,采用剪枝(Pruning)方法,对搜索树进行修剪的技术,帮助减少搜索空间,提高回溯算法的效率。
3.3 剪枝函数
可以从以下三个方面考虑,使得回溯法的效率更高
1.如果当前机器在下一步操作之后已经超过了最小完成时间,则跳过该机器
if machine_times[machine] + times[next_task] >= best_time: # 剪枝:如果当前机器已经超过了最小完成时间,则跳过该机器
continue
2.如果两台机器在分配同一个任务之后具有相同的完成时间,以为上一台机器已经完成了这样的操作,所以当前机器跳过此步骤
if machine > 0 and machine_times[machine] == machine_times[machine - 1]: # 剪枝:如果两台机器在分配同一个任务之后具有相同的完成时间,则跳过
continue
3.在将一个任务分配给一台机器之后,更新当前机器的完成时间,如果这个时间已经超过了之前的最短完成时间bestTime
,则直接跳过,没有必要再进行下一层递归
schedule[next_task] = machine # 将下一个任务分配给当前机器
new_machine_times = machine_times[:] # 创建机器完成时间的副本
new_machine_times[machine] += times[next_task] # 更新当前机器的完成时间
if max(new_machine_times) < best_time: # 剪枝:如果当前机器已经超过了最小完成时间,则跳过
backtrack(schedule, next_task + 1, new_machine_times) # 递归进行下一个任务的分配
3.4 回溯法的完整实现
# 时间调度函数
def schedule_tasks(n, k, times):
best_time = float('inf') # 初始化最佳完成时间为无穷大
best_schedule = [] # 初始化最佳调度方案为空列表
# 定义回溯函数
def backtrack(schedule, next_task, machine_times):
nonlocal best_time, best_schedule # 使用非本地变量来修改外部函数中的变量
if next_task == n: # 如果所有任务都已经分配完成
if max(machine_times) < best_time: # 剪枝:如果当前机器已经超过了最小完成时间,则跳过
best_time = max(machine_times) # 更新最佳完成时间
best_schedule = schedule[:] # 更新最佳调度方案
return
for machine in range(k): # 遍历每台机器
if machine_times[machine] + times[next_task] >= best_time: # 剪枝:如果当前机器已经超过了最小完成时间,则跳过该机器
continue
if machine > 0 and machine_times[machine] == machine_times[machine - 1]: # 剪枝:如果两台机器在分配同一个任务之后具有相同的完成时间,则跳过
continue
schedule[next_task] = machine # 将下一个任务分配给当前机器
new_machine_times = machine_times[:] # 创建机器完成时间的副本
new_machine_times[machine] += times[next_task] # 更新当前机器的完成时间
if max(new_machine_times) < best_time: # 剪枝:如果当前机器已经超过了最小完成时间,则跳过
backtrack(schedule, next_task + 1, new_machine_times) # 递归进行下一个任务的分配
initial_schedule = [0] * n # 初始化任务分配方案
initial_machine_times = [0] * k # 初始化机器完成时间列表
times.sort() # 对任务的完成时间进行排序
backtrack(initial_schedule, 0, initial_machine_times) # 调用回溯函数开始搜索最佳调度方案
return best_time, best_schedule # 返回最佳完成时间和调度方案
3.5 输出结果
# 输出结果
print(f"最佳完成时间为:{best_time}") # 打印最佳完成时间
print("调度方案为:") # 打印调度方案
for i in range(k):
tasks_on_machine = [str(j+1) for j in range(n) if best_schedule[j] == i]
print(f"机器 {i+1} : 任务 {' '.join(tasks_on_machine)}") # 打印每台机器上的任务
4 实验结果
4.1 test1.txt
4.2 test2.txt
4.3 test3.txt