Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Dijkstra's Algorithm in Python... totally lost

Credits go to Arthur Kalliokoski, Goalie Ca, jhetfield21, Jonatan Hedborg, kazzmir, and Neil Walker for helping out!
This thread is locked; no one can reply to it. rss feed Print
Dijkstra's Algorithm in Python... totally lost
Neil Black
Member #7,867
October 2006
avatar

I have to implement Dijkstra's Algorithm in Python for a homework assignment. It's due tomorrow and I'm currently totally lost. I just started learning Python on Sunday, and I've never actually implemented Dijkstra's algorithm before.

I can Google up some code, but I don't understand the code (and I'm not just going to copy-paste, what would I learn from that?)

The specific problems I'm having are:

1) Representing the data in Python. He gave us a graph with a number of vertices and weights on the paths between them. I'm not sure how to put this into code, including the vertices and the paths with their weights.

2) Implementing the actual algorithm to go through the vertices and find the shortest path between two points.

Any help would be greatly appreciated.

Also, Python is totally awesome.

Arthur Kalliokoski
Second in Command
February 2005
avatar

I'm not sure how to put this into code, including the vertices and the paths with their weights.

Python is totally awesome.

You can't have both.

They all watch too much MSNBC... they get ideas.

Neil Black
Member #7,867
October 2006
avatar

You can't have both.

Yes I can! I spent about six hours learning Python on Sunday, and it was the best six hours I've spent at my computer all year.

kazzmir
Member #1,786
December 2001
avatar

I would do something like

vertex = id:integer
path = start:vertex end:vertex weight:integer

Since vertex is just a single integer you can use normal python variables because integers are native datatypes. To represent path you should use classes since those are for compound datatypes.

(BTW, if you find designing these things difficult I highly recommend reading through how to design programs)

Neil Walker
Member #210
April 2000
avatar

Life's too short. Just look at the pseudo code in wikipedia and compare with the code below. It's always best to learn by diving in.

#SelectExpand
1# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/117228 2from priodict import priorityDictionary 3 4def Dijkstra(G,start,end=None): 5 D = {} # dictionary of final distances 6 P = {} # dictionary of predecessors 7 Q = priorityDictionary() # est.dist. of non-final vert. 8 Q[start] = 0 9 10 for v in Q: 11 D[v] = Q[v] 12 if v == end: break 13 14 for w in G[v]: 15 vwLength = D[v] + G[v][w] 16 if w in D: 17 if vwLength < D[w]: 18 raise ValueError, \ 19 "Dijkstra: found better path to already-final vertex" 20 elif w not in Q or vwLength < Q[w]: 21 Q[w] = vwLength 22 P[w] = v 23 24 return (D,P) 25 26def shortestPath(G,start,end): 27 28 D,P = Dijkstra(G,start,end) 29 Path = [] 30 while 1: 31 Path.append(end) 32 if end == start: break 33 end = P[end] 34 Path.reverse() 35 return Path

It's called re-use ;)

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Neil Black
Member #7,867
October 2006
avatar

See, Neil, that code loses me. Probably because I don't know Python very well.

1) Representing the data in Python. He gave us a graph with a number of vertices and weights on the paths between them. I'm not sure how to put this into code, including the vertices and the paths with their weights.

A friend has introduced me to the obvious-in-hindsight concept of weighted adjacency matrices. I think this solves problem #1.

jhetfield21
Member #12,379
November 2010

http://www.python.org/doc/essays/graphs.html

It says there:
A -> B
A -> C
B -> C
B -> D
C -> D
D -> C
E -> F
F -> C

This graph has six nodes (A-F) and eight arcs. It can be represented by the following Python data structure:

graph = {'A': ['B', 'C'],
'B': ['C', 'D'],
'C': ['D'],
'D': ['C'],
'E': ['F'],
'F': ['C']}

Use that as a reference to make yours.the weights can be attached to the vertices if you put instead of 'B' in 'A'=['B','C'] sth like a dictionary or a list that has in the first field 'B' and in the second the weight.

See in the link how to traverse and access the fields of the graph and then find out how to traverse the inner structs that contain the name of the vertex and weight and the rest is just the translation of the algorithm in code which if you know and understand what it does won't be that difficult.might take some hours but you can do it.

Neil Black
Member #7,867
October 2006
avatar

