(笔记) A*启蒙

来源:互联网 发布:男士保湿 知乎 编辑:程序博客网 时间:2024/05/04 05:01

原文很精彩!


地图表示

当理解算法的时候,第一件事是理解数据,什么是输入?什么是输出?

输入:图形搜索算法,包括A*,把“图”作为输入.一个图是地点(节点)的集合和地点之间的连线(边)

A*不知道任何其他信息。它只知道图。它不知道一个节点是房间还是门口或者一个区域多大。

输出: A*算法发现的路径由节点和边组成。边是抽象的数学概念。A*告诉你从一个地点移动到另一个地点,但不会告诉你怎么移动过去的。

权衡:对于任意给定的游戏地图,有很多种方法制作一个路径发现的图给A*使用。我们可以把门作为节点,也可以把门做为边,也可以使用路径发现网格。

用于路径发现的地图不需要和你游戏地图一模一样。一个网格游戏地图可以使用非网格路径搜索图,反之亦然。图节点越少A*算法越快。网格更容易操作但是会导致很多节点。本文讲解A*算法而不是地图设计;访问我的另一篇文章获取更多图的信息。为了方便解释,本文接下来的内容我将使用网格讲解。


算法

这里写图片描述

广度优先搜索向每个方向均等地搜索。这是一个非常好用的算法,不仅仅用于常规的路径搜索,还可以用于地图生成,流场寻路,距离地图,和其他地图分析。

这里写图片描述

迪杰斯特拉算法(也被称作成本一致搜索法)让我们优先探索一部分路径,它青睐低成本的路径,而不是均等地探索所有可能的路径。我们可以给马路赋予低成本来促进走在马路上,给森林赋予高成本来避免森林,给敌人赋予高成本来避免靠近它们。当移动的成本不同时,我们使用这个算法而不是广度优先搜索算法。

这里写图片描述

A*算法是迪杰斯特拉算法的一个改版,用以优化单个目的地的寻路。迪杰斯特拉算法可以发现到所有地点的路径;A*算法只能发现到一个地点的路径。它把更高的优先权赋予给那先看起来和目标更近的路径。


广度优先搜索

这些所有算法的关键想法是我们能够跟踪被称作边界的扩展圆环。

frontier = Queue()frontier.put(start)visited = {}visited[start] = Truewhile not frontier.empty():   current = frontier.get()   for next in graph.neighbors(current):      if next not in visited:         frontier.put(next)         visited[next] = True

这个循环是本页搜索算法的本质,包括A*。但它只告诉我们怎么访问地图上的每个元素,实际上并没有构造路径。所以对于每个节点,我们需要记录它是从哪个节点来的,我们将visited数组重命名为came_from:

frontier = Queue()frontier.put(start)came_from = {}came_from[start] = Nonewhile not frontier.empty():   current = frontier.get()   for next in graph.neighbors(current):      if next not in came_from:         frontier.put(next)         came_from[next] = current

重构路径的代码很简单

current = goal path = [current]while current != start:    current = came_from[current]   path.append(current)path.append(start) # optionalpath.reverse() # optional

提早退出

frontier = Queue()frontier.put(start )came_from = {}came_from[start] = Nonewhile not frontier.empty():   current = frontier.get()   if current == goal:  //退出      break              for next in graph.neighbors(current):      if next not in came_from:         frontier.put(next)         came_from[next] = current

移动成本

在一些路径搜索的场景中,不同的移动有不同的成本。比如在文明这款游戏中,穿过平地和沙漠的成本是1而穿过森林和山可能是5.
让我们比较一下移动的次数和移动的距离

对于迪杰特斯拉算法,我们要记录移动的成本,让我们添加一个变量,cost_so_far,来记录从起始位置开始的总体移动成本。当我们从frontier中挑选location的时候,我们希望考虑移动成本的因素。为此,我们将queue变为priorityqueue。而且我们多次访问一个位置,每次访问的路径都有着不同的成本。所以,除了在location没被访问的情况下,我们需要把location记录到frontier中,如果到location的新路径的成本比之前的低,我们也需要。

