NB: If you need to revise how Dijstra's work, have a look to the post where I detail Dijkstra's algorithm operations step by step on the whiteboard, for the example below.
First, let's choose the right data structures. The implementation can be a nightmare if you pick a bad data structure for describing the graphs. In python, the dict structure is particularly handy, so let's use it.
On the above example, s has two neighbors a and b, linked with cost 2 and 1 respectively. So, our graph dictionary will look like this:
graph = {'s': {'a': 2, 'b': 1}, 'a': {'s': 3, 'b': 4, 'c':8}, 'b': {'s': 4, 'a': 2, 'd': 2}, 'c': {'a': 2, 'd': 7, 't': 4}, 'd': {'b': 1, 'c': 11, 't': 5}, 't': {'c': 3, 'd': 5}}Now, if you have followed my previous post detailing the pseudo code and operations of Dijsktra's shortest path tree algorithm, you know that we need to keep track of the following information:
- which nodes are not visited yet: we can use a list for that.
- a predecessor and a cost for every node. Let's use dict type for these elements, it will ease our life when we look up their value for a given node.
So the signature of our function is the following:
def shortestpath(graph,src,dest,visited=[],distances={},predecessors={}):
It will return the shortest path from src to dest, but could as well return a shortest path tree.
You will need to know the two following python functions to implement Dijkstra smartly.
mydict.get(mykeyvalue,17)this function of a dict element (here 'mydict') searches for the value of the dict for the keyvalue 'mykeyvalue'. If this key does not exist in the dict, the function does not raise an error. Instead, it returns the value provided as the second argument, here 17.
min(mydict, key=mydict.get)The min function can be applied on dictionaries! You just have to specify the key function as in the above example.
Now, we have everything we need to implement the function. I propose that you try it on your side and then look at the solution. Otherwise, you'll not memorize the algorithm. That being said, it's your choice...
One last thing before jumping to the code: if your graph bears negative weights, Dijkstra's algorithm will run into trouble - you should rather use Floyd or Bellman-Ford algorithm, which are able to detect negative cycles in graphs. Remember that Dijsktra's algorithm is for graphs with positive weights only.
def dijkstra(graph,src,dest,visited=[],distances={},predecessors={}): """ calculates a shortest path tree routed in src """ # a few sanity checks if src not in graph: raise TypeError('The root of the shortest path tree cannot be found') if dest not in graph: raise TypeError('The target of the shortest path cannot be found') # ending condition if src == dest: # We build the shortest path and display it path=[] pred=dest while pred != None: path.append(pred) pred=predecessors.get(pred,None) print('shortest path: '+str(path)+" cost="+str(distances[dest])) else : # if it is the initial run, initializes the cost if not visited: distances[src]=0 # visit the neighbors for neighbor in graph[src] : if neighbor not in visited: new_distance = distances[src] + graph[src][neighbor] if new_distance < distances.get(neighbor,float('inf')): distances[neighbor] = new_distance predecessors[neighbor] = src # mark as visited visited.append(src) # now that all neighbors have been visited: recurse # select the non visited node with lowest distance 'x' # run Dijskstra with src='x' unvisited={} for k in graph: if k not in visited: unvisited[k] = distances.get(k,float('inf')) x=min(unvisited, key=unvisited.get) dijkstra(graph,x,dest,visited,distances,predecessors) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] #unittest.main() graph = {'s': {'a': 2, 'b': 1}, 'a': {'s': 3, 'b': 4, 'c':8}, 'b': {'s': 4, 'a': 2, 'd': 2}, 'c': {'a': 2, 'd': 7, 't': 4}, 'd': {'b': 1, 'c': 11, 't': 5}, 't': {'c': 3, 'd': 5}} dijkstra(graph,'s','t')
And magic... It returns:
shortest path: ['t', 'd', 'b', 's'] cost=8
Well done and congratulations for reading this post to the end! Now, if you are working on Dijsktra's algorithm, I think you are preparing for an interview at Google, Microsoft, or Amazon. Am I right?
Then, the best tip I can give you is to look at two books "Cracking the PM Interview: How to Land a Product Manager Job in Technology" and "Cracking the Coding Interview: 150 Programming Questions and Solutions". They were written by Gayle Laakmann McDowell, a former recruiter from Google who also worked at Apple and I find them really great!
I hope this post has helped you. If you like it, please share it on social media and you will make my day! Thanks!
PS: I love your comments and I must apologize for not being able to answer them all. I have a demanding job and family. Thank you for your understanding!
Hi,
ReplyDeleteIf I am given an input as the following:
node1, node2, length1
node2, node3, length2
node1, node3, length3
How do I create a nested dictionary like your graph?
Thank you very much for your help.
Hello,
ReplyDeleteyour question is incomplete, sorry! What's your data structure in input? A string, an array, ... This point conditions the conversion method to the nested dictionary format I propose. Anyway it should be pretty easy: a loop to go through your input and fill in the nested dictionary appropriately.
dic={}
ReplyDeletedef add_dic(node1,node2,length):
if node1 not in dic:
dic[node1]={}
dic[node1][node2]=length
else:
dic[node1].update({node2:length})
what if there won't be any path between s and t ?
ReplyDeleteGood question :) In such case, the path has an infinite cost.
Deletethanks alot and i really love the outlook of your blog. please can you teach me how to design my this way
ReplyDeleteThank you for this article.
ReplyDeleteI used it in my code and saved a lot of time :)
I have to make a comment though: the end condition is not ok. Now it ends when it reaches the destination and if there is a shorter path it won't find it. I came around such a case. The proper end is when there are no more unvisited nodes. Then all the posibile distances are computed.
There is a catch: you can't return the path in this case. Not without some othere modifications. (I didn't need the path so I skipped it)
I just wanted to tell you this. Maybe it will help you like it helped me :)
Actually, your wrong. Dijkstras algorithm builds upon the paths it already has and in such a way that it extends the shortest path it has. Hence, upon reaching your destination you have found the shortest path possible. This is the strength of Dijkstra's algorithm, it does not need to evaluate all nodes to find the shortest path from a to b. Of course, this only works if the distances between nodes are only positive.
DeleteUgh, you're*
DeleteCan you suggest why this error occurs? It does not occur in the previous use of ('inf').
ReplyDeleteunvisited[k] = distances.get(k,float('inf'))
TypeError: list indices must be integers, not str
Hi,
ReplyDeleteI tried changing print to return, but it gave me none, is there way to return the values instead?
Just having a look at this now, hopefully you sorted this. For those people who haven't you'll need to return the value in both the if src==dest & else clauses as otherwise your output will get lost somewhere in the stack.
DeleteSo change the print statement to:
return path, distances[dest]
and the recursion to:
return dijkstra(graph, x, dest, visited, distances, predecessors)
this is my input 1->7 7->2 2->5 5->6
ReplyDeletei need output 7->1->2->5->6
how do i change this algorithm to obtain this output
Hi,
ReplyDeleteWhat is the time complexity for this implementation?
Hi,
ReplyDeleteWhen I run the programme a couple of times then the following error pops up:
File "C:/Users/brams/Desktop/Network scheduling/Code/dijkstrasalgoritm.py", line 88, in dijkstra
x = min(unvisited, key=unvisited.get)
ValueError: min() arg is an empty sequence
What can I do about this?
How about plain old debugging? Start by printing the value of the variables triggering hte issue?
DeleteI'll pile on to the above. I've tested with multiple graphs and it has both given error above and given incorrect answers. Don't think this code is perfectly implemented.
DeleteThis was fantastic! Worked like a charm for one of the Project Euler problems (I was stuck on it for a while). Thanks for taking the time to share it -- you made my day.
ReplyDelete