Ok, so here's what I've come up with so far (and yes, the code is terrible, I know):

#SelectExpand
1# Dijkstra's Algorithm 2# Implements Dijkstra's algorithm 3# CS338 4# Neil Black 5 6# Ah, nexted sequences. This one takes form of Vertices = (A, B, C...) 7# Where A, B, and so on are all sequences containing the weights of paths 8# to other vertices, with the form Weights = (A, B, C...) 9# I simply used a high number to represent a non-existent path 10 11# This is, I believe, called a Weighted Adjacency Matrix 12 13weights = ((0, 2, 999, 999, 1, 4, 999, 5, 999, 999), 14 (2, 0, 1, 999, 999, 3, 999, 999, 999, 999), 15 (999, 1, 0, 2, 999, 999, 999, 999, 999, 4), 16 (999, 999, 2, 0, 999, 999, 3, 999, 999, 1), 17 (1, 999, 999, 999, 0, 3, 999, 2, 999, 999), 18 (4, 3, 999, 999, 3, 0, 2, 1, 999, 999), 19 (999, 999, 999, 3, 999, 2, 0, 3, 999, 2), 20 (5, 999, 999, 999, 2, 1, 3, 0, 2, 999), 21 (999, 999, 999, 999, 999, 999, 999, 2, 0, 3), 22 (999, 999, 4, 1, 999, 999, 2, 999, 3, 0)) 23 24# This list stores visited nodes, in the same order as the tuple above stores 25# their weighted paths. Therefore the visited list and the weighted tuple 26# will use the same index to refer to the same vertex. 27 28visited = [] 29 30# This list stores the total length from the starting vertex to the vertex 31# matching the index (the first index goes with A, the second with B, and so 32# on, like with the previous sequences). Since I cannot represent infinity in 33# my code, I will use 999, since that's higher than any path that will come 34# with the given data. 35 36length = [999, 999, 999, 999, 999, 999, 999, 999, 999, 999] 37 38# Stores the shortest distance from A to vertices 39 40distance = [999, 999, 999, 999, 999, 999, 999, 999, 999, 999] 41 42# This tuple is used merely for printing purposes 43 44letters = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J") 45 46# Since all the calculations start at A, I set Start to 0 (the index 47# corresponding to A) and leave it there. I set w to equal Start and 48# change it in the algorithm 49 50Start = 0 51w = Start 52 53length[Start] = 0 54 55for i in range(10): 56 # These two need to be reset for every iteration of the algorithm 57 visited = [] 58 length = [999, 999, 999, 999, 999, 999, 999, 999, 999, 999] 59 length[Start] = 0 60 while i not in visited: 61 # Find the vertex with the lowest length, that is not in visited 62 lowest = 9999 63 l = -1 64 for j in range(10): 65 if j not in visited: 66 if length[j] < lowest: 67 lowest = length[j] 68 l = j 69 visited.append(j) 70 71 for j in range(10): 72 if j not in visited: 73 if length[l] + weights[l][j] < length[j]: 74 length[j] = length[l] + weights[l][j] 75 76 distance[i] = length[i] 77 78for i in range(10): 79 print(letters[i], ":", distance[i]) 80 81input("\n\nPress the enter key to exit.")

It should be outputting:

A: 0
B: 2
C: 3
D: 5
E: 1
F: 4
G: 6
H: 3
I: 5
J: 6

Instead, I'm getting:

A: 0
B: 2
C: 999
D: 999
E: 1
F: 4
G: 6
H: 3
I: 5
J: 8

And I'm not sure why.

EDIT:

I've checked and re-checked and then checked again the data entered into the weights sequence. I'm almost positive there are no errors in that data.

kazzmir
Member #1,786
December 2001
avatar

I got your program to work but I will refrain from posting the answer because you should do your own homework, but I will post it after its due (tommorow?). Your problem lies in not checking if two nodes are neighbors.

Neil Black
Member #7,867
October 2006
avatar

kazzmir said:

I got your program to work but I will refrain from posting the answer because you should do your own homework, but I will post it after its due (tommorow?). Your problem lies in not checking if two nodes are neighbors.

Thanks. I prefer the hint to an outright answer because, yes, I should do my own homework (with help when I need it!).

Goalie Ca
Member #2,579
July 2002
avatar

