寻路算法 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 最短路径