A*算法与启发函数

本文介绍了A*算法的历史和发展,解释了为什么称为A*算法,并通过对比Dijkstra算法和贪心搜索算法,阐述了A*算法的工作原理及其实现方式。

A*算法为什么叫这个名

这个从wiki上看来的,一开始是57年提出的Dijkstra算法,然后64年Nils Nilsson提出了A1算法,是一个启发式搜索算法,而后又被改进成为A2算法,直到68年,被Peter E. Hart改进成为A*算法,为什么叫A*呢,因为原作者借鉴了统计学方面的一个*上标,在统计学中,一个变量加上 * 表示这个变量的最优解,所以作者认为他们是最优解,是前人(A算法)的集大成者,所以叫A*。

BFS和Dijkstra算法

要讲A*算法就必须去讲Dijkstra算法,因为Dijkstra算法可以看成A*算法的特例。而BFS又可以看成Dijkstra的特例, 
对于BFS算法,python代码如下:

frontier = Queue()
frontier.put(start)
came_from = {}
came_from[start] = None

while 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

对于Dijkstra算法,python代码如下:

frontier = PriorityQueue()   #这个地方用优先队列了
frontier.put(start, 0)
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0

while 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Dijkstra算法是一个贪心算法/也可以看成动规。其是每次都找离起点最近的点 
Dijkstra算法保证能得到最优解,但是扫描的区域会比较的大。 
这里写图片描述

贪心搜索算法(Greedy Best First Search)

Dijkstra算法搜索的空间太大了,能不能利用网格图的一些特点来简化搜索呢?想到了贪心算法。 
贪心搜索算法,是每次找离终点距离最近的点。 
另外如何定义这个距离呢,一般网格图是用曼哈顿距离来定义的。即两个点之间坐标差的绝对值之和。

def heuristic(a, b):
   # Manhattan distance on a square grid
   return abs(a.x - b.x) + abs(a.y - b.y)

frontier = PriorityQueue()
frontier.put(start, 0)
came_from = {}
came_from[start] = None

while 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

这里写图片描述

启发函数的性质

那么能不能综合上面Dijkstra算法得到最优解和贪心算法速度快的特点,有更好的办法呢? 
注意一下这个地方,Dijkstra算法是适用于任何图算法找最短距离均可的,但是用到启发式算法的话,大部分情况下会是一个方格图,因为只有方格图才能比较好的估算从当前点到终点的距离】 
那么我们可以先定义一个估算函数 

f(n)=g(n)+h(n)f(n)=g(n)+h(n)

其中 g(n)g(n)表示起点到当前点 nn实际走的代价, h(n)h(n)表示当前点 nn到终点的估算代价。 
所以上面两个合起来就是其走当前点到终点的总代价函数 f(n)f(n) 
h(n)h(n)这个估计函数不同的估计情况,结果也不会相同。

  1. 先考虑极端情况, 如果h(n)=0h(n)=0的情况下,只有g(n)g(n)起作用,那么A*算法就是Dijkstra算法。
  2. 如果h(n)h(n)始终小于等于实际nn点到终点的距离,那么必然能够保证A*算法找的解就是最优解。而且h(n)h(n)越小,则A*扩展的节点也就越多,A*算法运行的也就越慢。
  3. 如果h(n)h(n)始终都等于实际nn点到终点的距离,那么A*算法只会严格走从起点到终点的最短路径。虽然这种情况一般不可能发生,当然一些特殊情况下会发生【比如没有障碍物的情况】。
  4. 如果h(n)h(n)有时候大于实际nn点到终点的距离,那么不能保证A*算法能够找到最短路径
  5. 另外一种极端情况,就是如果只有h(n)h(n)发挥作用,则A*算法就相当于贪心算法。

A*算法

一般选择曼哈顿距离的A*算法被称为simply A算法,不过因为这种方法被用的最广泛,所以一般不加以区分了。实际上还有很多别的变种的。 
A*算法的python代码,可以看到跟Dijkstra算法非常像啦,只不过加到队列里的优先级是f(n)f(n)值,而不是g(n)g(n)

