第 15 题
答案与解析
第(1)小题
厨师1
18:30
厨师 0 烹制订单 1,到 18:25 已结束;厨师 1 此前未接单。18:30 时,两人都空闲且擅长川菜,而厨师 2 只能烹制粤菜。
| 时刻与事件 | 厨师 0 | 厨师 1 |
|---|---|---|
| 18:25,订单 1 完成后 | 空闲 | 空闲 |
| 18:30,先分配原有订单 3 | 接订单 3,至 19:00 | 空闲 |
| 18:30,再分配新增订单 7 | 忙碌 | 接订单 7 |
同一时刻,原有订单 3 先按最小编号原则交给厨师 0,新增订单 7 随后交给仍空闲的厨师 1,因此无需等待,实际开始时间为 18:30。
第(2)小题
i > 0 and work[i][0] < work[i-1][0]
解题思路
新记录先追加到有序列表末尾,再通过与前一项比较、交换向左移动,这是插入排序中插入一个新元素的过程。排序关键字是结束时间,即每条记录中下标 0 的数据。
程序分析
def insert_work(end_time, chef):
work.append([end_time, chef])
i = len(work) - 1
while work[i][0] > work[i-1][0]:
work[i], work[i-1] = work[i-1], work[i]
i -= 1append() 把新记录放在末尾,i 指向它当前所在的位置。循环体每交换一次,新记录就向左移动一位,i 也减 1;因此 while 条件既要判断是否还有前一项,又要判断新记录是否需要继续向左移动。
填空分析
work[i][0] > work[i-1][0]
升序排列要求前一项的结束时间不大于后一项。只有当前记录的结束时间更小,即 work[i][0] < work[i-1][0] 时,才需要交换;原来的 > 会把较大的结束时间向前移动,方向相反。
同时必须限制 i > 0。i 为 0 时已经到达表首,work[i-1] 会访问末项,不能再把它当作前一项。把边界判断放在 and 左侧,可以在 i == 0 时短路,停止比较。正确条件为:
i > 0 and work[i][0] < work[i-1][0]
结束时间相等时不交换,保留这些记录原有的先后顺序。
第(3)小题
orders[tail[chef]][5] = oid
chef = find_chef(tp)
now += 1
程序分析
idle = [True] * n
work = [] # 工作队列
wait = [] # 等待队列
head = [-1] * n
tail = [-1] * n
i = 0
now = 0
while i < len(orders) or wait or work:
...
return head主程序调用 arrange(orders, n, skills),开始调度。idle 中的 True 表示对应厨师空闲,work、wait 初始为空,head、tail 都以 -1 初始化。i 从第一个订单的下标 0 开始,now 从 0 分钟开始。只要还有未接收订单、等待订单或正在工作的厨师,外层循环就继续。
while i < len(orders) and con1(orders[i][1]) <= now:
tp = orders[i][3]
dur = orders[i][2]
②
if chef != -1:
assign(chef, i, now)
else:
wait.append([i, tp, dur])
i += 1内层循环按到达顺序处理时间不晚于 now 的订单,tp 取当前订单的菜品类型。空②产生后续分支使用的 chef:不是 -1 就调用 assign(chef, i, now),否则把订单下标、菜品类型和耗时放入 wait。处理后 i 加 1,转向下一条尚未接收的订单。这里 i、oid 保存的是 orders 的列表下标,与从 1 开始的订单编号不是同一个量。
遇到 assign(chef, i, now) 时,转入该函数。它先执行:
dur = orders[oid][2]
et = st + dur
orders[oid][1] = con2(st)
orders[oid].append(con2(et))
orders[oid].append(-1)
idle[chef] = False
insert_work(et, chef)
st 是实际开始时间,et 是结束时间。订单原有 4 个数据项,因此两次 append 分别新增下标 4、5 的字段;下标 1 原先保存到达时间,此时改为实际开始时间。厨师被标记为忙碌,工作记录按结束时间插入 work。
随后执行该厨师订单记录的首尾处理:
if head[chef] == -1:
head[chef] = oid
else:
①
tail[chef] = oid
空表时直接把当前订单设为首结点,每次都把当前订单设为尾结点。结合 head、tail 和新增的 -1 字段,可知程序用 orders 中的记录构造每位厨师的订单链表:head、tail 保存首尾订单的下标,下标 5 的字段用于保存后继订单的下标,-1 表示没有后继。记录扩展后的结构是:
| 字段下标 | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 含义 | 订单编号 | 实际开始时间 | 所需时间 | 菜品类型 | 结束时间 | 后继订单下标 |
完成分配后返回接单循环;等待订单后来调用 assign 时,也使用相同的记录和链接方式。
new_work = []
for et, cid in work:
if et <= now:
idle[cid] = True
if wait:
for k in range(len(wait)):
woid, wtp, wdur = wait[k]
chef = find_chef(wtp)
if chef != -1:
assign(chef, woid, now)
wait.pop(k) # pop:弹出索引k处的元素
break
else:
new_work.append([et, cid])
work = new_work接收当前时刻已到达的订单后,程序按 work 中的顺序检查结束时间。et <= now 时立即将该厨师标为空闲,并从 wait 的队首向后查找可分配的订单;找到后调用 assign,把订单交给找到的厨师,再用 pop(k) 从等待队列中移除,break 结束本次等待队列扫描。等待更久且当前可以处理的订单因而先被检查。
尚未结束的工作记录进入 new_work,最后用它更新 work。这里释放厨师和给等待订单派单是交替进行的,并未先把本时刻全部结束的厨师统一标为空闲。
③
if i < len(orders):
now = min(now, con1(orders[i][1]))
if work:
now = min(now, work[0][0])完成当前时刻的处理后,空③更新now。其后的两个min分别用下一条订单到达时间、最早工作结束时间限制更新后的时刻,避免越过尚未处理的事件。
整体功能
程序用wait保存等待订单,用work跟踪厨师结束工作的时刻,并为每位厨师建立订单链表。多名同类型厨师同时结束时,固定代码逐个释放厨师后立即派单,可能违背最小厨师编号规则。
例如,两位厨师均擅长川菜,依次收到以下订单:
| 订单编号 | 到达时间 | 所需时间 |
|---|---|---|
| 1 | 18:00 | 10分钟 |
| 2 | 18:01 | 19分钟 |
| 3 | 18:11 | 9分钟 |
| 4 | 18:15 | 1分钟 |
订单1、3交给厨师0,订单2交给厨师1,订单4等待。两人均在18:20结束,但厨师1的结束记录先进入work,程序先释放厨师1并把订单4交给他;题干要求此时交给厨师0。这一调度顺序缺陷位于三处空缺之外。
填空分析
①
执行到 else 时,该厨师已有订单链表。新订单下标为 oid,其后继已初始化为 -1;此时 tail[chef] 仍保存原尾订单下标,下一行才会把表尾改成 oid。因此先把原尾订单的后继指向新订单:
orders[tail[chef]][5] = oid
随后 tail[chef] = oid 更新尾指针,链表就从原尾订单延伸到新订单,原有首结点保持不变。
②
tp 已保存当前订单的菜品类型。题目给出的 find_chef(tp) 用于寻找空闲且擅长该类型的厨师,找到返回厨师编号,找不到返回 -1,正好与后面的 chef != -1 判断衔接。因此填写:
chef = find_chef(tp)
按题干规则,这个省略实现的函数应在符合条件的空闲厨师中选择编号最小者。
③
后续两条min只能限制now,不能使它增大。当剩余事件都在未来时,若不先推进时间,外层循环将一直停留在当前时刻。题目以整数分钟计时,因此填写:
now += 1
这使程序按分钟推进;下一事件仍在当前时刻时,后续min会把now限制回该时刻,继续处理。
问题反馈
解析有疑问或显示异常?请先复制题目信息,再打开反馈表单填写。
题目编号:2026_03_G3_qiangjilianmeng_q015
