Ulisses Costa Blog

A* search algorithm

1 01 2011

Time to talk about efficiently pathfinding and graph traversal algorithms. The first algorithm related with graphs pathfinding I learned was Dijkstra’s algorithm and I remember the feeling for learning how to find the shortest path (minimal cost to be generic) in a graph, it was amazing to me, so that I learned a few more and did a simple academic GPS core system.

Dijkstra’s algorithm is truly beautiful, but unfortunately the complexity it too high to be considered time efficient.

If you want to go from point $A$ to $B$ Dijkstra’s wil search all the surrounding nodes, as you can see in this image:

So I start to find more efficient implementations for the pathfinding problem and I discover A Star.

A Star algorithm

I find this algorithm in wikipedia, and I will paste it here because there are a few things I want to explain.

function A*(start,goal)
// The set of nodes already evaluated.
closedset := the empty set
// The set of tentative nodes to be evaluated.
openset := set containing the initial node
// The map of navigated nodes.
came_from := the empty map
// Distance from start along optimal path.
g_score[start] := 0
h_score[start] := heuristic_estimate_of_distance(start, goal)
// Estimated total distance from start to goal through y.
f_score[start] := h_score[start]
while openset is not empty
x := the node in openset having the lowest f_score[] value
if x = goal
return reconstruct_path(came_from, came_from[goal])
remove x from openset
add x to closedset
foreach y in neighbor_nodes(x)
if y in closedset
continue
tentative_g_score := g_score[x] + dist_between(x,y)
if y not in openset
add y to openset
tentative_is_better := true
elseif tentative_g_score < g_score[y]
tentative_is_better := true
else
tentative_is_better := false
if tentative_is_better = true
came_from[y] := x
g_score[y] := tentative_g_score
h_score[y] := heuristic_estimate_of_distance(y, goal)
f_score[y] := g_score[y] + h_score[y]
return failure

function reconstruct_path(came_from, current_node)
if came_from[current_node] is set
p = reconstruct_path(came_from, came_from[current_node])
return (p + current_node)
else
return current_node

If you are familiarized with Dijkstra’s algorithm you probably noticed a lot of coincidences in the algorithm. You are right!
So, we have some structures to use: the $closedset$ is the set of nodes already evaluated by A Star, $openset$ containing the nodes being evaluated, $g\_score$ being the commulative distance to this node, $h\_score$ being the heuristic result for this node (I will explain this in a minute) and $f\_score$ being the sum of $g$ and $h$.

A good thing about A Star is the nodes it needs to search until find the Best-First-Search path:

Seems good right? All the juice of this algorithms lies on the heuristic function, I like the result of the Manhattan distance, but you can read this blog post and find out more about this subject.
Basically this heuristic is empirical knowledge, this particular Manhattan distance calculates the distance from point $A$ to point $B$ in a grid.

I tried to use it in a Geometric graph and it worked fine too!

Point of view

I’m particularly interested in optimizing this algorithm as much as possible and will be doing that using C++.
So, I spend 30 minutes observing the code and I was already very familiarized with Dijkstra’s. I think as a Software Engineer you have to find good algorithms to your problem, deeply understand them, but your job is not done after that! After find a good solution and understand it to the point you are able to explain it to a non-CS person you have to *observe* the code, talk with it and, believe me, don’t make the literal implementation of it, It will be slow, or at least it probably could be more efficient!

So, let’s forget about the problem this algorithm solves and try to identify inefficient chunks in the algorithm. The first thing that cames to my mind is: we need to have a bunch of structures to keep a lot of node related information, a lot of vectors, sets and so.

So, let’s identify what I mean by that:

function A*(start,goal)
...
while openset is not empty
...
x := the node in openset having the lowest f_score[] value
...
foreach y in neighbor_nodes(x)
...
g_score[y] := tentative_g_score
h_score[y] := heuristic_estimate_of_distance(y, goal)
f_score[y] := g_score[y] + h_score[y]