When I first learned Dijkstra's I drew it out by hand. Pen and pencil are unbeatable methods for learning.

Basically, with this algo, you have sort of a "wave front". each point on this wave front is a node in a queue. This queue is sorted by "cost".. basically the cheapest points are first and the most expensive are last. This cost is usually cumulative. Every time you pull a point off the cue, you compute the costs (distances) to all of its neighbours and throw it on the queue. To make sure you dont go in circles, you mark visited queues so you make sure you dont check them again.

-------------
Bah weep granah weep nini bong!

Neil Walker
Member #210
April 2000
avatar

Am I right in thinking, if performance is not an issue (e.g. a turn based system) then it's possibly better to use Dijkstra than A*?

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Jonatan Hedborg
Member #4,886
July 2004
avatar

Am I right in thinking, if performance is not an issue (e.g. a turn based system) then it's possibly better to use Dijkstra than A*?

Not really - It depends on the problem. A* will find the same (ie best) solution assuming that the heuristic used is admissible. And it usually does it a lot faster (if the heuristic is good).

Neil Walker
Member #210
April 2000
avatar

That's what I mean. My simple understanding is they essentially use the same methods but A* optimises by guessing the nodes to check rather than doing them all whereas Dijkstra checks them all. So if time is not an issue then Dijkstra may produce a better path.

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Jonatan Hedborg
Member #4,886
July 2004
avatar

That's what I mean. My simple understanding is they essentially use the same methods but A* optimises by guessing the nodes to check rather than doing them all whereas Dijkstra checks them all. So if time is not an issue then Dijkstra may produce a better path.

If the heuristic used is conservative, meaning it will always be equal or lower than the actual path, A* will find the best path as well. An example of a conservative heuristic useful for navigation (in a game for example) would be distance.

This is because a node is only closed after it's been evaluated as the lowest cost+heuristic in the open list (and the best path is found when the target node is closed). Dijkstras alone can (and will) waste a lot of time searching the opposite side of the "playing field" for example, since there is no concept of a goal in that algorithm.

Neil Walker
Member #210
April 2000
avatar

Well, I applied a very simple A* in my game (tranzam) keeping all tiles equal weighting and used straight line as the heuristic and it seemed to work fine, but this was limited to about 5 enemies and a 40x40 grid of tiles. If I widened the search area or added many more enemies it started to slow down (especially the search area), so I suspect my interpretation wasn't exactly great ;)

I should really improve on it to take into account the terrain and add way-points, but it's done now...

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Jonatan Hedborg
Member #4,886
July 2004
avatar

Well, I applied a very simple A* in my game (tranzam [retrospec.sgn.net]) keeping all tiles equal weighting and used straight line as the heuristic and it seemed to work fine, but this was limited to about 5 enemies and a 40x40 grid of tiles. If I widened the search area or added many more files it started to slow down, so I suspect my interpretation wasn't exactly great

There are lots of techniques you can use to improve the speed of A* (or any path finding algorithm really).
Not calculating the path at every AI update is pretty obvious - only recalculate the path if the target (or something else that is meaningful in the particular problem) changes.

Another benefit of A* is that you can lock it to a certain number of "steps" and get a "maybe good" partial path.

Goalie Ca
Member #2,579
July 2002
avatar

I don't think Dijkstra's algorithm is such a problem in terms of speed. I have a version in C++ that can take 500x500 grid and solve in milliseconds. I get real-time interactivity on a 200x200x8 grid.

Another trick, if you need extra performance, you run the graph search from both start->end and end->start and find when they meet in the middle. This saves tons because the time complexity is greater than linear.

-------------
Bah weep granah weep nini bong!

Jonatan Hedborg
Member #4,886
July 2004
avatar

Goalie Ca said:

I don't think Dijkstra's algorithm is such a problem in terms of speed. I have a version in C++ that can take 500x500 grid and solve in milliseconds. I get real-time interactivity on a 200x200x8 grid.

Another trick, if you need extra performance, you run the graph search from both start->end and end->start and find when they meet in the middle. This saves tons because the time complexity is greater than linear.

That's true. But going from two ends at once is pretty much like adding a heuristic, only still a lot slower and a fair bit more work ;)

The difference between Dijkstra's and A* is like 25 lines of code. So if it's possible to implement it, I don't see any reason why you should not.

Neil Black
Member #7,867
October 2006
avatar

