web-dev-qa-db-fra.com

L'algorithme de Dijkstra en python

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?

6
user3504080

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}
21
Hyperboreus

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))
9
Archibald

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

7
docsnuz
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)
0
shadowroot

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.)

0
Kenny Ostrom