问题描述
假设有一条绳子,上面有红、白、蓝三种颜色的旗子。开始时绳子上旗子的颜色并没有顺序,现要对旗子进行分类,并按照蓝、白、红的顺序排列。需要注意的是只能在这条绳子上进行移动,并且一次只能调换两个旗子,如何移动才能使旗子移动的次数最少。
这个问题的本质就是说,在一个列表中随机分布着3种元素,元素的数量未知但有限,我们需要设计一个程序来交换这些元素的位置,使这个列表种的元素变成3部分,每部分中都是相同的元素。每次只能交换两个元素的位置,输出交换次数最少的操作步骤。
在列表中交换两个元素的位置很简单,难的是操作步骤要最少。我们必须要分析一下,怎样操作得到的操作步骤会最少。首先我们知道旗子的总数、每种旗子的数量以及旗子最终的排布顺序,假设红色、白色、蓝色旗子各4个,最终排布顺序为蓝、白、红。
想一想我们要怎样操作才能使交换的步骤最少,我们应该根据最终顺序来判断某些旗子的顺序是否需要交换位置,例如第3个蓝旗、第10个红旗、第11个红旗都不需要交换位置。其次在交换旗子时我们应该把要交换的旗子先交换到它最终的区间,例如第2个白旗跟第5个蓝旗交换、第4个白旗跟第6个蓝旗交换、第7个红旗跟第9个白旗交换都能把它们换到各自的最终区间。最后我们再来交换剩余需要交换的旗子,如此操作就可以使操作步骤最少。
因此我们可以把交换步骤分为两类,第一类为互惠交换,第二类为自然交换。先执行互惠交换,再执行自然交换,可以使交换步骤最少。
设计交换程序
根据上述描述,我们可以把交换程序分为两个部分,第一个部分为互惠交换,第二个部分为自然交换。
互惠交换
互惠交换时我们需要先判断出各色旗子所在的区间,然后根据棋子的区间来判断是否需要交换,以及交换时要使两个旗子都放置到各自的最终区间。如果不能满足互惠交换的棋子就不交换,留给自然交换去处理。
flag = ["红", "白", "蓝", "白", "蓝", "蓝", "红", "蓝", "白", "红", "红", "白"] # 排序旗子
order = ["蓝", "白", "红"] # 最终顺序
# 互惠交换代码
range_list = [0] # 定义各色棋子区间列表
for i in order:
range_list.append(flag.count(i)) # 先统计各色棋子的数量
for i in range(len(range_list)):
if i > 0:
range_list[i] = range_list[i - 1] + range_list[i] # 算出各色棋子在绳子上的区间
for i in range(len(order)):
for n in range(range_list[i], range_list[i + 1]): # 按最终的棋子顺序输出棋子的区间
if flag[n] != order[i]: # 判断棋子是否属于此段区间
index = order.index(flag[n]) # 找出该棋子在颜色排布列表中的索引
if order[i] in flag[range_list[index]:range_list[index + 1]]: # 判断该区间颜色的棋子是否存在于可直接交换区间中
for m in range(range_list[index], range_list[index + 1]): # 逐个输出直接交换区间的棋子
if flag[m] == order[i]: # 判断棋子是否为直接交换的棋子
flag[n], flag[m] = flag[m], flag[n] # 交换棋子
print(f'{n + 1} 旗和 {m + 1} 旗交换位置') # 打印交换位置
print(flag) # 打印交换后的棋子顺序
break # 结束循环
自然交换
自然交换时需要查看是否还有旗子没有归位到最终区间,如果有我们就把区间之外属于此区间的旗子交换到此区间,不需要做任何处理和思考,就是简单的交换。
for i in range(len(order)):
for n in range(range_list[i], range_list[i + 1]): # 按最终的棋子顺序输出棋子的区间
if flag[n] != order[i]: # 判断棋子是否属于此段区间
for x in range(range_list[i + 1], range_list[-1]): # 循环输出该区间以外的棋子
if flag[x] == order[i]: # 判断棋子是否为该区间的棋子
flag[n], flag[x] = flag[x], flag[n] # 交换棋子
print(f'{n + 1} 旗和 {x + 1} 旗交换位置') # 打印交换位置
print(flag) # 打印交换后的棋子顺序
break # 结束循环
完整代码
把互惠交换和自然交换结合起来,互惠交换在前,自然交换收尾。我们就能得到一个操作步骤最少的交换程序,完整代码如下:
flag_list = ["红", "白", "蓝", "白", "蓝", "蓝", "红", "蓝", "白", "红", "红", "白"]
order_list = ["蓝", "白", "红"]
def three_flag(flag, order):
"""
三色旗问题
:param flag: 旗子列表
:param order: 颜色排布列表
:return:
"""
range_list = [0]
for i in order:
range_list.append(flag.count(i))
for i in range(len(range_list)):
if i > 0:
range_list[i] = range_list[i - 1] + range_list[i]
for i in range(len(order)):
for n in range(range_list[i], range_list[i + 1]):
if flag[n] != order[i]:
index = order.index(flag[n])
if order[i] in flag[range_list[index]:range_list[index + 1]]:
for m in range(range_list[index], range_list[index + 1]):
if flag[m] == order[i]:
flag[n], flag[m] = flag[m], flag[n]
print(f'{n + 1} 旗和 {m + 1} 旗交换位置')
print(flag)
break
for i in range(len(order)):
for n in range(range_list[i], range_list[i + 1]):
if flag[n] != order[i]:
for x in range(range_list[i + 1], range_list[-1]):
if flag[x] == order[i]:
flag[n], flag[x] = flag[x], flag[n]
print(f'{n + 1} 旗和 {x + 1} 旗交换位置')
print(flag)
break
three_flag(flag_list, order_list)
执行结果如下:
有了这个函数,3色旗、4色旗、5色旗,不管多少种颜色的旗子。只要我们传入需要排序的旗子列表和最终排布的顺序,我们就能得到给旗子排序的最少交换步骤。