【问题标题】:How to find the shortest path in 2D maze array, by only hitting walls如何通过仅撞墙找到二维迷宫阵列中的最短路径
【发布时间】:2021-04-15 13:13:00
【问题描述】:

我正在努力实现一种算法,该算法通过仅在撞到墙壁或其他障碍物时改变方向来解析 2D 迷宫阵列。

它需要做的是,给定以下数组(其中x 是开始,1 是障碍物,g 是目标)通过仅先碰到障碍物(不改变方向)找到最短路径除非到达障碍物/墙)。

[[1, 1, 1, 1, 1]
 [1, x, 0, 0, 1]
 [1, 0, 0, 0, g]
 [1, 1, 1, 1, 1]]

解决办法应该是:

[(1,1), (2,1), (2,4)]

在上面的示例中,它仅在迷宫墙壁旁边移动,但这只是因为示例非常小。总而言之,它应该只在 4 个方向上移动,并且一旦开始一个方向就不会改变它的路线,直到遇到障碍物。这是一个更直观的示例:

我设法找到了以下代码,它获取最短路径的长度但不显示路径本身。非常感谢任何帮助。

def shortestDistance(self, maze: List[List[int]], start: List[int], destination: List[int]):
start, destination = tuple(start), tuple(destination)
row, col = len(maze), len(maze[0])

def neighbors(maze, node):
    temp = []
    used = set()
    used.add(node)
    for dx, dy in [(-1, 0), (0, 1), (0, -1), (1, 0)]:
        (x, y), dist = node, 0
        while 0 <= x + dx < row and 0 <= y + dy < col and maze[x + dx][y + dy] == 0:
            x += dx
            y += dy
            dist += 1

        if (x, y) not in used:
            temp.append((dist, (x, y)))

    return temp

heap = [(0, start)]
visited = set()
while heap:
    dist, node = heapq.heappop(heap)
    if node in visited: continue
    if node == destination:
        return dist
    visited.add(node)
    for neighbor_dist, neighbor in neighbors(maze, node):
        heapq.heappush(heap, (dist + neighbor_dist, neighbor))
return -1

【问题讨论】:

    标签: python algorithm breadth-first-search path-finding maze


    【解决方案1】:

    问题:

    • 通常的广度优先搜索速度太慢,无法解决迷宫海报所需的尺寸,即 24 x 24。

    算法和代码

    普通墙跟随的关键算法修改

    • 继续朝同一个方向走,直到撞到墙
    • 在当前方向撞墙时,在当前方向的左右创建新的分支路径
    • 使用堆专注于以最小距离和方向变化次数扩展路径

    代码

    # Modification of https://github.com/nerijus-st/Maze-Solving/blob/master/left%20hand%20rule.py
    
    import numpy as np
    import heapq
    
    class Maze():
        # Movement directions
        directions = ["N", "E", "S", "W"]
    
        def __init__(self, maze):
            assert isinstance(maze, np.ndarray)                                # maze should be 2D numpy array
            self.maze = maze
            self.m, self.n = maze.shape       
            
        def solve_maze(self, start, goal):
            """
                N
            W       E
                S
    
            UP      (N) - get_neighbours()['N']
            RIGHT   (E) - get_neighbours()['E']
            DOWN    (S) - get_neighbours()['S']
            LEFT    (W) - get_neighbours()['W']
    
            maze    2D Numpy array
            start   tuple for stating position
            goal    tuple for destination
            
            Strategy: Keeps going in same direction until a wall is reached.  Then try
            form branches for both left and right directions (i.e. search both)
            """
            # Try all starting directions
            heap = [(0, 0, facing, [start]) for facing in Maze.directions]
            heapq.heapify(heap)
    
            while heap:
                dist, changes, facing, path  = heapq.heappop(heap)
                changes = -changes                                             # Negative to make earlier since using min heap
                if path[-1] == goal:
                    return self.compress_path(path)
                
                self.x, self.y = path[-1]                                      # Set current position in maze
                front_wall = self.get_front_wall(facing)                       # Coordinates next position in front
                
                if front_wall and not self.maze[front_wall] and not front_wall in path: # if Clear in front
                    # Front direction clear
                        heapq.heappush(heap, (dist+1, -changes, facing,  path + [front_wall]))
                elif len(path) > 1:
                    # Try to left
                    left_wall = self.get_left_wall(facing)                      # Coordinates to the left
                    if left_wall and not self.maze[left_wall] and not left_wall in path:                       # if clear to the left
                        left_facing = self.rotate_facing(facing, "CCW")         # Rotate left (i.e. counter clockwise)
                        heapq.heappush(heap, (dist+1, -(changes+1), left_facing, path + [left_wall]))  
                
                    # Try to the right
                    right_wall = self.get_right_wall(facing)                     # Coordinates to the right
                    if right_wall and not self.maze[right_wall] and not right_wall in path:                      # if Clear to the right
                        right_facing = self.rotate_facing(facing, "CW")               # Rotate right (i.e. clockwise)
                        heapq.heappush(heap, (dist+1, -(changes+1), right_facing, path + [right_wall]))
                
        def compress_path(self, path):
            if not path:
                return 
            
            if len(path) < 3:
                return path
        
            result = [path[0]]
            for i, p in enumerate(zip(path, path[1:])):
                direction = self.get_direction(*p)
                if i == 0:
                    prev_direction = direction
                    result.append(p[1][:])
                    continue
                    
                if len(result) > 2:
                    if prev_direction == direction:
                        result[-1] = p[1][:]
                    else:
                        result.append(p[1][:])
                else:
                    result.append(p[1][:])
                    
                prev_direction = direction
                    
            return result
                           
        def get_neighbours(self):
            ' Neighbors of current position '
            x, y = self.x, self.y
            result = {}
            result['N'] = (x-1, y) if x + 1 < self.m else None
            result['S'] = (x+1, y) if x-1 >= 0 else None
            result['E'] = (x, y+1) if y + 1 < self.n else None
            result['W'] = (x, y-1) if y -1 >= 0 else None
            return result
        
        def get_direction(self, point1, point2):
            x1, y1 = point1
            x2, y2 = point2
            if y1 == y2 and x1 - 1 == x2:
                return 'N'
            if y1 == y2 and x1 + 1 == x2:
                return 'S'
            if x1 == x2 and y1 + 1 == y2:
                return 'E'
            if x1 == x2 and y1 - 1 == y2:
                return 'W'
        
        def get_left_wall(self, facing):
            if facing == "N":
                return self.get_neighbours()['W']  # cell to the West
            elif facing == "E":
                return self.get_neighbours()['N']
            elif facing == "S":
                return self.get_neighbours()['E']
            elif facing == "W":
                 return self.get_neighbours()['S']
            
        def get_right_wall(self, facing):
            if facing == "N":
                return self.get_neighbours()['E']  # cell to the East
            elif facing == "E":
                return self.get_neighbours()['S']
            elif facing == "S":
                return self.get_neighbours()['W']
            elif facing == "W":
                return self.get_neighbours()['N']
        
        def get_front_wall(self, facing):
            return self.get_neighbours()[facing]
                
        def rotate_facing(self, facing, rotation):
            facindex = Maze.directions.index(facing)
    
            if rotation == "CW":
                if facindex == len(Maze.directions) - 1:
                    return Maze.directions[0]
                else:
                    return Maze.directions[facindex + 1]
    
            elif rotation == "CCW":
                if facindex == 0:
                    return Maze.directions[-1]
                else:
                    return Maze.directions[facindex - 1]
                
    

    测试

    测试 1

    maze = [[1, 1, 1, 1, 1],
            [1, 0, 0, 0, 1],
            [1, 0, 0, 0, 0],
            [1, 1, 1, 1, 1]]
    
    M = Maze(np.array(maze))
    p = M.solve_maze((1,1), (2,3))
    print('Answer:', p)
    # Output: Answer:  [(1, 1), (2, 1), (2, 3)]
    

    测试 2

    maze = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1],
            [1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1],
            [1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1],
            [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1],
            [1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1],
            [1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1],
            [1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
            [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1],
            [1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1],
            [1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
            [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
    
    M = Maze(np.array(maze))
    p = M.solve_maze((1,1), (23, 23))
    print('Answer:', p)
    # Output: Answer: [(1, 1), (1, 2), (1, 9), (3, 9), (3, 12), (5, 12), (5, 10), (7, 10), (7, 9), (17, 9), (17, 10), (23, 10), (23, 23)]
    

    【讨论】:

    • 您好,非常感谢您的回复! compress_path 函数未在任何地方使用。另外,如果我想获得最短路径,我会在哪里返回?目前,该程序只是挂在最后什么都不做。我正在使用更复杂的 23x23 数组对其进行测试。
    • @feedy--对不起,我在 min_path 中留下了最后一行。这应该可以解决您问题中的问题。如果有/没有,请告诉我。谢谢。
    • 嘿,没问题,感谢您抽出宝贵时间回复!它仍然只是挂在我身上,没有返回任何东西。我可以把我正在测试它的数组发给你吗?
    • @feedy--当然--试试pastbin等在线网站
    • 在此处添加:pastebin.com/UzSskyGE 起点为 (1, 1) 终点为 (23,23)
    【解决方案2】:

    您可以将其解释为图形问题。 生成一个包含所有可能运动的图表,这些运动是边缘,而可能的位置是节点。边缘应标有长度(或重量)(例如,在您的情况下可能是 6 个块)。 我猜球从某个地方开始,因此问题是(在考虑图表时):到节点 u 形成节点 v 的最短路径是什么。

    因此您可以使用Bellman–Ford algorithm。 我不会解释它,因为维基百科在这方面做得更好。 如果您不喜欢 Dijkstra 算法,还有其他一些算法可以解决您的问题。

    【讨论】:

    • 感谢您的回复,不幸的是,我真的在代码实现上苦苦挣扎。您是否知道任何可以帮助我在实践中实现它的示例?
    • Wikipedia 有一个例子。
    【解决方案3】:

    您可以使用递归生成器函数,该函数在每一步要么继续沿同一方向前进,要么在撞墙时通过寻找其他可能的方向来改变航向:

    graph = [[1, 1, 1, 1, 1], [1, 'x', 0, 0, 1], [1, 0, 0, 0, 'g'], [1, 1, 1, 1, 1]]
    d_f = [lambda x, y:(x+1, y), lambda x, y:(x+1, y+1), lambda x, y:(x, y+1), lambda x, y:(x-1, y), lambda x, y:(x-1, y-1), lambda x, y:(x, y-1)]
    def navigate(coord, f = None, path = [], s = []):
       if graph[coord[0]][coord[-1]] == 'g':
          yield path+[coord]
       elif f is None:
          for i, _f in enumerate(d_f):
             if graph[(j:=_f(*coord))[0]][j[-1]] != 1 and j not in s:
                 yield from navigate(j, f = _f, path=path+[coord], s = s+[coord])
       else:
           if graph[(j:=f(*coord))[0]][j[-1]] == 1:
              yield from navigate(coord, f = None, path=path, s = s)
           else:
              yield from navigate(j, f = f, path=path, s = s+[coord])
           
    
    start = [(j, k) for j, a in enumerate(graph) for k, b in enumerate(a) if b == 'x']
    r = min(navigate(start[0]), key=len)
    

    输出:

    [(1, 1), (2, 1), (2, 4)]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-08
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-27
      相关资源
      最近更新 更多