您当前的位置:首页 > 计算机 > 编程开发 > 数据结构与算法

程序员的算法趣题Q63: 迷宫会合

时间:01-01来源:作者:点击数:

1. 问题描述

2. 解题分析

5*5的网格共有25个格子,每个格子有两个可能(墙壁或通道),共有2^25=33554432种。但是,并非每种都能构成有效的迷宫。相当惊人的一个基数,没有有效的缩小范围或者计算优化策略的话,会需要话很长的时间。

2.1 有效的迷宫

有效的迷宫是指入口(左上角)到出口(右下角)是连通的。因此判断某种通道/墙壁排列是否有效可以作为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

2.2 移动

根据题意,在迷宫中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

2.3 能否碰上

在选定一种合法的迷宫模式后,两人是否能碰上按照以下算法进行判断:

代码如下:

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先回到起点然后再移动,然后再碰头。按照以上条件的话,再碰头之前回到自己出发点就判为失败了。

2.4 搜索

基于以上准备工作,搜索流程如下所示:

3. 代码及测试

# -*- 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)

4. 后记

速度太慢了。需要进一步考虑优化方法。最近几道题目都这样,习惯了。。。^-^。不过作为普通的渣渣,不指望一蹴而就地搞定,而是先做一个可行的方案然后再来做优化比较可行吧^-^。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门