python三色旗

问题描述

        假设有一条绳子,上面有红、白、蓝三种颜色的旗子。开始时绳子上旗子的颜色并没有顺序,现要对旗子进行分类,并按照蓝、白、红的顺序排列。需要注意的是只能在这条绳子上进行移动,并且一次只能调换两个旗子,如何移动才能使旗子移动的次数最少。

        这个问题的本质就是说,在一个列表中随机分布着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色旗,不管多少种颜色的旗子。只要我们传入需要排序的旗子列表和最终排布的顺序,我们就能得到给旗子排序的最少交换步骤。

猜你喜欢

转载自blog.csdn.net/qq_40148262/article/details/130990578
今日推荐