With this chunk of code I want to highlight that we are iterating throw all neighbors for each $x$ belongs to $openset$, then we get the minimum $f$ from $openset$ and change the $g,h,f$ arrays for $y$.

The first thing hitting me is, I can make $openset$ a MinHeap and I can keep all the information for each $*\_score$ in the node itself, like so I won’t be wasting time in accessing positions in a set and I just make a question to the node object.

So, I start to put all this information in the nodes side and keep only track of the locally created minHeap. This is the result:

LinkedList<Edge*> AStar::findPath(Node* start, Node* goal) {
MinHeap<Node*, Comparator> openSet;
bool tentative_is_better = false;

float h = heuristic(start,goal);

start->setStatus(NODE_OPEN);
start->getOrSetScore().set(NULL, h,0,h);
openSet.insert(start);

while(!openSet.isEmpty()) {
Node x = openSet.getMin();
AStarScore & xScore = x->getOrSetScore();

if(x == goal) return process(ret,x);
openSet.removeMin();
x->setStatus(NODE_CLOSE);
ArrayList<Edge> & neighbors = x->getEdges();
for(int i = 0; i < neighbors.getLength (); i++) {
Node *y = neighbors [i]->getDstNode();
AStarScore & yScore = y->getOrSetScore();
GeoNodeStatus yStatus = y->getStatus();

if(yStatus == NODE_CLOSE) {
continue;
}

float tentative_g_score = xScore.g_score + x->getEdge(y)->getCost();

if(yStatus != NODE_OPEN) {
y->setStatus(_version,GEONODE_OPEN);
tentative_is_better = true;
} else if(tentative_g_score < yScore.g_score) {
tentative_is_better = true;
} else {
tentative_is_better = false;
}
if(tentative_is_better) {
yScore.parent = x;
yScore.g_score = tentative_g_score;
yScore.h_score = heuristic(y,goal);
yScore.f_score = yScore.g_score + yScore.h_score;
openSet.insert(y);
}
}
}
return process(ret,goal);
}

Where $process$ is the function that iterate throw the nodes, starting with $goal$ and go the parent until $start$ node and construct the path in reverse order, from $start$ to $goal$.

One side note, A star is so close to Dijkstra, that if you make you $heuristic$ function return always zero, A star will work just like Dikjstra (the first image).

Basically the ideas I want to transmit here is the A star algorithm, it implementation and (the most important one) the process of how to look to an algorithm. Summarizing I think a Software Engineer could not just implement algorithms and substitute all the words $insert(lista,elem)$ in the algorithm for $list.push\_back(elem)$ in C++ and so. I think we should look for an algorithm as a valuable aid and if we convert it to code we must improve it. Ultimately copying an algorithm to code is a good opportunity to leave our personal touch in the code.

Acknowledgments

Images from here.

Information

• Date : January 1, 2011
• Tags: algorithm, astar, c/c++, code, English, graph, programming
• Categories : Uncategorized

Cancel
{"extraction_info": {"found_math": true, "script_math_tex": 0, "script_math_asciimath": 0, "math_annotations": 0, "math_alttext": 0, "mathml": 0, "mathjax_tag": 0, "mathjax_inline_tex": 0, "mathjax_display_tex": 0, "mathjax_asciimath": 0, "img_math": 0, "codecogs_latex": 0, "wp_latex": 27, "mimetex.cgi": 0, "/images/math/codecogs": 0, "mathtex.cgi": 0}, "config": {"markdown_headings": false, "markdown_code": false, "boilerplate_config": {"ratio_threshold": 0.3, "absolute_threshold": 10, "end_threshold": 15, "enable": true}, "remove_buttons": true, "remove_image_figures": true, "remove_link_clusters": true, "table_config": {"min_rows": 2, "min_cols": 3, "format": "github"}, "remove_chinese": true, "remove_edit_buttons": true, "extract_latex": true}, "english_confidence": 0.9372345805168152, "perplexity_flag": "middle"}