Got it working.

It turns out that I was adding nodes to the list of visited nodes when I hadn't actually visited them. Thus their distance was never getting updated, making it stay at 999.

Also, there was an issue where I was choosing the next node to visit incorrectly.

Still not sure what kazzmir meant about not checking if two nodes are neighbors. I'll wait until tomorrow and see what he says about it.

Thanks all!

kazzmir
Member #1,786
December 2001
avatar

#SelectExpand
1 2# Dijkstra's Algorithm 3# Implements Dijkstra's algorithm 4# CS338 5# Neil Black 6 7# Ah, nexted sequences. This one takes form of Vertices = (A, B, C...) 8# Where A, B, and so on are all sequences containing the weights of paths 9# to other vertices, with the form Weights = (A, B, C...) 10# I simply used a high number to represent a non-existent path 11 12# This is, I believe, called a Weighted Adjacency Matrix 13 14weights = ((0, 2, 999, 999, 1, 4, 999, 5, 999, 999), 15 (2, 0, 1, 999, 999, 3, 999, 999, 999, 999), 16 (999, 1, 0, 2, 999, 999, 999, 999, 999, 4), 17 (999, 999, 2, 0, 999, 999, 3, 999, 999, 1), 18 (1, 999, 999, 999, 0, 3, 999, 2, 999, 999), 19 (4, 3, 999, 999, 3, 0, 2, 1, 999, 999), 20 (999, 999, 999, 3, 999, 2, 0, 3, 999, 2), 21 (5, 999, 999, 999, 2, 1, 3, 0, 2, 999), 22 (999, 999, 999, 999, 999, 999, 999, 2, 0, 3), 23 (999, 999, 4, 1, 999, 999, 2, 999, 3, 0)) 24 25# This list stores visited nodes, in the same order as the tuple above stores 26# their weighted paths. Therefore the visited list and the weighted tuple 27# will use the same index to refer to the same vertex. 28 29visited = [] 30 31# This list stores the total length from the starting vertex to the vertex 32# matching the index (the first index goes with A, the second with B, and so 33# on, like with the previous sequences). Since I cannot represent infinity in 34# my code, I will use 999, since that's higher than any path that will come 35# with the given data. 36 37length = [999, 999, 999, 999, 999, 999, 999, 999, 999, 999] 38 39# Stores the shortest distance from A to vertices 40 41distance = [999, 999, 999, 999, 999, 999, 999, 999, 999, 999] 42 43# This tuple is used merely for printing purposes 44 45letters = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J") 46 47# Since all the calculations start at A, I set Start to 0 (the index 48# corresponding to A) and leave it there. I set w to equal Start and 49# change it in the algorithm 50 51def neighbors(i, j): 52 return weights[i][j] != 999 53 54Start = 0 55w = Start 56length = [0, 999, 999, 999, 999, 999, 999, 999, 999, 999] 57 58visited = [] 59 60for i in range(10): 61 # These two need to be reset for every iteration of the algorithm 62 # while i not in visited: 63 # Find the vertex with the lowest length, that is not in visited 64 lowest = 9999 65 l = -1 66 for j in range(10): 67 if j not in visited and neighbors(i, j): 68 if length[j] < lowest: 69 lowest = length[j] 70 l = j 71 visited.append(j) 72 73 for j in range(10): 74 if neighbors(l, j): 75 if length[l] + weights[l][j] < length[j]: 76 length[j] = length[l] + weights[l][j] 77 78 distance[i] = length[i] 79 80for i in range(10): 81 print(letters[i], ":", distance[i]) 82 83# input("\n\nPress the enter key to exit.")

I'm not sure why your implementation works when you check all the nodes in the graph and not just the neighbors of the node that currently has the lowest length.

Neil Black
Member #7,867
October 2006
avatar

kazzmir said:

I'm not sure why your implementation works when you check all the nodes in the graph and not just the neighbors of the node that currently has the lowest length.

Because if two nodes are not neighbors, then the weight between them is 999.

if length[l] + weights[l][j] < length[j]:
    length[j] = length[l] + weights[l][j]

The if length[l] + weights[l][j] < length[j]: line can't resolve to true if the value of weights[l][j] is 999, because the highest that length[j] can be is 999. No matter what, if the two aren't neighbors, then the weight between them cannot be less than the length.

Go to: