A*寻路算法(转载)

原地址https://www.redblobgames.com/pathfinding/a-star/implementation.html。

A*算法是游戏中常用的寻路算法。A*算法在Dijkstra算法的基础上进行了一些改进,Dijkstra算法只按照离起点的值作为搜索的顺序,而实际上,离起点近并不意味着离终点近,这样的搜索顺序可能并不是最优的。但问题是,我们并不能准确知道当前点离终点的距离,因此,只能用一种估算的方式来计算。A*算法的有一个“估价函数”,该函数以任意“状态”为输入,计算出从该状态到目标状态所需代价的估计值。带有估价函数的优先队列BFS就称为A*算法。

A*算法作为游戏中常用的寻路算法是因为游戏地图通常是基于网格的,使用A*算法有更快的运行效率。

先来看图的数据结构。因为我最近在学Python,所以这里值引用了Python的写法。在原地址中,有C++和C#的版本。

class SimpleGraph:
    def __init__(self):
        self.edges = {}

    def neighbors(self,id):
        return self.edges[id]

class SquareGrid:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.walls = []

    def in_bounds(self, id):
        (x, y) = id
        return 0 <= x < self.width and 0 <= y < self.height

    def passable(self, id):
        return id not in self.walls

    def neighbors(self, id):
        (x, y) = id
        results = [(x + 1, y), (x, y - 1), (x - 1, y), (x, y + 1)]
        if (x + y) % 2 == 0: results.reverse()  # aesthetics
        results = filter(self.in_bounds, results)
        results = filter(self.passable, results)
        return results

class GridWithWeights(SquareGrid):
    def __init__(self, width, height):
        super(SquareGrid).__init__(width,height)
        self.weights = {}

    def cost(self, from_node, to_node):
        # 走到相邻单位的消耗为1,这里相当于返回一个常量
        return self.weights.get(to_node, 1)

这里的写法确实让我印象深刻,可能是因为我才学Python的缘故吧。我觉得他写得接口比较好,图的结构写得相当简洁。

BFS

为了更好的理解A*算法,作者首先介绍了BFS的搜索方式。

def breadth_first_search(graph, start, goal):
    frontier = collections.deque()
    came_from = {}
    came_from[start] = None
    frontier.append(start)
    while len(frontier) > 0:
        current = frontier.popleft()
        if current == goal:
            break
        for next in graph.neighbors(current):
            if next not in came_from:
                came_from[next] = current
                frontier.append(next)
    return came_from

Dijkstra

def dijkstra_search(graph, start, goal):
    frontier = []
    heapq.heappush(frontier,(0,start))
    came_from = {}
    cost_so_far = {}
    came_from[start] = None
    cost_so_far[start] = 0

    while len(frontier) > 0:
        current = heapq.heappop(frontier)[1]
        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
                heapq.heappush(frontier,(priority,next))
                came_from[next] = current
                
    return came_from, cost_so_far

A*

def heuristic(a, b):
    (x1, y1) = a
    (x2, y2) = b
    return abs(x1 - x2) + abs(y1 - y2)


def a_star_search(graph, start, goal):
    frontier = []
    heapq.heappush(frontier,(0,start))
    came_from = {}
    cost_so_far = {}
    came_from[start] = None
    cost_so_far[start] = 0

    while len(frontier) > 0:
        current = heapq.heappop(frontier)[1]
        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)
                heapq.heappush(frontier,(priority,next))
                came_from[next] = current

    return came_from, cost_so_far

可以看出,A*在Dijkstra的基础上改动并不大,就是改变了搜索的顺序。因此,A*算法提高搜索效率的关键,就在于能否设计出一个优秀的估价函数。估价函数的估值不能大于未来实际代价,并且还应该尽可能反映未来实际代价的变化趋势和相对大小关系,这样搜索才会较快地逼近最优解。

在寻路算法中,通常使用的估计函数有:

  • 曼哈顿距离,在二维平面就是。上面的例子就是用的曼哈顿距离。
  • 欧氏几何平面距离,在二维平面是。
  • 切比雪夫距离,在二维平面就是。

你可能感兴趣的:(A*寻路算法(转载))