J'essaie d'implémenter l'algorithme de Dijkstra en python en utilisant des tableaux. Ceci est ma mise en œuvre.
def extract(Q, w):
m=0
minimum=w[0]
for i in range(len(w)):
if w[i]<minimum:
minimum=w[i]
m=i
return m, Q[m]
def dijkstra(G, s, t='B'):
Q=[s]
p={s:None}
w=[0]
d={}
for i in G:
d[i]=float('inf')
Q.append(i)
w.append(d[i])
d[s]=0
S=[]
n=len(Q)
while Q:
u=extract(Q,w)[1]
S.append(u)
#w.remove(extract(Q, d, w)[0])
Q.remove(u)
for v in G[u]:
if d[v]>=d[u]+G[u][v]:
d[v]=d[u]+G[u][v]
p[v]=u
return d, p
B='B'
A='A'
D='D'
G='G'
E='E'
C='C'
F='F'
G={B:{A:5, D:1, G:2}, A:{B:5, D:3, E:12, F:5}, D:{B:1, G:1, E:1, A:3}, G:{B:2, D:1, C:2}, C:{G:2, E:1, F:16}, E:{A:12, D:1, C:1, F:2}, F:{A:5, E:2, C:16}}
print "Assuming the start vertex to be B:"
print "Shortest distances", dijkstra(G, B)[0]
print "Parents", dijkstra(G, B)[1]
Je m'attends à ce que la réponse soit:
Assuming the start vertex to be B:
Shortest distances {'A': 4, 'C': 4, 'B': 0, 'E': 2, 'D': 1, 'G': 2, 'F': 4}
Parents {'A': 'D', 'C': 'G', 'B': None, 'E': 'D', 'D': 'B', 'G': 'D', 'F': 'E'}
Cependant, la réponse que je reçois est la suivante:
Assuming the start vertex to be B:
Shortest distances {'A': 4, 'C': 4, 'B': 0, 'E': 2, 'D': 1, 'G': 2, 'F': 10}
Parents {'A': 'D', 'C': 'G', 'B': None, 'E': 'D', 'D': 'B', 'G': 'D', 'F': 'A'}.
Pour le noeud F, le programme donne la réponse incorrecte. Quelqu'un peut-il me dire pourquoi s'il vous plaît?
Comme d'autres l'ont souligné, en raison de l'absence de noms de variables compréhensibles, il est presque impossible de déboguer votre code.
En suivant l'article du wiki sur l'algorithme de Dijkstra, on peut l'implémenter de la manière suivante (et de mille manières):
nodes = ('A', 'B', 'C', 'D', 'E', 'F', 'G')
distances = {
'B': {'A': 5, 'D': 1, 'G': 2},
'A': {'B': 5, 'D': 3, 'E': 12, 'F' :5},
'D': {'B': 1, 'G': 1, 'E': 1, 'A': 3},
'G': {'B': 2, 'D': 1, 'C': 2},
'C': {'G': 2, 'E': 1, 'F': 16},
'E': {'A': 12, 'D': 1, 'C': 1, 'F': 2},
'F': {'A': 5, 'E': 2, 'C': 16}}
unvisited = {node: None for node in nodes} #using None as +inf
visited = {}
current = 'B'
currentDistance = 0
unvisited[current] = currentDistance
while True:
for neighbour, distance in distances[current].items():
if neighbour not in unvisited: continue
newDistance = currentDistance + distance
if unvisited[neighbour] is None or unvisited[neighbour] > newDistance:
unvisited[neighbour] = newDistance
visited[current] = currentDistance
del unvisited[current]
if not unvisited: break
candidates = [node for node in unvisited.items() if node[1]]
current, currentDistance = sorted(candidates, key = lambda x: x[1])[0]
print(visited)
Ce code est plus verbeux que nécessaire et j'espère que, en comparant votre code avec le mien, vous remarquerez peut-être des différences.
Le résultat est:
{'E': 2, 'D': 1, 'G': 2, 'F': 4, 'A': 4, 'C': 3, 'B': 0}
Je l'ai écrit sous une forme plus verbeuse pour le rendre plus clair pour un lecteur novice:
from collections import defaultdict
def get_shortest_path(weighted_graph, start, end):
"""
Calculate the shortest path for a directed weighted graph.
Node can be virtually any hashable datatype.
:param start: starting node
:param end: ending node
:param weighted_graph: {"node1": {"node2": "weight", ...}, ...}
:return: ["START", ... nodes between ..., "END"] or None, if there is no
path
"""
# We always need to visit the start
nodes_to_visit = {start}
visited_nodes = set()
distance_from_start = defaultdict(lambda: float("inf"))
# Distance from start to start is 0
distance_from_start[start] = 0
tentative_parents = {}
while nodes_to_visit:
# The next node should be the one with the smallest weight
current = min(
[(distance_from_start[node], node) for node in nodes_to_visit]
)[1]
# The end was reached
if current == end:
break
nodes_to_visit.discard(current)
visited_nodes.add(current)
for neighbour, distance in weighted_graph[current]:
if neighbour in visited_nodes:
continue
neighbour_distance = distance_from_start[current] + distance
if neighbour_distance < distance_from_start[neighbour]:
distance_from_start[neighbour] = neighbour_distance
tentative_parents[neighbour] = current
nodes_to_visit.add(neighbour)
return _deconstruct_path(tentative_parents, end)
def _deconstruct_path(tentative_parents, end):
if end not in tentative_parents:
return None
cursor = end
path = []
while cursor:
path.append(cursor)
cursor = tentative_parents.get(cursor)
return list(reversed(path))
Ce n'est pas ma réponse - mon prof l'a fait beaucoup plus efficacement que ma tentative. Voici son approche, utilisant évidemment des fonctions d'assistance pour les tâches répétitives
def dijkstra(graph, source):
vertices, edges = graph
dist = dict()
previous = dict()
for vertex in vertices:
dist[vertex] = float("inf")
previous[vertex] = None
dist[source] = 0
Q = set(vertices)
while len(Q) > 0:
u = minimum_distance(dist, Q)
print('Currently considering', u, 'with a distance of', dist[u])
Q.remove(u)
if dist[u] == float('inf'):
break
n = get_neighbours(graph, u)
for vertex in n:
alt = dist[u] + dist_between(graph, u, vertex)
if alt < dist[vertex]:
dist[vertex] = alt
previous[vertex] = u
return previous
Étant donné un graphique
({'A', 'B', 'C', 'D'}, {('A', 'B', 5), ('B', 'A', 5), ('B', ' C ', 10), (' B ',' D ', 6), (' C ',' D ', 2), (' D ',' C ', 2)}
la commande print(dijkstra(graph, 'A')
donne
Considérant actuellement A avec une distance de 0
Considérant actuellement B avec une distance de 5
Considérant actuellement D avec une distance de 11
Considérant actuellement C avec une distance de 13
iD est:
{'C': 'D', 'D': 'B', 'A': Aucun, 'B': 'A'} => dans un ordre aléatoire
import sys
import heapq
class Node:
def __init__(self, name):
self.name = name
self.visited = False
self.adjacenciesList = []
self.predecessor = None
self.mindistance = sys.maxsize
def __lt__(self, other):
return self.mindistance < other.mindistance
class Edge:
def __init__(self, weight, startvertex, endvertex):
self.weight = weight
self.startvertex = startvertex
self.endvertex = endvertex
def calculateshortestpath(vertexlist, startvertex):
q = []
startvertex.mindistance = 0
heapq.heappush(q, startvertex)
while q:
actualnode = heapq.heappop(q)
for Edge in actualnode.adjacenciesList:
tempdist = Edge.startvertex.mindistance + Edge.weight
if tempdist < Edge.endvertex.mindistance:
Edge.endvertex.mindistance = tempdist
Edge.endvertex.predecessor = Edge.startvertex
heapq.heappush(q,Edge.endvertex)
def getshortestpath(targetvertex):
print("The value of it's minimum distance is: ",targetvertex.mindistance)
node = targetvertex
while node:
print(node.name)
node = node.predecessor
node1 = Node("A");
node2 = Node("B");
node3 = Node("C");
node4 = Node("D");
node5 = Node("E");
node6 = Node("F");
node7 = Node("G");
node8 = Node("H");
Edge1 = Edge(5,node1,node2);
Edge2 = Edge(8,node1,node8);
Edge3 = Edge(9,node1,node5);
Edge4 = Edge(15,node2,node4);
Edge5 = Edge(12,node2,node3);
Edge6 = Edge(4,node2,node8);
Edge7 = Edge(7,node8,node3);
Edge8 = Edge(6,node8,node6);
Edge9 = Edge(5,node5,node8);
Edge10 = Edge(4,node5,node6);
Edge11 = Edge(20,node5,node7);
Edge12 = Edge(1,node6,node3);
Edge13 = Edge(13,node6,node7);
Edge14 = Edge(3,node3,node4);
Edge15 = Edge(11,node3,node7);
Edge16 = Edge(9,node4,node7);
node1.adjacenciesList.append(Edge1);
node1.adjacenciesList.append(Edge2);
node1.adjacenciesList.append(Edge3);
node2.adjacenciesList.append(Edge4);
node2.adjacenciesList.append(Edge5);
node2.adjacenciesList.append(Edge6);
node8.adjacenciesList.append(Edge7);
node8.adjacenciesList.append(Edge8);
node5.adjacenciesList.append(Edge9);
node5.adjacenciesList.append(Edge10);
node5.adjacenciesList.append(Edge11);
node6.adjacenciesList.append(Edge12);
node6.adjacenciesList.append(Edge13);
node3.adjacenciesList.append(Edge14);
node3.adjacenciesList.append(Edge15);
node4.adjacenciesList.append(Edge16);
vertexlist = (node1,node2,node3,node4,node5,node6,node7,node8)
calculateshortestpath(vertexlist,node1)
getshortestpath(node7)
Définir un point d'arrêt dans l'extrait. Vous verrez que vous supprimez des entrées de Q mais jamais de w. Tout le reste est un dicton, mais Q/w est un tableau apparié que vous ne tenez pas à jour. Vous devez garder ces deux synchronisés ou les remplacer par un dict. Remarque spéciale: lorsque l’algorithme fonctionnera, vous voudrez peut-être éventuellement remplacer Q/w par une liste d’arêtes et re-coder la fonction "extract" par une file d’attente prioritaire (module heapq).
De plus, vous verrez que w a toujours une pondération de 0 pour la source et 'inf' pour tous les autres nœuds. Vous avez complètement ignoré l'étape critique de mise à jour des distances candidates.
Vous prenez donc toujours le premier chemin que vous rencontrez, plutôt que de choisir le chemin le plus court. Vous calculerez plus tard la distance réelle de ce chemin. Le tableau de distances renvoyé a donc des valeurs réelles, mais elles ont été choisies de manière arbitraire et vous n’avez aucune raison de vous attendre à ce qu’elles soient les plus courtes.
Après avoir (incorrectement) trouvé le nœud suivant, vous examinez tous ses bords. Cela aurait dû être l'étape critique que j'ai mentionnée ci-dessus dans le deuxième paragraphe, où vous mettez à jour les candidats pour le noeud suivant. Au lieu de cela, vous faites quelque chose de complètement différent: vous semblez parcourir toutes les solutions précédentes (qui sont garanties correctes et que vous devez laisser telles quelles si vous implémentez correctement dijkstra) et vous recherchez une solution en deux étapes à partir de source-> current- > aucun. L’intention correcte de les regarder aurait été d’ajouter le prochain candidat des chemins précédents au noeud suivant, mais comme ils ne sont jamais ajoutés, vous ne regardez pas (le chemin le plus court précédent) + (une étape), vous ne regardez que à littéralement deux solutions de nœud.
Donc, fondamentalement, vous parcourez tous les chemins possibles à deux nœuds à partir de la source afin de trouver les chemins les plus courts. Ceci est une erreur complète et n'a rien à voir avec dijkstra. Mais cela fonctionne presque sur votre tout petit graphique où la plupart des chemins les plus corrects sont des chemins en deux étapes.
(ps: je suis d’accord avec tout le monde sur vos noms de variables. Vous auriez beaucoup mieux fait si vous utilisiez des noms verbeux indiquant ce que ces variables représentent. Je devais les renommer avant de pouvoir analyser votre code où que ce soit.)