super()
call and the print
function with analogues from Python 2. class SimpleGraph: def __init__(self): self.edges = {} def neighbors(self, id): return self.edges[id]
example_graph = SimpleGraph() example_graph.edges = { 'A': ['B'], 'B': ['A', 'C', 'D'], 'C': ['A'], 'D': ['E', 'A'], 'E': ['B'] }
import collections class Queue: def __init__(self): self.elements = collections.deque() def empty(self): return len(self.elements) == 0 def put(self, x): self.elements.append(x) def get(self): return self.elements.popleft()
collections.deque
class. In your code, you can use deque
directly. from implementation import * def breadth_first_search_1(graph, start): # , frontier = Queue() frontier.put(start) visited = {} visited[start] = True while not frontier.empty(): current = frontier.get() print("Visiting %r" % current) for next in graph.neighbors(current): if next not in visited: frontier.put(next) visited[next] = True breadth_first_search_1(example_graph, 'A')
Visiting 'A'
Visiting 'B'
Visiting 'C'
Visiting 'D'
Visiting 'E'
SquareGrid
, with a tuple (int, int) points . Instead of storing the edges explicitly, we will compute them as neighbors
. class SquareGrid: def __init__(self, width, height): self.width = width self.height = height self.walls = [] def in_bounds(self, id): (x, y) = id return 0 <= x < self.width and 0 <= y < self.height def passable(self, id): return id not in self.walls def neighbors(self, id): (x, y) = id results = [(x+1, y), (x, y-1), (x-1, y), (x, y+1)] if (x + y) % 2 == 0: results.reverse() # results = filter(self.in_bounds, results) results = filter(self.passable, results) return results
from implementation import * g = SquareGrid(30, 15) g.walls = DIAGRAM1_WALLS # long, [(21, 0), (21, 2), ...] draw_grid(g)
. . . . . . . . . . . . . . . . . . . . . ####. . . . . . .
. . . . . . . . . . . . . . . . . . . . . ####. . . . . . .
. . . . . . . . . . . . . . . . . . . . . ####. . . . . . .
. . . ####. . . . . . . . . . . . . . . . ####. . . . . . .
. . . ####. . . . . . . . ####. . . . . . ####. . . . . . .
. . . ####. . . . . . . . ####. . . . . . ##########. . . .
. . . ####. . . . . . . . ####. . . . . . ##########. . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . . . . . . . . . . . ####. . . . . . . . . . . . . . .
. . . . . . . . . . . . . ####. . . . . . . . . . . . . . .
. . . . . . . . . . . . . ####. . . . . . . . . . . . . . .
visited
(True / False) to came_from
(point): from implementation import * def breadth_first_search_2(graph, start): # "came_from" frontier = Queue() frontier.put(start) came_from = {} 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 return came_from g = SquareGrid(30, 15) g.walls = DIAGRAM1_WALLS parents = breadth_first_search_2(g, (8, 7)) draw_grid(g, width=2, point_to=parents, start=(8, 7))
β β β β β β β β β β β β β β β β β β β β β ####β β β β β β β
β β β β β β β β β β β β β β β β β β β β β ####β β β β β β β
β β β β β β β β β β β β β β β β β β β β β ####β β β β β β β
β β β ####β β β β β β β β β β β β β β β β ####β β β β β β β
β β β ####β β β β β β β β ####β β β β β β ####β β β β β β β
β β β ####β β β β β β β β ####β β β β β β ##########β β β β
β β β ####β β β β β β β β ####β β β β β β ##########β β β β
β β β ####β β β A β β β β ####β β β β β β β β β β β β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β β β β β β β β β β β ####β β β β β β β β β β β β β β β
β β β β β β β β β β β β β ####β β β β β β β β β β β β β β β
β β β β β β β β β β β β β ####β β β β β β β β β β β β β β β
came_from
and other values ββfor each node of the graph. Instead, I decided to use external storage by creating a single table of hashes for storing came_from
all nodes of the graph. If you know that the points of your map have integer indices, then there is another option - use the array for storing the came_from
. from implementation import * def breadth_first_search_3(graph, start, goal): frontier = Queue() frontier.put(start) came_from = {} 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 return came_from g = SquareGrid(30, 15) g.walls = DIAGRAM1_WALLS parents = breadth_first_search_3(g, (8, 7), (17, 2)) draw_grid(g, width=2, point_to=parents, start=(8, 7), goal=(17, 2))
. β β β β β β β β β β β β β β β β . . . . ####. . . . . . .
β β β β β β β β β β β β β β β β β β . . . ####. . . . . . .
β β β β β β β β β β β β β β β β β Z . . . ####. . . . . . .
β β β ####β β β β β β β β β β β β β β . . ####. . . . . . .
. β β ####β β β β β β β β ####β β β . . . ####. . . . . . .
. . β ####β β β β β β β β ####β β . . . . ##########. . . .
. . . ####β β β β β β β β ####β . . . . . ##########. . . .
. . . ####β β β A β β β β ####. . . . . . . . . . . . . . .
. . . ####β β β β β β β β ####. . . . . . . . . . . . . . .
. . β ####β β β β β β β β ####. . . . . . . . . . . . . . .
. β β ####β β β β β β β β ####. . . . . . . . . . . . . . .
β β β ####β β β β β β β β ####. . . . . . . . . . . . . . .
β β β β β β β β β β β β β ####. . . . . . . . . . . . . . .
β β β β β β β β β β β β β ####. . . . . . . . . . . . . . .
. β β β β β β β β β β β β ####. . . . . . . . . . . . . . .
Z
cost(from_node, to_node)
function cost(from_node, to_node)
, which tells us the cost of moving from from_node
to its neighbor to_node
. In this forest map, I decided that movement would depend only on to_node
, but there are other types of movement using both nodes . An alternative to the implementation will be to merge it into the function neighbors
. class GridWithWeights(SquareGrid): def __init__(self, width, height): super().__init__(width, height) self.weights = {} def cost(self, from_node, to_node): return self.weights.get(to_node, 1)
import heapq class PriorityQueue: def __init__(self): self.elements = [] def empty(self): return len(self.elements) == 0 def put(self, item, priority): heapq.heappush(self.elements, (priority, item)) def get(self): return heapq.heappop(self.elements)[1]
cost_so_far
. This means that the line if next not in came_from
will not work. Instead, we need to check whether the cost has decreased since the last visit. (In the original version of the article, I did not check it, but my code still worked. I wrote notes about this error .) def dijkstra_search(graph, start, goal): frontier = PriorityQueue() frontier.put(start, 0) came_from = {} cost_so_far = {} 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 return came_from, cost_so_far
def reconstruct_path(came_from, start, goal): current = goal path = [current] while current != start: current = came_from[current] path.append(current) path.append(start) # path.reverse() # return path
came_from
map pointing to the previous node. When we reach the beginning, it's done. This is the return path, so at the end of reconstruct_path
we call reverse()
if we need to store it in a direct sequence. In fact, it is sometimes more convenient to store the path in reverse. In addition, it is sometimes convenient to keep the starting node in the list. from implementation import * came_from, cost_so_far = dijkstra_search(diagram4, (1, 4), (7, 8)) draw_grid(diagram4, width=3, point_to=came_from, start=(1, 4), goal=(7, 8)) print() draw_grid(diagram4, width=3, number=cost_so_far, start=(1, 4), goal=(7, 8)) print() draw_grid(diagram4, width=3, path=reconstruct_path(came_from, start=(1, 4), goal=(7, 8)))
β β β β β β β β β β
β β β β β β β β β β
β β β β β β β β β β
β β β β β β β β β .
β A β β β β . . . .
β β β β β β . . . .
β β β β β β β . . .
β #########β β β . . .
β #########β β β Z . .
β β β β β β β β β .
5 4 5 6 7 8 9 10 11 12
4 3 4 5 10 13 10 11 12 13
3 2 3 4 9 14 15 12 13 14
2 1 2 3 8 13 18 17 14 .
1 A 1 6 11 16 . . . .
2 1 2 7 12 17 . . . .
3 2 3 4 9 14 19 . . .
4 #########14 19 18 . . .
5 #########15 16 13 Z . .
6 7 8 9 10 11 12 13 14 .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
@ @ . . . . . . . .
@ . . . . . . . . .
@ . . . . . . . . .
@ #########. . . . . .
@ #########. . @ @ . .
@ @ @ @ @ @ @ . . .
if next not in cost_so_far or new_cost < cost_so_far[next]
can be simplified to if new_cost < cost_so_far.get(next, Infinity)
, but I didnβt want to explain get()
Python in the last article, so I left it as it is. You can also use collections.defaultdict
with a default infinity value. def heuristic(a, b): (x1, y1) = a (x2, y2) = b return abs(x1 - x2) + abs(y1 - y2) def a_star_search(graph, start, goal): frontier = PriorityQueue() frontier.put(start, 0) came_from = {} cost_so_far = {} 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 return came_from, cost_so_far
from implementation import * came_from, cost_so_far = a_star_search(diagram4, (1, 4), (7, 8)) draw_grid(diagram4, width=3, point_to=came_from, start=(1, 4), goal=(7, 8)) print() draw_grid(diagram4, width=3, number=cost_so_far, start=(1, 4), goal=(7, 8))
. . . . . . . . . .
. β β β . . . . . .
β β β β β . . . . .
β β β β β . . . . .
β A β β β . . . . .
β β β β β . . . . .
β β β β β β . . . .
β #########β . β . . .
β #########β β β Z . .
β β β β β β β β . .
. . . . . . . . . .
. 3 4 5 . . . . . .
3 2 3 4 9 . . . . .
2 1 2 3 8 . . . . .
1 A 1 6 11 . . . . .
2 1 2 7 12 . . . . .
3 2 3 4 9 14 . . . .
4 #########14 . 18 . . .
5 #########15 16 13 Z . .
6 7 8 9 10 11 12 13 . .
template<typename L> struct SimpleGraph { typedef L Location; typedef typename vector<Location>::iterator iterator; unordered_map<Location, vector<Location> > edges; inline const vector<Location> neighbors(Location id) { return edges[id]; } };
char
to mark points: SimpleGraph<char> example_graph {{ {'A', {'B'}}, {'B', {'A', 'C', 'D'}}, {'C', {'A'}}, {'D', {'E', 'A'}}, {'E', {'B'}} }};
#include "redblobgames/pathfinding/a-star/implementation.cpp" template<typename Graph> void breadth_first_search(Graph graph, typename Graph::Location start) { typedef typename Graph::Location Location; queue<Location> frontier; frontier.push(start); unordered_set<Location> visited; visited.insert(start); while (!frontier.empty()) { auto current = frontier.front(); frontier.pop(); std::cout << "Visiting " << current << std::endl; for (auto& next : graph.neighbors(current)) { if (!visited.count(next)) { frontier.push(next); visited.insert(next); } } } } int main() { breadth_first_search(example_graph, 'A'); }
Visiting A
Visiting B
Visiting C
Visiting D
Visiting E
Location
and a neighbors
function. struct SquareGrid { typedef tuple<int,int> Location; static array<Location, 4> DIRS; int width, height; unordered_set<Location> walls; SquareGrid(int width_, int height_) : width(width_), height(height_) {} inline bool in_bounds(Location id) const { int x, y; tie (x, y) = id; return 0 <= x && x < width && 0 <= y && y < height; } inline bool passable(Location id) const { return !walls.count(id); } vector<Location> neighbors(Location id) const { int x, y, dx, dy; tie (x, y) = id; vector<Location> results; for (auto dir : DIRS) { tie (dx, dy) = dir; Location next(x + dx, y + dy); if (in_bounds(next) && passable(next)) { results.push_back(next); } } if ((x + y) % 2 == 0) { // std::reverse(results.begin(), results.end()); } return results; } }; array<SquareGrid::Location, 4> SquareGrid::DIRS {Location{1, 0}, Location{0, -1}, Location{-1, 0}, Location{0, 1}};
implementation.cpp
file, I defined a function for grid drawing: #include "redblobgames/pathfinding/a-star/implementation.cpp" int main() { SquareGrid grid = make_diagram1(); draw_grid(grid, 2); }
. . . . . . . . . . . . . . . . . . . . . ####. . . . . . .
. . . . . . . . . . . . . . . . . . . . . ####. . . . . . .
. . . . . . . . . . . . . . . . . . . . . ####. . . . . . .
. . . ####. . . . . . . . . . . . . . . . ####. . . . . . .
. . . ####. . . . . . . . ####. . . . . . ####. . . . . . .
. . . ####. . . . . . . . ####. . . . . . ##########. . . .
. . . ####. . . . . . . . ####. . . . . . ##########. . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . ####. . . . . . . . ####. . . . . . . . . . . . . . .
. . . . . . . . . . . . . ####. . . . . . . . . . . . . . .
. . . . . . . . . . . . . ####. . . . . . . . . . . . . . .
. . . . . . . . . . . . . ####. . . . . . . . . . . . . . .
came_from
: #include "redblobgames/pathfinding/a-star/implementation.cpp" template<typename Graph> unordered_map<typename Graph::Location, typename Graph::Location> breadth_first_search(Graph graph, typename Graph::Location start) { typedef typename Graph::Location Location; queue<Location> frontier; frontier.push(start); unordered_map<Location, Location> came_from; came_from[start] = start; while (!frontier.empty()) { auto current = frontier.front(); frontier.pop(); for (auto& next : graph.neighbors(current)) { if (!came_from.count(next)) { frontier.push(next); came_from[next] = current; } } } return came_from; } int main() { SquareGrid grid = make_diagram1(); auto parents = breadth_first_search(grid, std::make_tuple(7, 8)); draw_grid(grid, 2, nullptr, &parents); }
β β β β β β β β β β β β β β β β β β β β β ####β β β β β β β
β β β β β β β β β β β β β β β β β β β β β ####β β β β β β β
β β β β β β β β β β β β β β β β β β β β β ####β β β β β β β
β β β ####β β β β β β β β β β β β β β β β ####β β β β β β β
β β β ####β β β β β β β β ####β β β β β β ####β β β β β β β
β β β ####β β β β β β β β ####β β β β β β ##########β β β β
β β β ####β β β β β β β β ####β β β β β β ##########β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β ####β β * β β β β β ####β β β β β β β β β β β β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β ####β β β β β β β β ####β β β β β β β β β β β β β β β
β β β β β β β β β β β β β ####β β β β β β β β β β β β β β β
β β β β β β β β β β β β β ####β β β β β β β β β β β β β β β
β β β β β β β β β β β β β ####β β β β β β β β β β β β β β β
came_from
and other values ββfor each node of the graph. I chose to use external storage , creating a single std::unordered_map
for storing came_from
for all nodes of the graph. If you know that the points of your map have integer indices, then there is another option - use a one-dimensional or two-dimensional array / vector to store came_from
and other values.std::tuple
C ++, because I used tuples in my Python code. However, the tuples are unfamiliar to most C ++ programmers. It will be a little longer to define a struct with {x, y}, because you need to define a constructor, a copy constructor, an assignment operator, and an equality comparison along with a hash function, but such code will be more familiar to most C ++ programmers. You will need to change it.int
. T If the A * code always has a dense set of integer-valued values ββinstead of arbitrary Location
types, this allows for additional optimizations. You can use arrays for different sets, not hashes tables. You can leave most of them uninitialized. For those arrays that need to be initialized anew each time, you can make them permanent for A * calls (possibly using local stream storage) and reinitialize only those parts of the array that were used in the previous call. This is a more complicated technique that I do not want to use in the tutorial of the initial level. #include "redblobgames/pathfinding/a-star/implementation.cpp" template<typename Graph> unordered_map<typename Graph::Location, typename Graph::Location> breadth_first_search(const Graph& graph, typename Graph::Location start, typename Graph::Location goal) { typedef typename Graph::Location Location; queue<Location> frontier; frontier.push(start); unordered_map<Location, Location> came_from; came_from[start] = start; while (!frontier.empty()) { auto current = frontier.front(); frontier.pop(); if (current == goal) { break; } for (auto& next : graph.neighbors(current)) { if (!came_from.count(next)) { frontier.push(next); came_from[next] = current; } } } return came_from; } int main() { SquareGrid grid = make_diagram1(); auto parents = breadth_first_search(grid, SquareGrid::Location{8, 7}, SquareGrid::Location{17, 2}); draw_grid(grid, 2, nullptr, &parents); }
. β β β β β β β β β β β β β β β β . . . . ####. . . . . . .
β β β β β β β β β β β β β β β β β β . . . ####. . . . . . .
β β β β β β β β β β β β β β β β β β . . . ####. . . . . . .
β β β ####β β β β β β β β β β β β β β . . ####. . . . . . .
. β β ####β β β β β β β β ####β β β . . . ####. . . . . . .
. . β ####β β β β β β β β ####β β . . . . ##########. . . .
. . . ####β β β β β β β β ####β . . . . . ##########. . . .
. . . ####β β β * β β β β ####. . . . . . . . . . . . . . .
. . . ####β β β β β β β β ####. . . . . . . . . . . . . . .
. . β ####β β β β β β β β ####. . . . . . . . . . . . . . .
. β β ####β β β β β β β β ####. . . . . . . . . . . . . . .
β β β ####β β β β β β β β ####. . . . . . . . . . . . . . .
β β β β β β β β β β β β β ####. . . . . . . . . . . . . . .
β β β β β β β β β β β β β ####. . . . . . . . . . . . . . .
. β β β β β β β β β β β β ####. . . . . . . . . . . . . . .
to_node
, but there are other types of movement using both nodes . struct GridWithWeights: SquareGrid { unordered_set<Location> forests; GridWithWeights(int w, int h): SquareGrid(w, h) {} double cost(Location from_node, Location to_node) const { return forests.count(to_node) ? 5 : 1; } };
priority_queue
class that uses a binary heap, but without a priority change operation. I use a pair (priority, element) for the elements of the queue to get the correct order. By default, the C ++ priority queue first returns the maximum element using the std::less
comparator. We need a minimal element, so I use the std::greater
comparator. template<typename T, typename priority_t> struct PriorityQueue { typedef pair<priority_t, T> PQElement; priority_queue<PQElement, vector<PQElement>, std::greater<PQElement>> elements; inline bool empty() const { return elements.empty(); } inline void put(T item, priority_t priority) { elements.emplace(priority, item); } inline T get() { T best_item = elements.top().second; elements.pop(); return best_item; } };
std::priority_queue
, but I think it would be wise to use this class without a wrapper. template<typename Graph> void dijkstra_search (const Graph& graph, typename Graph::Location start, typename Graph::Location goal, unordered_map<typename Graph::Location, typename Graph::Location>& came_from, unordered_map<typename Graph::Location, double>& cost_so_far) { typedef typename Graph::Location Location; PriorityQueue<Location, double> frontier; frontier.put(start, 0); came_from[start] = start; cost_so_far[start] = 0; while (!frontier.empty()) { auto current = frontier.get(); if (current == goal) { break; } for (auto& next : graph.neighbors(current)) { double new_cost = cost_so_far[current] + graph.cost(current, next); if (!cost_so_far.count(next) || new_cost < cost_so_far[next]) { cost_so_far[next] = new_cost; came_from[next] = current; frontier.put(next, new_cost); } } } }
cost
variables must match the types used in the graph. If you use int
, you can use int
for variable values ββand priorities in the priority queue. If you use double
, then you also need to use double
for them. In this code, I used double
, but int
could also be used, and the code would work just the same. However, if the cost of the graph edges is stored in double or double is used in heuristics, then double should be used here. template<typename Location> vector<Location> reconstruct_path( Location start, Location goal, unordered_map<Location, Location>& came_from ) { vector<Location> path; Location current = goal; path.push_back(current); while (current != start) { current = came_from[current]; path.push_back(current); } path.push_back(start); // std::reverse(path.begin(), path.end()); return path; }
came_from
map pointing to the previous node. The process will end when we reach the beginning. This is a return path, so if you want to store it in a direct form, then at the end of reconstruct_path
you need to call reverse()
. Sometimes it is more convenient to store the path in reverse. Sometimes it is also useful to keep the starting node in the list. #include "redblobgames/pathfinding/a-star/implementation.cpp" int main() { GridWithWeights grid = make_diagram4(); SquareGrid::Location start{1, 4}; SquareGrid::Location goal{8, 5}; unordered_map<SquareGrid::Location, SquareGrid::Location> came_from; unordered_map<SquareGrid::Location, double> cost_so_far; dijkstra_search(grid, start, goal, came_from, cost_so_far); draw_grid(grid, 2, nullptr, &came_from); std::cout << std::endl; draw_grid(grid, 3, &cost_so_far, nullptr); std::cout << std::endl; vector<SquareGrid::Location> path = reconstruct_path(start, goal, came_from); draw_grid(grid, 3, nullptr, nullptr, &path); }
β β β β β β β β β β
β β β β β β β β β β
β β β β β β β β β β
β β β β β β β β β β
β * β β β β β β β β
β β β β β β . β β .
β β β β β β β β β .
β ######β β β β β .
β ######β β β β β β
β β β β β β β β β β
5 4 5 6 7 8 9 10 11 12
4 3 4 5 10 13 10 11 12 13
3 2 3 4 9 14 15 12 13 14
2 1 2 3 8 13 18 17 14 15
1 0 1 6 11 16 21 20 15 16
2 1 2 7 12 17 . 21 16 .
3 2 3 4 9 14 19 16 17 .
4 #########14 19 18 15 16 .
5 #########15 16 13 14 15 16
6 7 8 9 10 11 12 13 14 15
. @ @ @ @ @ @ . . .
. @ . . . . @ @ . .
. @ . . . . . @ @ .
. @ . . . . . . @ .
. @ . . . . . . @ .
. . . . . . . . @ .
. . . . . . . . . .
. #########. . . . . .
. #########. . . . . .
. . . . . . . . . .
SquareGrids
) and in the function heuristic
. If you replace them, you can use the code of the algorithm A * with any other structure of graphs. inline double heuristic(SquareGrid::Location a, SquareGrid::Location b) { int x1, y1, x2, y2; tie (x1, y1) = a; tie (x2, y2) = b; return abs(x1 - x2) + abs(y1 - y2); } template<typename Graph> void a_star_search (const Graph& graph, typename Graph::Location start, typename Graph::Location goal, unordered_map<typename Graph::Location, typename Graph::Location>& came_from, unordered_map<typename Graph::Location, double>& cost_so_far) { typedef typename Graph::Location Location; PriorityQueue<Location, double> frontier; frontier.put(start, 0); came_from[start] = start; cost_so_far[start] = 0; while (!frontier.empty()) { auto current = frontier.get(); if (current == goal) { break; } for (auto& next : graph.neighbors(current)) { double new_cost = cost_so_far[current] + graph.cost(current, next); if (!cost_so_far.count(next) || new_cost < cost_so_far[next]) { cost_so_far[next] = new_cost; double priority = new_cost + heuristic(next, goal); frontier.put(next, priority); came_from[next] = current; } } } }
priority
, including the type used for the priority queue, must be large enough to contain both the value of the graph ( cost_t
) and the heuristic value. For example, if the value of the graph is stored in an int, and the heuristics returns a double, then it is necessary that the priority queue can receive a double. In this code example I use double
for all three values ββ(cost, heuristics and priority), but I could use and int
, because my cost and heuristics have integer values.frontier.put(start, heuristic(start, goal))
, rather thanfrontier.put(start, 0)
, but here it is not important, because the priority of the initial node does not matter. This is the only node in the priority queue and it is selected and deleted before something is written there. #include "redblobgames/pathfinding/a-star/implementation.cpp" int main() { GridWithWeights grid = make_diagram4(); SquareGrid::Location start{1, 4}; SquareGrid::Location goal{8, 5}; unordered_map<SquareGrid::Location, SquareGrid::Location> came_from; unordered_map<SquareGrid::Location, double> cost_so_far; a_star_search(grid, start, goal, came_from, cost_so_far); draw_grid(grid, 2, nullptr, &came_from); std::cout << std::endl; draw_grid(grid, 3, &cost_so_far, nullptr); std::cout << std::endl; vector<SquareGrid::Location> path = reconstruct_path(start, goal, came_from); draw_grid(grid, 3, nullptr, nullptr, &path); }
β β β β β β β β β β
β β β β β β β β β β
β β β β β β β β β β
β β β β β β . β β β
β * β β β β . β β β
β β β β β β . . β .
β β β β β β . . . .
β ######β . . . . .
β ######. . . . . .
β . . . . . . . . .
5 4 5 6 7 8 9 10 11 12
4 3 4 5 10 13 10 11 12 13
3 2 3 4 9 14 15 12 13 14
2 1 2 3 8 13 . 17 14 15
1 0 1 6 11 16 . 20 15 16
2 1 2 7 12 17 . . 16 .
3 2 3 4 9 14 . . . .
4 #########14 . . . . .
5 #########. . . . . .
6 . . . . . . . . .
. . . @ @ @ @ . . .
. . . @ . . @ @ . .
. . . @ . . . @ @ .
. . @ @ . . . . @ .
. @ @ . . . . . @ .
. . . . . . . . @ .
. . . . . . . . . .
. #########. . . . . .
. #########. . . . . .
. . . . . . . . . .
using System; using System.Collections.Generic; public class Graph<Location> { // string, // NameValueCollection public Dictionary<Location, Location[]> edges = new Dictionary<Location, Location[]>(); public Location[] Neighbors(Location id) { return edges[id]; } }; class BreadthFirstSearch { static void Search(Graph<string> graph, string start) { var frontier = new Queue<string>(); frontier.Enqueue(start); var visited = new HashSet<string>(); visited.Add(start); while (frontier.Count > 0) { var current = frontier.Dequeue(); Console.WriteLine("Visiting {0}", current); foreach (var next in graph.Neighbors(current)) { if (!visited.Contains(next)) { frontier.Enqueue(next); visited.Add(next); } } } } static void Main() { Graph<string> g = new Graph<string>(); g.edges = new Dictionary<string, string[]> { { "A", new [] { "B" } }, { "B", new [] { "A", "C", "D" } }, { "C", new [] { "A" } }, { "D", new [] { "E", "A" } }, { "E", new [] { "B" } } }; Search(g, "A"); } }
using System; using System.Collections.Generic; // A* WeightedGraph L, ** // . . public interface WeightedGraph<L> { double Cost(Location a, Location b); IEnumerable<Location> Neighbors(Location id); } public struct Location { // : Equals , // . , // Equals GetHashCode. public readonly int x, y; public Location(int x, int y) { this.x = x; this.y = y; } } public class SquareGrid : WeightedGraph<Location> { // : , // , , // . public static readonly Location[] DIRS = new [] { new Location(1, 0), new Location(0, -1), new Location(-1, 0), new Location(0, 1) }; public int width, height; public HashSet<Location> walls = new HashSet<Location>(); public HashSet<Location> forests = new HashSet<Location>(); public SquareGrid(int width, int height) { this.width = width; this.height = height; } public bool InBounds(Location id) { return 0 <= id.x && id.x < width && 0 <= id.y && id.y < height; } public bool Passable(Location id) { return !walls.Contains(id); } public double Cost(Location a, Location b) { return forests.Contains(b) ? 5 : 1; } public IEnumerable<Location> Neighbors(Location id) { foreach (var dir in DIRS) { Location next = new Location(id.x + dir.x, id.y + dir.y); if (InBounds(next) && Passable(next)) { yield return next; } } } } public class PriorityQueue<T> { // , // . // C#: https://github.com/dotnet/corefx/issues/574 // // , : // * https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp // * http://visualstudiomagazine.com/articles/2012/11/01/priority-queues-with-c.aspx // * http://xfleury.imtqy.com/graphsearch.html // * http://stackoverflow.com/questions/102398/priority-queue-in-net private List<Tuple<T, double>> elements = new List<Tuple<T, double>>(); public int Count { get { return elements.Count; } } public void Enqueue(T item, double priority) { elements.Add(Tuple.Create(item, priority)); } public T Dequeue() { int bestIndex = 0; for (int i = 0; i < elements.Count; i++) { if (elements[i].Item2 < elements[bestIndex].Item2) { bestIndex = i; } } T bestItem = elements[bestIndex].Item1; elements.RemoveAt(bestIndex); return bestItem; } } /* : Python * , . C++ * typedef, int, double * . C# , * double. int, , * , , , * . */ public class AStarSearch { public Dictionary<Location, Location> cameFrom = new Dictionary<Location, Location>(); public Dictionary<Location, double> costSoFar = new Dictionary<Location, double>(); // : A* Location // Heuristic static public double Heuristic(Location a, Location b) { return Math.Abs(ax - bx) + Math.Abs(ay - by); } public AStarSearch(WeightedGraph<Location> graph, Location start, Location goal) { var frontier = new PriorityQueue<Location>(); frontier.Enqueue(start, 0); cameFrom[start] = start; costSoFar[start] = 0; while (frontier.Count > 0) { var current = frontier.Dequeue(); if (current.Equals(goal)) { break; } foreach (var next in graph.Neighbors(current)) { double newCost = costSoFar[current] + graph.Cost(current, next); if (!costSoFar.ContainsKey(next) || newCost < costSoFar[next]) { costSoFar[next] = newCost; double priority = newCost + Heuristic(next, goal); frontier.Enqueue(next, priority); cameFrom[next] = current; } } } } } public class Test { static void DrawGrid(SquareGrid grid, AStarSearch astar) { // cameFrom for (var y = 0; y < 10; y++) { for (var x = 0; x < 10; x++) { Location id = new Location(x, y); Location ptr = id; if (!astar.cameFrom.TryGetValue(id, out ptr)) { ptr = id; } if (grid.walls.Contains(id)) { Console.Write("##"); } else if (ptr.x == x+1) { Console.Write("\u2192 "); } else if (ptr.x == x-1) { Console.Write("\u2190 "); } else if (ptr.y == y+1) { Console.Write("\u2193 "); } else if (ptr.y == y-1) { Console.Write("\u2191 "); } else { Console.Write("* "); } } Console.WriteLine(); } } static void Main() { // " 4" var grid = new SquareGrid(10, 10); for (var x = 1; x < 4; x++) { for (var y = 7; y < 9; y++) { grid.walls.Add(new Location(x, y)); } } grid.forests = new HashSet<Location> { new Location(3, 4), new Location(3, 5), new Location(4, 1), new Location(4, 2), new Location(4, 3), new Location(4, 4), new Location(4, 5), new Location(4, 6), new Location(4, 7), new Location(4, 8), new Location(5, 1), new Location(5, 2), new Location(5, 3), new Location(5, 4), new Location(5, 5), new Location(5, 6), new Location(5, 7), new Location(5, 8), new Location(6, 2), new Location(6, 3), new Location(6, 4), new Location(6, 5), new Location(6, 6), new Location(6, 7), new Location(7, 3), new Location(7, 4), new Location(7, 5) }; // A* var astar = new AStarSearch(grid, new Location(1, 4), new Location(8, 5)); DrawGrid(grid, astar); } }
cost_so_far
, so you can write a priority queue that takes priority from any place. I do not know whether it is worth it.f
, f+e
, e
β . 1 5. , f
f+5
. , . You can use six blocks or do not sort anything at all! A * creates a wider range of priorities, but still this method is worth considering. And there are more interesting approaches to grouping that can handle a wider range of situations.cost_so_far
, visited
, came_from
etc. a simple array, not a hash table. Since it visited
is a boolean array, you can use a bit vector. Initialize the bit vector for all the IDs, but leave cost_so_far
and came_from
uninitialized (if the language allows it). Then initialize them only at the first visit.visited[]
, 1000 , 100 , , 100 , 1000 .new_cost < cost_so_far
). This check is cheap because I made the search cost_so_far
cheap.priority
, ( , ).cost
w or d l lengthcost_so_far
g d distanceheuristic
hpriority
A* f , f = g + hcame_from
Ο parent previous prevfrontier
OPENvisited
β OPEN CLOSEDcurrent
next
u , vSource: https://habr.com/ru/post/331220/
All Articles