寻路算法 Pathfinding

今天我在学 UGUI 的一般使用方法,然后在画 grid,除了画热力图之外,还开始了解用于处理寻路的算法 A*
寻路算法是图搜索算法,我打算不用 Unity 自带的寻路组件,自己简单的实现一个 A* 算法练习

简单来说寻路算法就是需要找出地图上两个点之间权重最低的路线,这里为什么要用权重而不是距离呢?因为有时候并不需要距离最短,也有可能要求时间最短,或者你开发游戏的时候某一条路线上奖励分多,所以要求又变成了“奖励分最多”的路线
寻路算法有几种,最简单的图搜索算法为广度优先搜索(BFS)
数据结构中的数据为顶点以及可选的权重,寻路算法它并不关心你的节点周围是山是墙还是水,也不关心你是走路是跑步还是开直升飞机前往目标点,它需要
输入的数据是:顶点权重
输出给你的是最低权重路径

四种寻路算法,
Breadth First Search (BFS) 广度优先搜索,
Dijkstra's Algorithm 也叫 Uniform Cost Search
Greedy Best First Search 贪心算法,
A* 也叫 A-star

我该使用哪种算法?

多目的地,权重相同, BFS;权重不同 Dijkstra's Algorithm
单目的地,A*

Breadth First Search (BFS)

平均的向所有方向进行计算
理解 BFS 2 个关键的地方:
边境节点 frontier
已探索节点

frontier = Queue() # 用于保存当前节点的所有边境,FIFO
frontier.put(start) # 将第一个节点放入,作为起点
reached = set() # 保存已探索节点(因为 reached 主要用于查找,所以使用查找最优化的 O(1) Set 集合)
reached.add(start) # 将第一个节点标记为已探索,这样在 Loop2 中的循环就会直接跳过

while not frontier.empty(): # Loop1: 开始遍历当前节点的所有周围节点(边境节点)
	current = frontier.get() # 获取当前边境的首个元素(FIFO 出队)
	for next in graph.neighbors(current): # Loop2: 遍历当前节点的所有邻居节点
	    if next not in reached: # Judgment1: 如果 邻居节点next 未被探索过
	        frontier.put(next) # 将 邻居节点next 入队边境队列(FIFO 入队)
	        reached.add(next) # 将 邻居节点next 加入已探索节点集合

但是 BFS 并没有直接给出两点的最短距离,上面的代码只是为所有节点遍历一遍,所以还需要进一步处理

frontier = Queue()
frontier.put(start)
came_from = dict() # 用来保存节点关系的,存储节点的指向,作用为给定一个节点,输出这个节点的下一个节点,你可以理解为为每个节点安排一个上级节点(有没有想起链表结构)
came_from[start] = None # start 是原点,并且不指向任何一个节点

while not frontier.empty():
    current = frontier.get()
    for next in graph.neighbors(current):
        if next not in came_from: # 如果邻居节点不在 came_from 中
            frontier.put(next) # 将邻居节点入队
            came_from[next] = current # 这句比较关键

这样做了以后会将所有节点都处理一遍自己的指向,为每一个节点找了个上级节点,,形成一个超大节点邻接表

这种感觉就像一群人在操场上站一个方队,然后大家一起排的整整齐齐,每个人并不需要知道问题的全貌,只需要知道自己的下一个人是谁,然后你作为方队队长一个人一个人的问,最后就能找到从第一个问话的人到另一个目标人的路径一样

路径通常保存的是边,但是存储节点会更容易一些

current = 目的地
path = []
while current != 起始地: # Loop1: 当前节点不是目的地,则表示没有完成寻路
	path.append(current) # 将当前节点添加进 path
	current = came_from[current] # 不断循环构建从目的地到起点的关系(目的地 -> 起点),可以看出来这是反向建立关系过程
path.append(start) # 最后加上开始节点收尾
path.reverse() # 反转一下,就是从起点到目的地的路径 (起点 -> 目的地)

这是最简单的寻路算法,
寻路算法的提前退出

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

Dijkstra’s Algorithm

BFS 用于计算平均移动成本
但是在一些场景下需要取最优权重路径
我们需要跟踪移动成本
能够找到多个目的地

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):
      # 新移动成本 = 当前节点的移动成本 + 当前节点到 邻居节点i 的移动成本
      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 # 保存 邻居节点i 的移动成本
         priority = new_cost # 优先级设置为移动成本
         frontier.put(next, priority) # 优先级队列保存 邻居节点i 的移动成本
         came_from[next] = current

因为引入了优先级队列,这就修改了边境扩展的形状,形状不再是平均的,而是往最小阻力前进

Greedy Best First Search

为单个目的地优化过后的 Dijkstra’s Algorithm 的改进版本

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

# 引入了启发函数
def heuristic(a, b):
   # Manhattan distance on a square grid
   return abs(a.x - b.x) + abs(a.y - b.y)

A* Algorithm

可以看出来与 Dijkstra’s Algorithm 算法十分类似
Dijkstra’s Algorithm 算法计算起点距离,贪婪优先搜索估计距离目的地的点
A* 使用他们两个的距离之和

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

强烈推荐宝藏网站:Introduction to the A* Algorithm
算法(第4版)/第四章/4.4 最短路径

posted @ 2024-05-18 15:14  我听不见  阅读(16)  评论(0编辑  收藏  举报