1035 插入与归并 (25 分)
题意描述:
根据维基百科的定义:
插入排序是迭代算法,逐一获得输入数据,逐步产生有序的输出序列。每步迭代中,算法从输入序列中取出一元素,将之插入有序序列中正确的位置。如此迭代直到全部元素有序。
归并排序进行如下迭代操作:首先将原始序列看成 N 个只包含 1 个元素的有序子序列,然后每次迭代归并两个相邻的有序子序列,直到最后只剩下 1 个有序的序列。
现给定原始序列和由某排序算法产生的中间序列,请你判断该算法究竟是哪种排序算法?
输入格式:
输入在第一行给出正整数 N (≤100);随后一行给出原始序列的 N 个整数;最后一行给出由某排序算法产生的中间序列。这里假设排序的目标序列是升序。数字间以空格分隔。
输出格式:
首先在第 1 行中输出Insertion Sort表示插入排序、或Merge Sort表示归并排序;然后在第 2 行中输出用该排序算法再迭代一轮的结果序列。题目保证每组测试的结果是唯一的。数字间以空格分隔,且行首尾不得有多余空格。
输入样例 1:
10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0
输出样例 1:
Insertion Sort
1 2 3 5 7 8 9 4 6 0
输入样例 2:
10
3 1 2 8 7 5 9 4 0 6
1 3 2 8 5 7 4 9 0 6
输出样例 2:
Merge Sort
1 2 3 8 4 5 7 9 0 6
解题思路:
Alice: 这题看起来有点麻烦诶。
Bob: 是啊,恩恩,要先判断出来 中间序列 是 插入排序 得到的还是归并排序得到的。然后再给出下一次插入排序或者归并排序的结果。
Alice: 我们可以写一个插入排序去一步一步的比较,如果某一步匹配上了就是插入排序,否则就是归并排序。
Bob: 嗯,这样肯定是可以的。应该有更快的办法吧,直接去找连续增长的最大子序列,然后看后面的元素是或否和原始序列的一样也行。你看输入一就是这样的,1 2 3 7 8连续增长,5 9 4 6 0和原始序列完全一样。
Alice: 那怎么给出下一次插入排序或者归并排序的结果呢?
Bob: 对于插入排序,要找到连续增长子序列的长度K,然后把这个序列的前K+1个元素排序,后面的元素按兵不动就行了。
Alice:归并排序呢?先找到归并排序的规模,然后再把相邻有序的序列排序?
Bob: 其实排序相邻的两个序列就是把排序的规模扩大一倍,比如说4 3 2 1,第一次归并排序是3 4 1 2,这时候归并排序的规模是2。第二次归并排序的规模是4,就变成了1 2 3 4。所以我们先找到中间序列归并排序的规模,然后把规模扩大一倍再排一下序就好了。
Alice:那找归并排序的规模就是 找连续增长的子序列的长度了?这里好像有问题ε=(´ο`*)))唉
Bob: 有问题(•ิ_•ิ)?
Alice:只看第一个 连续增长子序列好像不太对,比如说 1 2 3 4 5 6 7 8 4 3 2 1 4 3 2 1
=> 1 2 3 4 5 6 7 8 1 2 3 4 1 2 3 4
第一个连续增长的子序列长度是8,但是这个归并排序的规模应该是4啊。
Bob: Σ(⊙▽⊙"a ,哇,真的是这样的。
Alice: 所以找归并排序的规模还是要用归并排序去一次一次的归并,一次一次的匹配。要不我用匹配中间步骤的方式写,你用别的方式写,我肯定比你写得好。(^ ▽ ^)
Bob: -_- ||,那怎么找到归并排序中间序列的归并规模呢。。。
代码:
def main():
N = int(input())
# 接收输入的整数N
origin = [int(x) for x in input().split()]
# 原始序列的N个整数
middle = [int(x) for x in input().split()]
# 某排序算法产生的N个序列
answer = []
# answer 用来存储下一次排序的结果
insertion_result = is_insertion(origin, middle)
# 判断这个中间序列middle是不是由原始序列origin经过插入排序得到的
# 如果是,返回的insertion_result 就是下一次插入排序的结果
# 如果不是,返回 None
if insertion_result:
# 如果是插入排序
print("Insertion Sort")
# 先输出"Insertion Sort"
answer = insertion_result
# 把结果存到answer 中,因为我们要统一使用answer输出下一次排序的结果
else:
print("Merge Sort")
# 否则,不是插入排序,而是归并排序
answer = merge_sort(origin, middle)
# 调用merge_sort( )函数,返回归并排序下一次的结果
print(" ".join([str(x) for x in answer]))
# 使用字符串的join函数输出下一次排序的结果
def is_insertion(origin, middle):
"""
判断这个中间序列middle是不是由原始序列origin经过插入排序得到的
如果是,返回的insertion_result 就是下一次插入排序的结果
如果不是,返回 None
"""
for x, _ in enumerate(origin):
# 每次向sorted()中多增加一个元素,然后把原始序列后面未排序的部分接在后面就是插入排序的中间结果。
# 由于插入排序是从列表的第二个元素(下标为1的元素)开始的,所以我们在切片是要从 origin[:0+2]开始
temp = sorted(origin[:x + 2])
temp.extend(origin[x + 2:])
if temp == middle:
# 如果这个按照插入排序得到的中间结果等于题目中给出的中间结果
# 那我们就计算一下下一次插入排序的结果并返回
insertion_result = sorted(origin[:x + 3])
insertion_result.extend(origin[x + 3:])
# 和上面的操作类似,先将原始序列前面[:x+3]的元素排序,然后再把后面未排序的元素加进来
return insertion_result
# 最后返回下一次插入排序的结果
return None
def merge_sort(origin, middle):
"""
返回归并排序中间序列middle再进行一次归并排序后的结果
"""
step = 1
# 归并排序每次归并的步长
flag = False
# 标记是否找到了对应归并排序的中间序列
while True:
step = step * 2
# 归并排序的步长每次增加一倍,也就是说,第一次先对每2个元素排序,然后是每4个,8个等等
merge_sort_middle = []
# 存储归并排序的中间结果
for x in range(0, len(origin), step):
merge_sort_middle.extend(sorted(origin[x: x + step]))
# 对第一个长度为step的 块,第n个长度为step的块.. 排序并按序拼接这些块
if flag:
return merge_sort_middle
if merge_sort_middle == middle:
# 如果某个归并排序的中间结果等于题目中给出的中间结果,再一次归并排序
# 就可以返回了。这里把flag 赋值为True,下一次就会再归并排序一次然后返
# 回。注意这两个if语句块的位置不可变更。
flag = True
return None
# 其实在Python中如果函数没有任何返回值就会返回None,在这里只是显式的给出,提高代码的可读性。
if __name__ == '__main__':
main()
易错点:
- 插入排序从列表的第二个元素开始,如 4 3 2 1的第一次插入排序结果为 3 4 2 1,即将3的位置插入到4的位置之前。(这里对应第三个测试点)
- 我们可以用找 连续增长的 子列表的方法来 判断这个序列是插入排序还是归并排序,但无法确定归并排序的规模。根据题目,
3 1 2 8 7 5 9 4 6 0
和1 2 3 7 8 5 9 4 6 0
其中 1 2 3 7 8 就是连续增长的子序列,而后续的 5 9 4 6 0 和原始序列的后半部分是一致的。我们可以使用 连续增长的子序列 and 后续和原始序列相等 这个条件来判断中间序列是否是 插入排序得到的。但是对于归并排序,我们无法使用 连续增长的子序列 来找到规定排序的规模。 题目中给出的输入二是:3 1 2 8 7 5 9 4 0 6
和1 3 2 8 5 7 4 9 0 6
,我们观察到1 3 就是连续增长的一个子序列,此时,后续也满足两两有序如2 8 以及5 7 等等。 但是,让我们来看另一个例子:1 2 3 4 5 6 7 8 4 3 2 1 4 3 2 1
=>1 2 3 4 5 6 7 8 1 2 3 4 1 2 3 4
这里归并排序的规模应该是4而不是8,如果我们按照 连续增长子序列的长度来计算归并的规模就会出错哦。(这里对应第六个测试点)
总结: