【问题标题】:A star algorithm with ability to remove N obstacle(s)一种能够消除 N 个障碍物的星型算法
【发布时间】:2020-01-12 21:15:52
【问题描述】:

为了保持我的编码清晰,我从一些典型的谜题开始。我找到了 A* 算法的实现。下一步将是对其进行调整,以便可以移除 N 个障碍物以找到最佳路径。如果迷宫如下图所示,其中 1 代表障碍:

[[0, 1, 1, 0], 
 [0, 0, 0, 0], 
 [1, 1, 1, 0], 
 [1, 0, 1, 0]]

A* 实现: 导入堆

class Cell:
    def __init__(self,x,y,passable):
        self.passable = passable
        self.x = x
        self.y = y
        self.g = 0
        self.h = 0
        self.f = 0
        self.parent = None

    def __repr__(self):
        return 'Cell((%s, %s), %s)' % (self.x, self.y, self.passable) 

class Maze:
    def __init__(self,maze_map):
        self.maze_map = maze_map
        self.cells = []
        self.maze_height = None
        self.maze_width = None
        self.cells_to_visit = []
        heapq.heapify(self.cells_to_visit)
        self.cells_visited = set()

    def initialize_cells(self):
        self.maze_height = len(self.maze_map)
        self.maze_width = len(self.maze_map[0])
        door_start = (0,0)
        door_end = (self.maze_height-1,self.maze_width-1)

        for h in range(self.maze_height):
            for w in range(self.maze_width):
                if self.maze_map[h][w] == 0:
                    passable = True
                else:
                    passable = False
                self.cells.append(Cell(w,h,passable))

        self.start = self.get_cell(*door_start)
        self.end = self.get_cell(*door_end)

    def calculate_heuristic(self,cell):
        cost = 10
        return cost * (abs(cell.x - self.end.x) + abs(cell.y - self.end.y))

    def get_cell(self, x, y):
        return self.cells[x * self.maze_height + y]

    def get_path(self):
        cell = self.end
        path = [(cell.x, cell.y)]
        while cell.parent is not self.start:
            cell = cell.parent
            path.append((cell.x, cell.y))

        path.append((self.start.x, self.start.y))
        path.reverse()
        return path

    def update_cell(self, adj, cell):
        adj.g = cell.g + 10
        adj.h = self.calculate_heuristic(adj)
        adj.parent = cell
        adj.f = adj.h + adj.g

    def get_adjacent_cells(self,cell):
        cells = []
        if cell.x < self.maze_width-1:
            cells.append(self.get_cell(cell.x+1, cell.y))
        if cell.y > 0:
            cells.append(self.get_cell(cell.x, cell.y-1))
        if cell.x > 0:
            cells.append(self.get_cell(cell.x-1, cell.y))
        if cell.y < self.maze_width-1:
            cells.append(self.get_cell(cell.x, cell.y+1))
        return cells

    def solve(self):
        heapq.heappush(self.cells_to_visit, self.start)
        while len(self.cells_to_visit):            
            cell = heapq.heappop(self.cells_to_visit)
            self.cells_visited.add(cell)

            if cell is self.end:
                return self.get_path()

            adj_cells = self.get_adjacent_cells(cell)
            for adj_cell in adj_cells:
                if adj_cell not in self.cells_visited:
                    if (adj_cell.f, adj_cell) in self.cells_to_visit:
                        if adj_cell.g > cell.g + 10:
                            self.update_cell(adj_cell, cell)
                    else:
                        self.update_cell(adj_cell, cell)
                        heapq.heappush(self.cells_to_visit, adj_cell)

if __name__ == "__main__":
    m = [[0, 1, 1, 0], 
         [0, 0, 1, 0], 
         [1, 1, 1, 0], 
         [1, 0, 1, 0]]

    N = 1 #allowed to remove 1 obstacle
    maze = Maze(map,N)
    maze.initialize_cells()
    maze.solve()

    m = [[0, 1, 1, 0], 
         [0, 0, 1, 0], 
         [1, 1, 1, 1], 
          [1, 0, 1, 0]]

    N = 2 #allowed to remove 2 obstacles
    maze = Maze(map,N)
    maze.initialize_cells()
    maze.solve()

