[Unity] Pathfinding algorithm: breadth first, Dijkstra, heuristic, A star

The following is some simple understanding and summary that I gained by reading this link (the writing is really good, and the author also provides some visual operations to facilitate understanding the similarities and differences of various algorithms, which is highly recommended). If there are any deviations or errors, please correct me in the comment area.


 

1.Breadth First Search (breadth first algorithm)

a. Expand outward without any constraints, traverse and save the source nodes of all nodes, and store them in the came_from dictionary:

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. Starting from the goal point goal, search for the starting point in the reverse direction in came_from. If the starting point is not found, the loop is repeated until the starting point is found. Then the goal point is added to the pathfinding path and the path is reversed:

current = goal
path = []

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

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

Optimize. Add a judgment condition when searching for came_from in the above a. If the target node is found, jump out of the loop:

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 ( Dijkstra's algorithm )

In some cases, the consumption (time, physics, etc.) of each step is different, so a new parameter cost_so_far needs to be added to record the total cost consumed from the starting position to the current position. Therefore, when adding the came_from node, first consider comparing the cost, find the node with the smallest consumption, and add it to 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

Note: PriorityQueue needs to return the smallest value in the queue first (if it is a numerical value in python, it will be arranged from large to small by default, and other types need to be customized).

Since the minimum cost is not necessarily the shortest path, the following heuristic search is used.

3.Heuristic search (heuristic search)

In most cases, we don't need to search for a path in any direction, we only need to search for a path in the direction of the target point. Therefore, in Greedy Best First Search, we do not consider cost as a priority queue, but always add the distance between the next node and the end point into the queue, then find the shortest distance, take out this node, and then start from this node to find the next A node, looping until the end is found.

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

Advantages: If the terrain is simple, searching for paths will be fast. 

Disadvantages: If the terrain is complex, as shown in the figure...

The algorithm went straight to the target point, but found that the path was blocked. Then it searched back and took a big turn. As a result, the path found was not the shortest path. 

Solution: A-star algorithm.

4.The A* algorithm (A star algorithm)

Dijkstra's Algorithm can find the shortest path, but it is a waste of time; Greedy Best First Search is relatively fast, but it cannot find the shortest path.

The A-star algorithm not only uses Dijkstra's Algorithm to obtain the actual distance consumption from the starting point to the current position, but also uses the estimated distance from the current distance to the end point as a judgment, and adds the value obtained by adding the two results to the priority queue, and obtains The smallest value is the next moving target point.

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

how to choose

1. If your game requires a comprehensive path search, give priority to using Breadth First Search or Dijkstra's algorithm. If your cost at each step is the same, use Breadth First Search, otherwise, choose Dijkstra's algorithm.

2. If you only need to find the path from the starting point to the target point, use the A-star algorithm first.

Examples of the above algorithm implementation in Python, C++, and C#


This is a demo implemented by myself, using Unity+C#, the effect is as follows:

Resource links

Guess you like

Origin blog.csdn.net/qq302756113/article/details/124858376