|
Dijkstra's Algorithm in Python... totally lost |
Neil Black
Member #7,867
October 2006
|
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
|
Neil Black said: 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
|
Arthur Kalliokoski said: 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
|
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
|
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. 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. wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie |
Neil Black
Member #7,867
October 2006
|
See, Neil, that code loses me. Probably because I don't know Python very well. Neil Black said: 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: This graph has six nodes (A-F) and eight arcs. It can be represented by the following Python data structure: graph = {'A': ['B', '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
|
Ok, so here's what I've come up with so far (and yes, the code is terrible, I know): 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
|
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
|
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
|
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. ------------- |
Neil Walker
Member #210
April 2000
|
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. wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie |
Jonatan Hedborg
Member #4,886
July 2004
|
Neil Walker said: 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
|
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. wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie |
Jonatan Hedborg
Member #4,886
July 2004
|
Neil Walker said: 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
|
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. wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie |
Jonatan Hedborg
Member #4,886
July 2004
|
Neil Walker said: 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). 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
|
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. ------------- |
Jonatan Hedborg
Member #4,886
July 2004
|
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
|
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
|
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
|
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.
|
|