第 15 题
答案与解析
第(1)小题
4
08:50~09:20期间,101、201、104、202四个班同时训练,每个班都需要一根绳子,所以至少需要4根。
4根绳子也能满足全部安排:
| 绳子编号 | 班级及使用时段 |
|---|---|
| 1 | 101:07:30~10:30 |
| 2 | 201:08:20~09:30 |
| 3 | 104:08:30~09:20;301:09:40~11:30 |
| 4 | 202:08:50~09:55 |
104班09:20结束后,该绳子从09:21起可复用,满足301班09:40开始的要求。因此最少需要4根。
第(2)①小题
B
程序分析
def d_sort(data,k):
i = p = q = len(data) - 1
while i > 1 and q != 0:
j = q = 0
while j < p:
if data[j][k] > data[j+1][k]:
data[j],data[j+1] = data[j+1],data[j]
q = j+1
j += 1
p = q - 1
i -= 1
return data初始p=len(data)−1,内层j从0到p−1,比较位置j与j+1。每轮先令q=0;发生交换时执行q=j+1,因此一轮结束后的q记录最后一次交换涉及的右侧位置,没有交换时q仍为0。
一轮扫描后,从位置q开始的后缀已经就位,下一轮只需继续处理0~q−1,所以原程序令p=q−1。若整轮没有交换,则p变为−1,之后因q=0退出。
另需注意,i初值为n−1而循环要求i>1,最多只执行n−2轮,少于冒泡排序一般所需的最多n−1轮。例如关键字[3,2,1]只执行一轮就返回[2,1,3]。下面两个小问均依据给出的实际程序分析,不将其视为对任意输入都能完成排序。
解题思路
内层循环从左向右比较相邻记录,若左侧第k项较大就交换,使较大的关键字向右移动,这是冒泡排序。这里用最后一次交换的位置缩小下一轮比较范围。
原写法p=q−1使下一轮最后一次比较位置q−2与q−1;改为p=q后,只增加位置q−1与q的比较。上一轮最后一次交换后,位置q及其右侧元素已就位,这次额外比较不会交换,也不会改变最后交换位置的记录。
若上一轮没有交换,则q=0,外层循环直接结束。因此两种写法返回的数据相同,选B。
第(2)②小题
-1
共用程序分析见第(2)①小题。
按第1项排序,开始时间的初始顺序为450、500、510、580、530,初始p=4。
| 轮次 | 本轮交换情况 | 扫描后的开始时间顺序 | q | 更新后的p |
|---|---|---|---|---|
| 第1轮 | 只在j=3时交换580与530 | 450、500、510、530、580 | 4 | 3 |
| 第2轮 | j=0、1、2的比较均不交换 | 450、500、510、530、580 | 0 | −1 |
第2轮结束后仍会先执行p=q−1,得到−1,下一次检查循环条件时才因q=0退出。因此p经历4、3、−1,最小值为−1。
第(3)小题
p==head
data[0][3]=cnt
head=ins(lnk,head,data[i][2],data[i][3])
程序分析
ans_cnt,ans_lst = proc(data,n)主程序把各班的训练记录传给proc。原始data每行依次为班级编号、开始时间、结束时间;proc需要返回绳子总数及附有绳子编号的各班记录。
def proc(data,n):
lnk = [] # 构造一个空链表
data = d_sort(data,1)
for i in range(n):
data[i].append(0)
cnt = 1 # 采购的绳子从1开始编号
②
lnk.append([data[0][2],data[0][3],-1])
head = 0第(3)小题将d_sort的功能约定为升序排序,此处按开始时间安排处理顺序。每行追加一个0,为后续记录分配结果预留位置;cnt从1开始计数。
lnk的首条记录为[data[0][2],data[0][3],−1],其中第0项是结束时间。后续通过lnk[p][2]转向下一结点,可知第2项保存后继结点下标,−1表示没有后继;head保存首结点下标。
for i in range(1,n):
if data[i][1] > lnk[head][0]:
data[i][3] = lnk[head][1]
else:
cnt += 1
data[i][3] = cnt
③后续班级按开始时间依次处理。若当前开始时间严格大于首结点的结束时间,就把该结点第1项赋给data[i][3];否则增加绳子数量cnt,再把新编号赋给data[i][3]。由两分支可知,data的第3项和lnk的第1项都保存绳子编号。
题干规定绳子从结束时间的下一分钟才能复用,所以比较使用严格大于。要让这一步优先找到可复用的绳子,链首需要对应最早结束的占用记录。
def ins(lnk,head,x,y): # 插入
p = head
...
return headins接收链表、头指针和待插入的两个数据项x、y,先从首结点开始查找,最后返回插入后的头指针。
while p != -1 and lnk[p][0] <= x: # 找到插入位置
q = p
p = lnk[p][2]当当前结点第0项不大于x时,先让q保存该结点下标,再让p沿第2项转到后继。循环结束时,p指向第一个第0项大于x的结点,或为−1表示到达表尾;若已经向后移动过,q就是插入位置的前驱结点下标。因此该遍历用于保持第0项升序。
if ①:
lnk.append([x,y,head])
head = len(lnk)-1
else:
lnk.append([x,y,p])
lnk[q][2] = len(lnk)-1第一个分支把新结点的后继设为原head,再让head指向新结点,完成首部插入。另一个分支让新结点连接p,再把前驱q的后继改为新结点,完成中间或尾部插入。append只增加存储结点,链接修改决定它在链表中的逻辑位置。
ans_lst = d_sort(ans_lst,0) # 根据班级编号进行排序
for i in range(len(ans_lst)): # 输出各班级训练用的绳子编号
print(ans_lst[i][0],ans_lst[i][3])proc返回后,按班级编号重新排列结果,并输出每个班与所分配绳子编号的对应关系;这次排序只用于整理输出顺序。
填空分析
①
首部插入发生在遍历没有向后移动时,此时p仍等于head,填写p==head。空链表中p和head同时为−1,也会进入这一分支。若p已经离开head,说明循环至少执行过一次,q已经保存前驱下标,可以使用后面的lnk[q][2]完成链接。
②
第一个班使用编号为1的绳子,cnt已设为1;其记录追加的第3项用于保存绳子编号,并且下一句会读取data[0][3]建立链表结点,所以填写:
data[0][3]=cnt
③
两个分支都已为当前班确定data[i][3],还需把它的结束时间data[i][2]与绳子编号一起插入链表。ins可能改变首结点,必须用返回值更新head,填写:
head=ins(lnk,head,data[i][2],data[i][3])
这条语句完成插入,但原程序在复用绳子后没有移除旧的首结点,会留下该绳子的过期结束时间。例如三个训练区间依次为[0,1]、[2,10]、[3,9]:第二个班复用绳子1后,旧的结束时间1仍在链首,第三个班也会被分配绳子1,而后两个班的训练时间重叠。这说明原程序还缺少复用后移除过期结点的操作。
问题反馈
解析有疑问或显示异常?请先复制题目信息,再打开反馈表单填写。
题目编号:2026_05_G3_xianyujiaoyan_q015
