A * is a classic heuristic pathfinding algorithm. It is based on the Dijkstra algorithm, but the heuristic function is added to make the path search more efficient. It is easy to implement. However, to achieve high universality, such as supporting different types of maps, not only maps, but also a map structure that is used by the N-puzzle game, you need to spend more time thinking. If implemented in C ++, templates can be used to meet different needs. You can also use class inheritance.
template <typename NodeType, typename CostType, typename Heuristic> static vector<NodeType> search(Map<NodeType, CostType> &map, NodeType start, NodeType goal, Heuristic heuristic) { map.initialize(start, goal); map.open_node(start, 0, heuristic(start, goal), start); // Get started. while (map.opon_node_available()) { NodeType top_node = map.close_front_open_node(); if (map.nodes_equal(top_node, goal)) return map.get_path(top_node); // Stop and return the path found. const vector<Edge<NodeType, CostType>> &&edges = map.edges(top_node); for (auto edge : edges) { // For each qualified edge evaluate target node. NodeType node_to_evaluate = edge.to_; CostType g = map.current_cost(top_node) + edge.cost_; CostType h = heuristic(node_to_evaluate, goal); if (map.node_unexplored(node_to_evaluate)) { map.open_node(node_to_evaluate, g, h, top_node); } else if (map.cost_greater(map.current_cost(node_to_evaluate), g)) { if (map.node_open(node_to_evaluate)) { map.increase_node_priority(node_to_evaluate, g, h, top_node); } else { // Won‘t reach here if heuristic is consistent(monotone). map.reopen_node(node_to_evaluate, g, h, top_node); } } } } return vector<NodeType>(); // No path found. Return an empty path. }
More than a decade ago, I was infatuated with developing my own real-time strategic games in college and needed a path-finding algorithm. It was a little excited to implement this algorithm for the first time. However, at that time, the Internet was not popular, and it was difficult to find relevant materials. Therefore, it was not good to use the current perspective. In particular, the open list uses an ordered linked list. Now we can use a priority queue. After all, each time you extract the first element from the open list, the order of other elements is not important. C ++ STL provides a priority queue, but unfortunately it does not provide operations to change the priority of an element. Therefore, you can either implement a priority queue or add this function based on STL. If you look at the source code of STL, you will find that the priority queue of STL is actually a binary heap ). Therefore, you only need to add a function percolate_up () on this basis. Of course, if you are a little lazy, you can use STD: make_heap () to generate a new heap after modifying the element priority, but the efficiency is poor.
// Percolate up an element at the index specified.template<typename Container, typename ElementType, typename LessPriority>static void percolate_up(Container &container, int index, LessPriority less_priority) { auto value = container[index]; int hole = index; int parent = (hole - 1) / 2; while (hole > 0 && less_priority(container[parent], value)) { container[hole] = container[parent]; hole = parent; parent = (hole - 1) / 2; } container[hole] = value;}
You can use hot (heap on top) for optimization. That is to say, to create a data structure, only use the priority queue at the top, while other elements are placed in other containers, such as the vector divided into multiple buckets. The advantage of this is that the priority queue can be reduced, and those elements that are unlikely to be open will not be in the queue unless the queue has been emptied and a bucket needs to be converted to a new priority queue.
Another technology is jump point search ). This is an efficient pathfinding algorithm found in recent years. However, there is a restriction that you can only find the path on the regular grid map, and the vertices or edges on the map cannot carry weights, that is, there cannot be complex terrain, only flat and barrier terrain is supported. The idea is to skip a large number of symmetric paths in a flat area of a rectangle and only search for so-called jump points as search nodes. In this way, a large number of nodes in the rectangular area are cropped, so that the number of nodes in the open list is relatively small. You must know that heuristic search algorithms such as a * usually consume a lot of time on the open list operation. The implemented a * algorithm uses the priority queue or even hot (heap on top) to optimize the operation. However, when there are too many nodes in the open list, these operations will become very expensive. However, the disadvantage of JPS is that every time a node is generated, it is expensive to find a hop point. Fortunately, we usually get more benefits. Therefore, JPS is recommended when applicable.
The specific implementation consists of two parts. The first part is to take an optimal node from the open list, expand the search from several specific directions, and add the hop points in each direction to the open list. The second part is to find a hop point.
For the start point, you can expand the search in all directions. For other nodes, you must look at the direction of the parent node pointing to the current node and search for all natural neighbors and forced neighbor nodes.
For example, for node N and parent node P and obstacle X, + is the natural neighbor of N, that is, from P to n to + is the best path. If X is not an obstacle, from P to n to-is not the optimal path, because from P to X to-is closest. However, if X is an obstacle, then from P to n to-is the optimal path. Therefore, it is called-as a forced neighbor.
- + + x n +p x -
The above is an example of N and P diagonal. Another case is that N and P are straight lines:
x x - p n +x x -
The search for Skip points is recursive. First, determine whether a node is a hop point. If there are forced neighbors at this point, this node is a jump point. In the second case, if the node is a destination node, it is also considered a hop point. If not, recursive search continues in the original direction. Perform an additional two steps in the diagonal direction, that is, search for the corresponding (left and right) Straight line direction. If you find the hop point, you will also return the result as a hop point. If the straight line is not found, continue to search for the hop point recursively In the diagonal direction and return the hop point.
The following is a comparison of the results obtained by searching a 10x10 grid map using the * algorithm and JPS algorithm (X is the obstacle, S is the start point, G is the end point, and @ is the best path, O is an open node and-is a closed node)
We can roughly see how JPs skips the rectangular area through jump point.
A *:
ooox-S-- [email protected][email protected][email protected]@--[email protected][email protected]@xxx[email protected][email protected]@-o[email protected]@-o[email protected]@[email protected]oo[email protected]@@-- oo-------
JPs:
x S @Gx @ xxxx x xx @xxx - x x-x-x x x x @@x-[email protected] @ @
A *, JPs (skip search)