从消除一个障碍的可能性开始,让它泛化到 N 有多容易?另外,应该如何实现这样的事情?

提前谢谢你。

【问题讨论】:

    标签: python python-3.x a-star maze


    【解决方案1】:

    首先,我想说,如果您有关于问题的信息,例如“需要移除的障碍物很少”(意味着障碍物很稀疏),那么只考虑障碍物的算法可能会是快点。但我会给出一个通用的解决方案(我把问题变成了寻路问题)和一些建议。

    1- 游戏中使用了一个技巧(好吧,至少我从游戏中知道这一点)。请记住,A* 算法有成本。例如,在游戏中,如果您希望水路比地面路径成本更高,则与地面区域相比,您在水域中增加的“g”值更多。诀窍是巧妙地使用此成本功能。在某些游戏中,当您尝试将角色从 A 点移动到 B 点并且两点之间没有空路时,角色不仅会站在那里,而且会尽可能多地走到 B 点。这是通过您为它们分配非常高的成本而不是完全丢弃无法通行的区域来完成的。这样做的结果是角色开始走到两点之间最薄的障碍物,而不是无所事事地站着。如果我们对您的问题采用这种想法,问题基本上会变成“找到最少 1 的路径”。 (我的代码中的第 46、106 和 139 行)

    2- 我通常不在 python 中做事,所以我将堆操作包装在 PriorityQueue 类中(我从其他地方获取了这个类代码,不知道在哪里)。但我认为这样管理更容易,A* 主体看起来也很清晰。

    3- 您不需要为不同的方向(右、上、左、下)编写单独的 if 语句。是的,有一种说法是它更快,但如果你真的需要这样的速度,你最好不要使用 python。 (第 92-93 行和第 122-124 行查看用法)

    4- 我使用曼哈顿距离作为启发式假设仅允许 4 个基本方向移动,如果允许对角线移动,则应将其更改为欧几里得距离,并且需要考虑其他 4 个方向。

    import time
    import random
    
    
    #  use whichever data structure you like
    import heapq
    
    
    class Node():
    
        #  maybe calculate h value inside function instead of taking as arg
        def __init__(self, parent_node, row, col, g, h):
            self.parent_node = parent_node
            self.row = row
            self.col = col
            self.g = g
            self.h = h
    
        def __lt__(self, other):
            return self.g + self.h < other.g + other.h
    
    
    
    
    class PriorityQueue: 
        def __init__(self):
            self.elements = []
    
        def empty(self):
            return len(self.elements) == 0
    
        def put(self, item, priority):
            heapq.heappush(self.elements, (priority, item))
    
        def get(self):
            return heapq.heappop(self.elements)[1]
    
    
    class Maze:
        def __init__(self, maze_map):
            self.maze_map = maze_map
            self.maze_height = len(self.maze_map)
            self.maze_width = len(self.maze_map[0])
    
            #  this number must be large enough compared to your maze size
            self.OBSTACLE_COST = 1000
    
    
        def print_maze(self):
            for row in self.maze_map:
                for col in row:
                    print(col, end='')
                print("")
    
    
        def calculate_h(self, row, col):
            #  lets use manhattan distance, since we do not go diagonally
            #convert to euclidian if diagonal movement is allowed
            return abs(row - end_row) + abs(col - end_col)
    
        def solve(self, start_row, start_col, goal_row, goal_col):
            self.start_row = start_row
            self.start_col = start_col
            self.end_row = end_row
            self.end_col = end_col
    
    
            #  initialize result variables
            found_path = []
            found_path_cost = 0
    
    
    
            #  create root A* node
            h_initial = self.calculate_h(start_row, start_col)
            s0 = Node(None, start_row, start_col, 0, h_initial)
    
    
            #  create our priority queue and put root node in it
            Q = PriorityQueue()
            Q.put(s0, s0.h+0)
    
    
            #  keep visited/closed set, (so we are performing graph search) 
            closed_set = {}
    
    
            #  these will be useful
            #these numbers correspond to RIGHT,UP,LEFT,DOWN respectively
            #for right change in y=0 and change in x = 1
            #...
            dy = [0, -1, 0, 1]
            dx = [1, 0, -1, 0]
            while (not Q.empty()):
                #  pop a node from the priority queue
                s = Q.get()
    
                if (s.row == self.end_row and s.col == self.end_col):
                    #  solved, obtain the path by following parents of nodes
                    current_node = s
                    while current_node.parent_node != None:
                        found_path.append(current_node)
                        if (self.maze_map[current_node.row][current_node.col] == 0):
                            found_path_cost += 1
                        else:
                            found_path_cost += self.OBSTACLE_COST
    
                        current_node = current_node.parent_node
    
                    found_path.reverse()
                    break
    
    
                #  expand node s
                #  here I converted current position into a tuple of
                #row and col, then converted this tuple to a string
                #so it can be used as a dictionary key
                closed_set[str((s.row, s.col))] = 1
    
    
                #  continue searching, create child nodes of current node
                s_row = s.row
                s_col = s.col
                for d in range(4): #letter "d" represent direction
                    #letter "n" represent neighbour
                    n_row = s.row + dy[d]
                    n_col = s.col + dx[d]
    
                    #  check if neighbour node is in maze bounds
                    if (n_row >= 0 and n_row < self.maze_height and
                        n_col >= 0 and n_col < self.maze_width):
    
                        #  if this neighbour is already visited (already in
                        #closed list) discard it
                        if (str((n_row, n_col)) in closed_set):
                            continue
    
                        #  calculate new g and h value for the child node
                        new_g = s.g+1 if (self.maze_map[n_row][n_col] == 0) else s.g+self.OBSTACLE_COST
                        new_heuristic = self.calculate_h(n_row, n_col)
    
    
                        #  create new child node and add it to the priority queue
    
                        new_s = Node(s, n_row, n_col, new_g, new_heuristic)
                        Q.put(new_s, new_s.g + new_s.h)
    
    
            print("Number of obstacles along the path:", int(found_path_cost/self.OBSTACLE_COST))
            print("Found path manhattan dist:", len(found_path))
            return found_path, found_path_cost
    
    if __name__ == "__main__":
    
        #
        #  MAZE 1
        #
        print("\n MAZE 1\n")
        m = [[0, 1, 1, 0], 
             [0, 0, 1, 0], 
             [1, 1, 1, 0], 
             [1, 0, 1, 0]]
        maze = Maze(m)
        maze.print_maze()
    
        start_row = 0
        start_col = 0
        end_row = len(m)-1
        end_col = len(m[0])-1
        maze.solve(start_row, start_col, end_row, end_col)
    
    
        #
        #  MAZE 2
        #
        print("\n MAZE 2\n")
        m = [[0, 1, 1, 0], 
             [0, 0, 1, 0], 
             [1, 1, 1, 1], 
              [1, 0, 1, 0]]
        maze = Maze(m)
        maze.print_maze()
    
        start_row = 0
        start_col = 0
        end_row = len(m)-1
        end_col = len(m[0])-1
        maze.solve(start_row, start_col, end_row, end_col)
    

    输出:

     MAZE 1
    
    0110
    0010
    1110
    1010
    Number of obstacles along the path: 1
    Found path manhattan dist: 6
    
     MAZE 2
    
    0110
    0010
    1111
    1010
    Number of obstacles along the path: 2
    Found path manhattan dist: 6
    

    【讨论】:

    • 感谢您的及时回复。它确实工作得很好,我仍然需要仔细看看你的实现。然而,对于更大的迷宫,我发现解决方案不是最理想的,即曼哈顿距离大于最佳距离。这种迷宫的一个例子:[[0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]] 你能解释一下这是怎么回事吗?
    • @user3488736 因为从开始到目标已经有一条清晰的路径。不需要移除障碍,算法会找到这种方式。如果您愿意,您可以将移除障碍物的“成本”更改为例如 5,因此在空方格中执行 5 步将等于跨过障碍物等。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-15
    • 1970-01-01
    • 2011-11-01
    • 2020-03-22
    • 2014-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多