5*5的网格共有25个格子,每个格子有两个可能(墙壁或通道),共有2^25=33554432种。但是,并非每种都能构成有效的迷宫。相当惊人的一个基数,没有有效的缩小范围或者计算优化策略的话,会需要话很长的时间。
有效的迷宫是指入口(左上角)到出口(右下角)是连通的。因此判断某种通道/墙壁排列是否有效可以作为Reachability问题来处理,可以用广度优先搜索来进行判断。代码如下(注意0表示墙壁,1表示通道,而且为了方便判断,外加一圈围栏,围栏全部初始化为墙壁):
- def is_valid_maze(maze):
- '''
- Check the reachability from [1,1] to [N,N], using BFS algorithm
- '''
- start = (1,1)
- target = (N,N)
-
- curMaze = maze.copy()
- s = deque() # Used as stack, LIFO
- s.append(start)
- is_valid = False
- while len(s) > 0:
- cur = s.pop()
- if cur == target:
- is_valid = True
- # print(cur)
- curMaze[cur[0],cur[1]] = 0 # Flag it to indicate that it has already been visited.
- if curMaze[cur[0]-1,cur[1]] == 1: # Up grid
- s.append((cur[0]-1,cur[1]))
- if curMaze[cur[0]+1,cur[1]] == 1: # Down grid
- s.append((cur[0]+1,cur[1]))
- if curMaze[cur[0],cur[1]-1] == 1: # Left grid
- s.append((cur[0],cur[1]-1))
- if curMaze[cur[0],cur[1]+1] == 1: # Right grid
- s.append((cur[0],cur[1]+1))
- return is_valid
根据题意,在迷宫中A和B都必须按照“右手法则前进”,这意味着,在任何一个地方,
注意,前后左右是一个相对的概念,取决于人当时的朝向。因此:
由此可得代码如下(相当直白冗长,无暇优化^-^):
- def move(A, prev_dir):
- '''
- prev_dir: 0:down, 1: right, 2: up; 3: left
- '''
- if prev_dir == 0: # down
- if maze[A[0],A[1]-1] == 1: # A left
- A = [A[0],A[1]-1]
- A_dir = 3
- elif maze[A[0]+1,A[1]] == 1: # A down
- A = [A[0]+1,A[1]]
- A_dir = 0
- elif maze[A[0],A[1]+1] == 1: # A right
- A = [A[0],A[1]+1]
- A_dir = 1
- else: #if maze[A[0]-1,A[1]] == 1: # A up
- A = [A[0]-1,A[1]]
- A_dir = 2
- elif prev_dir == 1: # right
- if maze[A[0]+1,A[1]] == 1: # A down
- A = [A[0]+1,A[1]]
- A_dir = 0
- elif maze[A[0],A[1]+1] == 1: # A right
- A = [A[0],A[1]+1]
- A_dir = 1
- elif maze[A[0]-1,A[1]] == 1: # A up
- A = [A[0]-1,A[1]]
- A_dir = 2
- else: #if maze[A[0],A[1]-1] == 1: # A left
- A = [A[0],A[1]-1]
- A_dir = 3
- elif prev_dir == 2: # up
- if maze[A[0],A[1]+1] == 1: # A right
- A = [A[0],A[1]+1]
- A_dir = 1
- elif maze[A[0]-1,A[1]] == 1: # A up
- A = [A[0]-1,A[1]]
- A_dir = 2
- elif maze[A[0],A[1]-1] == 1: # A left
- A = [A[0],A[1]-1]
- A_dir = 3
- else: #if maze[A[0]+1,A[1]] == 1: # A down
- A = [A[0]+1,A[1]]
- A_dir = 0
- elif prev_dir == 3: # left
- if maze[A[0]-1,A[1]] == 1: # A up
- A = [A[0]-1,A[1]]
- A_dir = 2
- elif maze[A[0],A[1]-1] == 1: # A left
- A = [A[0],A[1]-1]
- A_dir = 3
- elif maze[A[0]+1,A[1]] == 1: # A down
- A = [A[0]+1,A[1]]
- A_dir = 0
- else: #if maze[A[0],A[1]+1] == 1: # A right
- A = [A[0],A[1]+1]
- A_dir = 1
- return A, A_dir
在选定一种合法的迷宫模式后,两人是否能碰上按照以下算法进行判断:
代码如下:
- def meet_or_not(maze):
- '''
- A starts from [1,1], B starts from [N,N]
- Can they meet at some grid, by reaching there at the same time?
- '''
- A = [1,1]
- B = [N,N]
- A_dir = 3 # 0:down, 1: right, 2: up; 3: left
- B_dir = 1
- while 1:
- # print('meet_or_not: ', A, B)
- A, A_dir = move(A, A_dir)
- B, B_dir = move(B, B_dir)
-
- if A==B:
- # print('meet_or_not(maze) with {0}: Can meet'.format(maze))
- return True
- #if (A == [1,1]) or (A == [N,N]) or (B == [1,1]) or (B == [N,N]):
- if (A == [N,N]) or (B == [1,1]):
- break
- # print('meet_or_not(maze) with \n{0}: Cannot meet'.format(maze))
- return False
需要注意的是,一开始受到题目描述中“所谓右手法则,就是靠着右边墙壁前进,不一定要按最短路径前进,但最终要回到入口或者到达出口”的误导,在失败判断中按以下条件处理:
if (A == [1,1]) or (A == [N,N]) or (B == [1,1]) or (B == [N,N]):
但是这个是不对的。按照右手法则,在某些特别的迷宫配置条件中,完全有可能A和/或B先回到起点然后再移动,然后再碰头。按照以上条件的话,再碰头之前回到自己出发点就判为失败了。
基于以上准备工作,搜索流程如下所示:
- # -*- coding: utf-8 -*-
- """
- Created on Sun Oct 24 13:34:24 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
-
- print(__doc__)
-
- def is_valid_maze(maze):
- '''
- Check the reachability from [1,1] to [N,N], using BFS algorithm
- '''
-
-
- def move(A, prev_dir):
- '''
- prev_dir: 0:down, 1: right, 2: up; 3: left
- '''
-
-
- def meet_or_not(maze):
- '''
- A starts from [1,1], B starts from [N,N]
- Can they meet at some grid, by reaching there at the same time?
- '''
-
-
- # # test meet_or_not()
- # N = 4
- # maze = np.zeros((N+2, N+2))
- # maze[1,1] = 1
- # maze[1,3] = 1
- # maze[2,1:5] = 1
- # maze[3,1] = 1
- # maze[3,3] = 1
- # maze[4,2:5] = 1
- # print(maze)
- # print(meet_or_not(maze))
-
-
- tStart = time.perf_counter()
- N = 5
- count = 0
- item_cnt = 0
- for item in it.product([0,1],repeat=N**2):
- item_cnt += 1
- if item_cnt%100000 == 0:
- print('item_cnt = {0}'.format(item_cnt))
- if item[0]==0 or item[N*N-1]==0:
- # The left-top and right-down corners must not be wall.
- continue
-
- # print(item)
- # Maze initialization
- # Assuming, '0' represents wall; '1' represent passage
- # Add guard for easy judge, and guard is also initialized to '0'
- maze = np.zeros((N+2, N+2))
- for k in range(N):
- for j in range(N):
- maze[k+1,j+1] = item[k*N+j]
-
- # Judge whether this maze is valid
- if not is_valid_maze(maze):
- # Not a valid maze, skip the following processing, go to the next
- # print(maze, 'invalid')
- continue
-
- # For a valid maze, decide whether A and B can meet
- if meet_or_not(maze):
- count = count + 1
-
- tCost = time.perf_counter() - tStart
- print('N={0}, count = {1}, tCost = {2:6.3f}(sec)'.format(N,count,tCost))
-
运行结果:N=5, count = 660148, tCost = 200.357(sec)
速度太慢了。需要进一步考虑优化方法。最近几道题目都这样,习惯了。。。^-^。不过作为普通的渣渣,不指望一蹴而就地搞定,而是先做一个可行的方案然后再来做优化比较可行吧^-^。