程序员的算法趣题Q69: 蓝白歌会

Posted 笨牛慢耕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员的算法趣题Q69: 蓝白歌会相关的知识,希望对你有一定的参考价值。

目录

1. 问题描述

2. 解题分析

2.1 基本思路

2.2 算法流程

 2.3 实现要点

3. 代码及测试

4. 后记


1. 问题描述

问题:当有4*6=24个人,以12人为一组分为两组时,求所需移动次数最多的起始状态有多少种?

2. 解题分析

2.1 基本思路

        由于终止状态是确定的(4个),所以适合于逆向搜索(本系列前面出现过不少逆向思维解决的问题例)。

        位置交换动作是可逆的,从4个确定性的终止状态开始,通过合理的位置交换操作(确保距离最小化)寻找距离4个确定性的终止状态最远的初始状态。这个显然是图搜索中最大距离问题,适合于用广度优先搜索算法。

        另外,由于本题要求不仅找出最大距离,还要求找出满足这一条件的初始状态的个数,所以相当于要找出再广度优先搜索中出现在最远一层的所有状态的个数。

2.2 算法流程

        基于广度优先搜索的算法流程如下所示:

 2.3 实现要点

        当前人员状态用二维数组(本题解用numpy数组)表示,为了方便判断,与棋盘问题类似采用了外加围栏的方式。

        如以上算法流程图中所述,由于最后需要最终层的所有状态个数,因此visited以字典的方式记录状态以及其对应的距离。

        由于numpy数组不能用作dict的key,所以,表示状态的numpy二维数组先变换为一维数组,然后转变为tuple,用作visited的key,而距离(层数, curStep)。存入队列时,则是将前述visited的key与距离(层数, curStep)组成一个嵌套式的tuple再存入。注意队列q与visited的这两种存储方式的区别。

        在每个格子上查询是否可以交换时,只考虑它与右边一格以及下边一格交换的可能性。这样按行扫描下去,不会遗漏也不会产生不必要的重复。这个技巧也是在前面的题目中已经出现过。

3. 代码及测试

# -*- coding: utf-8 -*-
"""
Created on Mon Oct 18 07:39:04 2021

@author: chenxy
"""

import sys
import time
import datetime
import math
# import random
from   typing import List
from   collections import deque
import itertools as it
import numpy as np

N = 4
M = 6

seat = np.ones((N,M))
initState1 = seat.copy()
initState1[:,:M//2] = -1
initState2 = seat.copy()
initState2[:,M//2:] = -1
initState3 = seat.copy()
initState3[:N//2,:] = -1
initState4 = seat.copy()
initState4[N//2:,:] = -1

q = deque()
visited = dict()

q.append((tuple(np.reshape(initState1, (N*M,))),0))
q.append((tuple(np.reshape(initState2, (N*M,))),0))
q.append((tuple(np.reshape(initState3, (N*M,))),0))
q.append((tuple(np.reshape(initState4, (N*M,))),0))

visited[tuple(np.reshape(initState1, (N*M,)))] = 0
visited[tuple(np.reshape(initState2, (N*M,)))] = 0
visited[tuple(np.reshape(initState3, (N*M,)))] = 0
visited[tuple(np.reshape(initState4, (N*M,)))] = 0

ansLst = []
tStart = time.perf_counter()
while len(q) > 0:    
    curState, curStep = q.popleft()
    # print(curState,curStep)
    curExt = np.zeros((N+2,M+2))
    curExt[1:N+1,1:M+1] = np.reshape(curState, (N,M))
    for k in range(1,N+1):
        for l in range(1,M+1):
            if curExt[k,l] * curExt[k,l+1] == -1:
                curExt[k,l] *= -1
                curExt[k,l+1] *= -1
                nxtState = curExt[1:N+1,1:M+1]
                if tuple(np.reshape(nxtState, (N*M,))) not in visited:
                    q.append((tuple(np.reshape(nxtState, (N*M,))),curStep+1))
                    visited[tuple(np.reshape(nxtState, (N*M,)))] = curStep+1                    
                curExt[k,l] *= -1
                curExt[k,l+1] *= -1            
            if curExt[k,l] * curExt[k+1,l] == -1:
                curExt[k,l] *= -1
                curExt[k+1,l] *= -1
                nxtState = curExt[1:N+1,1:M+1]
                if tuple(np.reshape(nxtState, (N*M,))) not in visited:
                    q.append((tuple(np.reshape(nxtState, (N*M,))),curStep+1))
                    visited[tuple(np.reshape(nxtState, (N*M,)))] = curStep+1                    
                curExt[k,l] *= -1
                curExt[k+1,l] *= -1


ansSteps = curStep    
numStates = 0
for key in visited:
    if visited[key] == ansSteps:
        ansLst.append(key)
        numStates += 1
tCost  = time.perf_counter() - tStart

print('N={0}, M={1}, ansSteps={2}, numStates={3}, {4:6.3f}(sec)'.format(N,M,ansSteps,numStates,tCost))        

运行结果:

        N=4, M=4, ansSteps=10, numStates=64,  1.380(sec)

        N=4, M=6, ansSteps=20, numStates=4, 479.002(sec)

4. 后记

        这本书的压卷之题。但是看着顺眼一点(思路对上了^-^)所以就先拉出来办了。但是,以上题解虽然给出了正确解答,速度却实在不能恭维!需要严重优化。不过也是,压轴之题如果就这么简单地解决了也着实不像话(太不把村长当干部了)。欲知后事如何,且听下回分解。我会回来的。。。

        上一题:Q68: 异性相邻的座位安排

        本系列总目录参见:程序员的算法趣题:详细分析和Python全解

以上是关于程序员的算法趣题Q69: 蓝白歌会的主要内容,如果未能解决你的问题,请参考以下文章

程序员的算法趣题Q14: 国名接龙

程序员的算法趣题Q39: 反复排序

程序员的算法趣题Q56: 鬼脚图中的横线(思路2的Python题解)

程序员的算法趣题Q35: 0和7的回文数

程序员的算法趣题Q10: 轮盘的最大值

程序员的算法趣题Q66: 设计填字游戏