WHCSRL 技术网

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

目录

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. 代码及测试

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Mon Oct 18 07:39:04 2021
  4. @author: chenxy
  5. """
  6. import sys
  7. import time
  8. import datetime
  9. import math
  10. # import random
  11. from typing import List
  12. from collections import deque
  13. import itertools as it
  14. import numpy as np
  15. N = 4
  16. M = 6
  17. seat = np.ones((N,M))
  18. initState1 = seat.copy()
  19. initState1[:,:M//2] = -1
  20. initState2 = seat.copy()
  21. initState2[:,M//2:] = -1
  22. initState3 = seat.copy()
  23. initState3[:N//2,:] = -1
  24. initState4 = seat.copy()
  25. initState4[N//2:,:] = -1
  26. q = deque()
  27. visited = dict()
  28. q.append((tuple(np.reshape(initState1, (N*M,))),0))
  29. q.append((tuple(np.reshape(initState2, (N*M,))),0))
  30. q.append((tuple(np.reshape(initState3, (N*M,))),0))
  31. q.append((tuple(np.reshape(initState4, (N*M,))),0))
  32. visited[tuple(np.reshape(initState1, (N*M,)))] = 0
  33. visited[tuple(np.reshape(initState2, (N*M,)))] = 0
  34. visited[tuple(np.reshape(initState3, (N*M,)))] = 0
  35. visited[tuple(np.reshape(initState4, (N*M,)))] = 0
  36. ansLst = []
  37. tStart = time.perf_counter()
  38. while len(q) > 0:
  39. curState, curStep = q.popleft()
  40. # print(curState,curStep)
  41. curExt = np.zeros((N+2,M+2))
  42. curExt[1:N+1,1:M+1] = np.reshape(curState, (N,M))
  43. for k in range(1,N+1):
  44. for l in range(1,M+1):
  45. if curExt[k,l] * curExt[k,l+1] == -1:
  46. curExt[k,l] *= -1
  47. curExt[k,l+1] *= -1
  48. nxtState = curExt[1:N+1,1:M+1]
  49. if tuple(np.reshape(nxtState, (N*M,))) not in visited:
  50. q.append((tuple(np.reshape(nxtState, (N*M,))),curStep+1))
  51. visited[tuple(np.reshape(nxtState, (N*M,)))] = curStep+1
  52. curExt[k,l] *= -1
  53. curExt[k,l+1] *= -1
  54. if curExt[k,l] * curExt[k+1,l] == -1:
  55. curExt[k,l] *= -1
  56. curExt[k+1,l] *= -1
  57. nxtState = curExt[1:N+1,1:M+1]
  58. if tuple(np.reshape(nxtState, (N*M,))) not in visited:
  59. q.append((tuple(np.reshape(nxtState, (N*M,))),curStep+1))
  60. visited[tuple(np.reshape(nxtState, (N*M,)))] = curStep+1
  61. curExt[k,l] *= -1
  62. curExt[k+1,l] *= -1
  63. ansSteps = curStep
  64. numStates = 0
  65. for key in visited:
  66. if visited[key] == ansSteps:
  67. ansLst.append(key)
  68. numStates += 1
  69. tCost = time.perf_counter() - tStart
  70. 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. 后记

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

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

推荐阅读