[Unity]寻路算法:广度优先、迪杰斯特拉、启发式、A星

以下是通过阅读这个链接(写的真挺不错,而且作者还提供了一些可视化的操作,方便理解各个算法的异同,强烈推荐),自己收获的一些浅俗的理解和总结。如有偏差和错误还望评论区指正。


1.Breadth First Search(广度优先算法)

a.没有任何约束条件的向外扩张,遍历保存所有节点的来源节点,存至came_from字典中:

frontier = Queue()
frontier.put(start )
came_from = dict() # path A->B is stored as came_from[B] == A
came_from[start] = None

while 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

b.从目标点goal出发,在came_from中逆向寻找起点,未找到起点时反复进行循环,直到找到起点为止,之后将目标点加入寻路路径中,将路径反转:

current = goal
path = []

while current != start:
   path.append(current)
   current = came_from[current]

path.append(start) # optional
path.reverse() # optional

优化.在上述a搜寻came_from时加入判断条件,若搜寻到目标节点,则跳出循环:

frontier = Queue()
frontier.put(start )
came_from = dict()
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

2.Dijkstra’s Algorithm(迪杰斯特拉算法

在某些情况下,每一步的消耗(时间、物理,等等)是不同的,所以需要新加一个参数cost_so_far来记录从起始位置到当前位置所消耗的总成本。所以在添加came_from节点时,先考虑对比一下cost,从中找出消耗最小的节点将其加入came_from。

frontier = PriorityQueue() #Queue(队列)->PriorityQueue(优先队列)
frontier.put(start, 0)
came_from = dict()
cost_so_far = dict()
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

注意事项:PriorityQueue需要返回优先返回队列中最小值(python中如果是数值的话默认会从大到小排列,其他类型的话需要自定义)。

由于最小消耗并不一定是最短路径,所以有了下面的启发式搜寻。

3.Heuristic search(启发式搜寻)

大部分情况下我们不需要向任意方向去搜寻路径,只需要去向目标点的方向去查找路径。所以在 Greedy Best First Search中我们不把cost作为优先队列去考虑,而是始终将下一个节点和终点的距离加入队列中,然后找出最短距离,取出这个节点,再从这个节点出发去寻找下一个节点,循环,直至找到终点。

frontier = PriorityQueue()
frontier.put(start, 0)
came_from = dict()
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

优点:地形简单的话,搜寻路径会很快。 

缺点:地形复杂的话,如图...

算法直奔目标点去,结果发现此路不通,然后往回搜寻,拐了个大弯,结果找到的路径并非最短路径。 

解决办法:A星算法。

4.The A* algorithm(A星算法)

Dijkstra’s Algorithm可以找到最短路径,但是却比较浪费时间;Greedy Best First Search相对来说比较快,但是却找不到最短路径。

A星算法则既使用了Dijkstra’s Algorithm来获得起点到当前位置的实际距离消耗,又使用了当前距离到终点的预估距离作为判断,将两者结果相加获得的值加入优先队列中,从中取得最小的值,即为下一个移动的目标点。

frontier = PriorityQueue()
frontier.put(start, 0)
came_from = dict()
cost_so_far = dict()
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.假如你的游戏需要全方位的寻找路径,那么优先使用Breadth First Search或者Dijkstra’s 算法。如果你的每一步消耗都是一样的,则使用Breadth First Search,反之,则选择Dijkstra’s 算法。

2.假如你只需要寻找起点到目标点路径,则优先使用A星算法。

以上算法Python、C++、C#实现示例


这是自己实现的一个demo,使用Unity+C#,效果如图:

资源链接

猜你喜欢

转载自blog.csdn.net/qq302756113/article/details/124858376