frontier = PriorityQueue()frontier.put(start, 0)came_from = {}cost_so_far = {}came_from[start] = Nonecost_so_far[start] = 0while not frontier.empty():   current = frontier.get()   if current == goal:      break   for next in graph.neighbors(current):      new_cost = cost_so_far[current] + graph.cost(current, next)      if next not in cost_so_far or new_cost < cost_so_far[next]:         cost_so_far[next] = new_cost         priority = new_cost         frontier.put(next, priority)         came_from[next] = current

启发式搜索

广度有限搜索和迪杰斯特拉算法的边界(frontier)朝着各个方向扩展。如果你想寻找到多个地点的路径,这是合理的。但通常情况下我们想找一个地点。我们需要让frontier朝着目标的方向扩展得更多一点。首先,我们定义一个会告诉我们和目标之间距离的启发式函数

def heuristic(a, b):    #正方形网格上的哈夫曼距离    return abs(a.x - b.x) + abs(a.y - b.y)

在迪杰斯特拉算法中,我们使用从起始点开始的距离做为priority queue的排序标准。但这一次,在贪心搜索中,我们使用到目标的估计距离做为排序标准。和目标越近的地点越先被探索。代码使用来自广度优先搜索的优先队列,而不是来自迪杰斯特拉算法中的cost_so_far

frontier = PriorityQueue()frontier.put(start, 0)came_from = {}came_from[start] = Nonewhile not frontier.empty():   current = frontier.get()   if current == goal:      break   for next in graph.neighbors(current):      if next not in came_from:         priority = heuristic(goal, next)         frontier.put(next, priority)         came_from[next] = current

贪心算法找到的路径不一定是最短的。所以这个算法在没有很多障碍物的时候跑得很快..我们可以解决这个问题吗?当然。


A*算法

迪杰斯特拉算法可以找到最短路径,但它在不太可能的方向上浪费了很多时间,贪心算法在可能的路径上寻找,但它找到的路径可能不是最短的。A*算法同时使用了从起始点开始的实际距离,以及到目标的估计距离。

frontier = PriorityQueue()frontier.put(start, 0)came_from = {}cost_so_far = {}came_from[start] = Nonecost_so_far[start] = 0while not frontier.empty():   current = frontier.get()   if current == goal:      break   for next in graph.neighbors(current):      new_cost = cost_so_far[current] + graph.cost(current, next)      if next not in cost_so_far or new_cost < cost_so_far[next]:         cost_so_far[next] = new_cost         priority = new_cost + heuristic(goal, next)         frontier.put(next, priority)         came_from[next] = current

只要启发式函数没有过多估计距离,A*就会找到最佳路径。


更多

你准备好实现它们了吗?如果你想自己实现它们,我有一个搭配的教程,一步步教你怎么实现图,队列和路径搜索算法。

那么你在游戏地图中应该使用哪个算法?

  • 如果你想寻找到所有地点的路径,使用广度有限搜索或者迪杰斯特拉算法。
  • 如果你只想找到一个地点,使用贪心算法或者A*,当然大多数情况下A*更棒。如果你尝试使用贪心算法,你可以考虑一下使用“inadmissible”heuristic A*算法。

广度优先搜索和迪杰斯特拉算法保证找到最短路径。贪心算法不保证。A*算法在启发式的估计值不大于真实值的时候保证找到最短路径。随着启发式的估计值变得更小,A*算法就会变为迪杰斯特拉算法。当启发式的估计值变大,A*算法就会变成贪心算法。

如果想提高性能,最佳的途径就是删去图中没用的节点。如果使用网格,可以看这里。降低图的大小有利于帮助图搜索算法。然后,使用尽可能简单的算法。

0 0