【问题标题】:Python, review and speed up A* algorithmPython,复习加速A*算法
【发布时间】:2013-10-01 20:58:31
【问题描述】:

我已经实现了一个 A* 算法来找到网格世界中两点之间的最短路径。对于较大的路径长度,该算法需要很长时间。我首先想知道我的实现是否正确,是否可以进行任何优化?

aStar 算法的参数是您的当前位置以及您希望作为(x,y) 元组前往的位置。

节点的Node.value 是一个行进方向(NSEW),getAdjacentNodes() 返回一个与我们可以行进到的节点直接相邻的节点列表。

#Perform an A* search to find the best path to the dirt
  def aStar(self, current, end):
    openSet = set()   #Set of explorable nodes
    openHeap = []     #All paths heap, lowest cost on top
    closedSet = set() #Best path so far
    curNode = Node(0, current, self.manHatDist(current, end))
    openSet.add(curNode)
    openHeap.append((curNode.cost,curNode))
    while openSet:
      curNode = heapq.heappop(openHeap)[1]
      if curNode.pos == end:
          return self.getDirections(curNode)
      openSet.remove(curNode)
      closedSet.add(curNode)
      for tile in self.getAdjacentNodes(curNode.pos):
         if tile not in closedSet:
             tile.parent = curNode
             tile.cost = self.manHatDist(curNode.pos, end) + self.euclidDist(curNode.pos, current) + curNode.cost
             if tile not in openSet:
                 openSet.add(tile)
                 heapq.heappush(openHeap, (tile.cost,tile))
    return []

  #Get the moves made to get to this endNode
  def getDirections(self, endNode):
    moves = []
    tmpNode = endNode
    while tmpNode.parent is not None:
      moves.append(tmpNode.value)
      tmpNode = tmpNode.parent
    moves.reverse()
    return moves

节点类

# Node class for A* search
class Node:
  def __init__(self, value, pos, cost):
    self.pos = pos
    self.cost = cost
    self.value = value
    self.parent = None

  def __lt__(a, b):
    if(a.cost < b.cost):
      return 1
    return 0

  def __gt__(a, b):
    if(a.cost > b.cost):
      return 1
    return 0

编辑 - 这是getAdjacentNodes 方法

  #Return all possible moves from given tile as Node objects
  def getAdjacentNodes(self, curPos):
    allMoves = ['North','South','East','West']
    posMoves = []
    for direction in allMoves:
      if(self.canMove(direction, curPos)):
        posMoves.append(Node(direction, self.getLocIfMove(curPos, direction), 0))
    return posMoves

EDIT2 - 分析结果

Profile Result Pastebin Link

【问题讨论】:

  • 代码对我来说看起来还不错,我做了一个类似的代码,在迷宫图像中寻找一条路径。通过将循环中的局部变量分配给 tile ,您可以在 for tile in ... 循环中获得一点好处,这样 Python 就不必每次使用它时都查找它。即 t = tile 并在整个循环的其余部分中使用 t,而不是 tile。您是否尝试过分析并查看它最挂在哪里? cyrille.rossant.net/profiling-and-optimizing-python-code
  • 类似for t=tile in adjacentNodes ?
  • 瓷砖中的瓷砖:t = tile 并使用 tthoughout,而不是 tile
  • 一个更好的分析链接,pymotw.com/2/profile
  • +1 到线分析器。 // 我有几个问题。为什么选择 A*(A* 几乎不是最好的算法)? Numpy 是一种选择吗?为什么你的__lt__/__gt__ 类返回1/0 而不是return a.cost &gt; b.cost?这是 Python 3 吗?避免上课是否会给您带来任何速度提升(记住tuples 有排序顺序)?

标签: python optimization path-finding


【解决方案1】:

这对我来说看起来不对:

for tile in self.getAdjacentNodes(curNode.pos):
    if tile not in closedSet:
        tile.parent = curNode
        tile.cost = self.manHatDist(curNode.pos, end) + self.euclidDist(curNode.pos, current) + curNode.cost
        if tile not in openSet:
            openSet.add(tile)
            heapq.heappush(openHeap, (tile.cost,tile))

第一个问题。新瓦片的成本计算为:

self.manHatDist(curNode.pos, end) + self.euclidDist(curNode.pos, current) + curNode.cost

但它应该是:

curNode.cost
- self.manHatDist(curNode.pos, end)
+ self.euclidDist(curNode.pos, tile.pos)
+ self.manHatDist(tile.pos, end)

(如果您更聪明地表示搜索节点的方式,您可以避免在计算新图块的成本时进行减法,但我将把它留给您。)

第二个问题。发现tile 不在closedSet 中后,您立即认为到达tile 的最佳方式是通过curNode。但是tile 不可能已经在openSet 中了吗?如果是这样,可能有另一条通往tile 的路线比通过curNode 的路线更好。* 所以这段代码应该是这样的:

for tile in self.getAdjacentNodes(curNode.pos):
    if tile not in closedSet:
        cost = (curNode.cost
                - self.manHatDist(curNode.pos, end)
                + self.euclidDist(curNode.pos, tile.pos)
                + self.manHatDist(tile.pos, end))
        if tile not in openSet or cost < tile.cost:
            tile.parent = curNode
            tile.cost = cost
            openSet.add(tile)
            heapq.heappush(openHeap, (cost,tile))

我不能说这是否会解决您的性能问题。但它可能会产生更好的结果。

* 如果self.euclidDist(curNode.pos, tile.pos) 始终为 1,则不可能有更短的路径。但如果是这种情况,为什么还要使用 euclidDist 方法呢?

【讨论】:

  • (我对 A* 了解不多)您第一点的理由是什么?又名。减法需要什么?
  • @Veedrac:您可能想阅读A* search。如果您理解了可接受性启发式但仍然不理解我的第一点,那么请回来询问。
  • @GarethRees 如果您查看我的编辑,我添加了 getAdjacentNodes 方法源。在其中,我从未将节点的成本设置为任何值。不认为把or cost &lt; tile.cost: 弄错了吗?
  • @BumSkeeter:Python 中的or 运算符是短路的,所以cost &lt; tile.cost 只有在tile not in openSet 为假时才会被评估,在这种情况下tile.cost 是在@987654342 时设置的@ 已添加到 openSet
  • 这将是一种方法。基本上,您必须确保始终为同一位置生成相同的Node,这样您就可以避免在搜索过程中重新访问该位置(因此最终会绕圈子)。我想我们已经找到了您的程序性能不佳的原因。
猜你喜欢
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-14
  • 1970-01-01
  • 2014-07-05
相关资源
最近更新 更多