Follows the implementation of Dijkstra and A* pathfinding algorithms in Go programming language. Obviously.
The best article on the A star algorithm was unexpectedly on Wikipedia. Before reading further, I am assuming you already have some background knowledge on what both Dijkstra and A star pathfinding algorithms do (hint: they find the shortest path), otherwise, make sure to check out both the Wikipedia page and a fine article on the implementation of A star. In essence, Dijkstra by itself is A star algorithm without heuristics, which will become clear pretty soon.
Implementation of A* and Dijkstra in Go
Again, I am assuming that you are good with the theory. If no, at least go (ahem) and check the pseudocode for the algorithm. The word “algorithm” is in the singular form, as we are going to implement only one function that will do both the work of A star and Dijkstra for us.
Given an undirected graph, with lots of starting and ending vertices, as well as the distance between them. For example, the input0 2 500
means that there is an edge between the vertex numbers 0 and 2 with a distance of 500 units. We will store them in an Edge
array.
Also given physical vertex locations within an NxN matrix. We will use this information as our heuristics to guide our A* algorithm. Simply, the heuristic function will return the euclidean distance between vertices in the “physical world”, which will be our estimation.
TheEdge
type has the fields of to
and distance
, which we will use to store our edge graph. But first, we initialize the global variables.
var edgeGraph = make([][]Edge, SIZE)
var vertexGraph = make([]int, SIZE)
var dist = make([]float64, SIZE)
var parent = make([]int, SIZE)
var visitCount int
edgeGraph[0][i]
will return the _i_th edge coming out of the vertex 0. vertexGraph[0]
will return the physical location of vertex 0. dist
and parent
slices should bother you only and only if you are here by chance.
Finding the Shortest Path with A* and Dijkstra Algorithms
As a quick recall, CTRL (⌘) + C
copies, and thex
button exits the tab in which you are reading this article. But c’mon.
A* / Dijkstra Golang implementation
func findShortestPath(start, finish int, algorithm string) {
reset()
var fCost float64
var gCost float64
var hCost float64
var currentVertex, to int
var edge Edge
// initializing priority queue
queue := NewPriorityQueue()
queue.Push(Edge{start, 0})
dist[start] = 0
for queue.Length() > 0 {
visitCount++
// getting the current edge and vertex
edge = queue.Front().(Edge)
queue.Pop() // dequeue
currentVertex = edge.to
// terminating search when reaching goal
if currentVertex == finish { break }
// iterating through all the vertices that we can reach from current vertex
for i := 0; i < len(edgeGraph[currentVertex]); i++ {
// getting the next connected vertex
to = edgeGraph[currentVertex][i].to
// calculating gCost
gCost = dist[currentVertex] + edgeGraph[currentVertex][i].distance
// relaxing the edge if needed
if gCost < dist[to] {
// updating distance if shorter path is found
dist[to] = gCost
// setting heuristic in case of A*
if algorithm == "astar" { hCost = heuristic(to, finish) }
// calculating fCost (hCost is zero in case of dijkstra)
fCost = gCost + hCost
// enqueue
queue.Push(Edge{to, fCost})
// memorizing the parent vertex for building path
parent[to] = currentVertex
}
}
}
}
findShortestPath
accepts starting and ending vertices, as well as the algorithm of choice (dijkstra
, astar
). Of course, you may also write apple
instead of dijkstra; the algorithm will act in the exact same way.
The reset
function gives default values to arrays: dist
gets filled with infinities, andparent
goes negative.
We then declare our costs and initialize our data structure — priority queue. There is no built-in priority queue implementation in Go, so we will use the simple priority queue library in Go by mtorbin. We then immediately push the start Edge to the queue.
visitCount
simply keeps track of the visited vertices in order to compare A star to Dijkstra. We break
the loop whenever we reach the goal node.
Now comes the most important part.
We go through all the outgoing edges of currentVertex
. You know, we have a thing called f(v), which is equal to g(v) + h(v). We may calculate our gCost, which will be the distance until our current vertex plus the distance from our current vertex to its child node.
gCost = dist[currentVertex] + edgeGraph[currentVertex][i].distance
If our gCost is less than the current distance until our child node, we update our dist[to]
(“to” is the child node, obv).
dist[to] = gCost
The difference between Dijkstra & A* algorithms
If you knew how to implement Dijkstra, then this is the culmination. Otherwise, it’s not. The difference between Dijkstra & A* algorithms is the addition of the heuristic function.
If the algorithm is defined as astar
we apply our heuristic function, otherwise, hCost remains 0 (in Go, zero is the default value for variables while declaration):
if algorithm == "astar" { hCost = heuristic(to, finish) }
fCost = gCost + hCost
fCost becomes gCost and hence turns into our usual Dijkstra in case our algorithm is specified as “apple”. The heuristic function simply calculates the euclidean distance between the child and goal nodes. Then we simply push the edge to the queue and update our parent array in order to be able to restore our path in the future.
The full code with the heuristic function, path building, and working test cases you will find in the Github repository. Pull requests if you find a bug or some other insect.