frontier = PriorityQueue()
frontier.put(start, 0)
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0

while 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

A*算法跑的时间要小于Dijkstra,同时能够保证找的路径是最短路径。 
这里写图片描述

附录

关于这些路径搜索算法的程序详见:http://www.redblobgames.com/pathfinding/a-star/implementation.html 
上面的PriorityQueue()详见下面的实现:

import heapq

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]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

参考资料

以下两个资料写的都非常的好,第一个

  1. Introduction to A*介绍的那个A*算法,里面各种动画非常丰富。
  2. Pathfinding这个资料非常的长,是1资料的加长版,里面详细讲了各种路径搜索算法,和改进
### A*算法 A*算法的核心思想是结合实际代价和启发式估计,以高效地搜索图形中的最优路径。通过在评估函数中权衡实际代价和启发式估计,A*算法能够在保证找到最优路径的同时,尽可能减小搜索的时间和空间开销,是解决路径规划问题的一种高效而灵活的算法[^4]。 A*算法具有完备性,若解存在,它将找到最优解;当启发式函数满足一致性条件时,还能保证找到最优解;同时具有可配置性,可以根据不同问题和启发式函数来配置该算法[^2]。 ### 启发函数 启发式函数,在路径规划领域尤其是A*算法中扮演着关键角色。它是一种估算从当前节点到达目标节点成本的方法,能够指导算法沿着最有希望的方向搜索,从而减少计算量并提高效率。启发式函数通常用符号 h(n) 表示,它为每一个节点n提供一个估计成本值[^1]。 一个好的启发式函数应该满足可接受性和一致性。可接受性指启发式函数不应高估从节点n到目标节点的实际代价,以保证找到的路径是最短路径;一致性要求对于任意节点n和它的邻居节点m,启发式函数应满足h(n) ≤ cost(n,m) + h(m),以保证搜索算法的正确性[^3]。 ### 应用 A*算法和启发式函数在路径规划领域应用广泛,如游戏开发中角色的寻路、机器人导航、地图导航等场景,通过合理选择启发式函数,能够快速找到最优路径,提高系统效率。 ### 代码示例 以下是一个简单的Python示例,用于实现A*算法: ```python import heapq def heuristic(a, b): # 曼哈顿距离作为启发函数 return abs(a[0] - b[0]) + abs(a[1] - b[1]) def astar(array, start, goal): neighbors = [(0, 1), (0, -1), (1, 0), (-1, 0)] close_set = set() came_from = {} gscore = {start: 0} fscore = {start: heuristic(start, goal)} oheap = [] heapq.heappush(oheap, (fscore[start], start)) while oheap: current = heapq.heappop(oheap)[1] if current == goal: data = [] while current in came_from: data.append(current) current = came_from[current] return data close_set.add(current) for i, j in neighbors: neighbor = current[0] + i, current[1] + j tentative_g_score = gscore[current] + heuristic(current, neighbor) if 0 <= neighbor[0] < len(array): if 0 <= neighbor[1] < len(array[0]): if array[neighbor[0]][neighbor[1]] == 1: continue else: # array bound y walls continue else: # array bound x walls continue if neighbor in close_set and tentative_g_score >= gscore.get(neighbor, 0): continue if tentative_g_score < gscore.get(neighbor, 0) or neighbor not in [i[1] for i in oheap]: came_from[neighbor] = current gscore[neighbor] = tentative_g_score fscore[neighbor] = tentative_g_score + heuristic(neighbor, goal) heapq.heappush(oheap, (fscore[neighbor], neighbor)) return None # 示例使用 maze = [ [0, 0, 0, 0], [1, 1, 0, 1], [0, 0, 0, 0], [0, 1, 1, 0] ] start = (0, 0) goal = (3, 3) path = astar(maze, start, goal) print(path) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值