A* Search Algorithm in JavaScript (Updated)
See the updated pathfinding demo of A* Search in JavaScript.
Get all the updated source code from github.
When I first wrote the A* Search in JavaScript article I knew there were some things that could make the pathfinding faster. I didn't know realize that it would be this much faster. Here are my (totally unscientific) results:
Observed Speed of A* Search on Chrome 6.0.472.55 on OS X | ||
---|---|---|
# rows | original | updated |
100 | 700-2500ms (wildly variant) | 5-10ms |
50 | 160ms | 5-10ms |
15 | 0-5ms | 0-2ms |
Here's What Changed
I got some feedback about using a heap or a priority queue instead of a list (basically keep the open items in sorted order the whole time so we can just grab it off of the top instead of needing to traverse the whole list every time. As predicted, this makes a huge difference as the number of nodes goes up, but may actually be a little slower for a very low number of nodes because of the overhead of keeping them sorted. I found a binary heap implementation from http://eloquentjavascript.net/appendix2.html and put it in.
Also, I realized two places that I was traversing lists that could be avoided. Getting rid of extra traversals (especially ones that ran inside nested loops) made a very big difference in performance.
- I was keeping all of the nodes inside a closed list, which required traversing that list every time to see if it was already closed. This was replaced with a simple bit on the node to track if it had been closed.
- I was checking to see if a node was visited already by seeing if it was in the open list. This was replaced by a simple bit to see if it had been visited.
Old Pseudocode
push startNode onto openList
while(openList is not empty) {
currentNode = find lowest f in openList
if currentNode is final, return the successful path
push currentNode onto closedList and remove from openList
foreach neighbor of currentNode {
if neighbor is not in openList {
save g, h, and f then save the current parent
add neighbor to openList
}
if neighbor is in openList but the current g is better than previous g {
save g and f, then save the current parent
}
}
}
Updated Pseudocode (* lines have changed)
* push startNode onto openHeap
while(openList is not empty) {
* currentNode = pop from openHeap
if currentNode is final, return the successful path
* set currentNode as closed
foreach neighbor of currentNode {
* if neighbor is not set visited {
* save g, h, and f then save the current parent and set visited
* add neighbor to openHeap
}
if neighbor is in openList but the current g is better than previous g {
save g and f, then save the current parent
* reset position in openHeap (since f changed)
}
}
}
I also did some general cleanup, getting rid of dependancies on the demo, and I split out the code so that the search function can be used standalone (taking a two dimensional array as a parameter), so if someone just wanted the optimized astar search, you could call it like so:
<script type='text/javascript' src='astar.js'></script>
<script type='text/javascript'>
var graph = new Graph([
[1,1,1,1], // 1 is open, 0 is wall
[0,1,1,0],
[0,0,1,1]
]);
var start = graph.nodes[0][0];
var end = graph.nodes[1][2];
var result = astar.search(graph.nodes, start, end);
// result is an array containing the shortest path
</script>
What Could Make It Better?
Last time I posted about this, I got a lot of feedback (mostly about how slow it was). If anyone reading this knows of anything that could make it better, let me know.
Source Code (direct links)
astar.js |
graph.js |
demo.js |
astar-list.js (old implementation with open and closed values swapped)