web-dev-qa-db-fra.com

Comment tracer le chemin dans une recherche en largeur d'abord?

Comment tracez-vous le chemin d'une recherche en largeur d'abord, comme dans l'exemple suivant:

Si vous recherchez une clé 11, retourne la liste la plus courte en connectant 1 à 11.

[1, 4, 7, 11]
86

Vous devriez d'abord regarder http://en.wikipedia.org/wiki/Breadth-first_search .


Ci-dessous, une implémentation rapide dans laquelle j'ai utilisé une liste de liste pour représenter la file d'attente de chemins.

# graph is in adjacent list representation
graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, start, end):
    # maintain a queue of paths
    queue = []
    # Push the first path into the queue
    queue.append([start])
    while queue:
        # get the first path from the queue
        path = queue.pop(0)
        # get the last node from the path
        node = path[-1]
        # path found
        if node == end:
            return path
        # enumerate all adjacent nodes, construct a new path and Push it into the queue
        for adjacent in graph.get(node, []):
            new_path = list(path)
            new_path.append(adjacent)
            queue.append(new_path)

print bfs(graph, '1', '11')

Une autre approche consisterait à conserver un mappage de chaque nœud à son parent et, lors de l'inspection du nœud adjacent, enregistrez son parent. Lorsque la recherche est terminée, il suffit de revenir en arrière selon le mappage parent.

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def backtrace(parent, start, end):
    path = [end]
    while path[-1] != start:
        path.append(parent[path[-1]])
    path.reverse()
    return path


def bfs(graph, start, end):
    parent = {}
    queue = []
    queue.append(start)
    while queue:
        node = queue.pop(0)
        if node == end:
            return backtrace(parent, start, end)
        for adjacent in graph.get(node, []):
            if node not in queue :
                parent[adjacent] = node # <<<<< record its parent 
                queue.append(adjacent)

print bfs(graph, '1', '11')

Les codes ci-dessus sont basés sur l'hypothèse qu'il n'y a pas de cycles.

160
qiao

J'ai beaucoup aimé la première réponse de qiao! La seule chose qui manque ici est de marquer les sommets comme visités.

Pourquoi devons-nous le faire?
Imaginons qu’un autre nœud numéro 13 soit connecté au nœud 11. Notre objectif est maintenant de trouver le nœud 13.
Après un peu d’exécution, la file d’attente ressemblera à ceci:

[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]

Notez qu'il existe DEUX chemins avec le numéro de noeud 10 à la fin.
Ce qui signifie que les chemins du nœud 10 seront vérifiés deux fois. Dans ce cas, cela ne semble pas si grave, car le noeud numéro 10 n’a pas d’enfant. Mais cela pourrait être très grave (même dans ce cas, nous allons vérifier ce noeud deux fois sans raison.)
Le numéro de noeud 13 ne se trouve pas dans ces chemins, le programme ne reviendra pas avant d'atteindre le deuxième chemin avec le numéro de noeud 10 à la fin..Et nous allons le revérifier ..

Tout ce qui nous manque, c'est un ensemble pour marquer les nœuds visités et ne pas les vérifier à nouveau.
Ceci est le code de qiao après la modification:

graph = {
    1: [2, 3, 4],
    2: [5, 6],
    3: [10],
    4: [7, 8],
    5: [9, 10],
    7: [11, 12],
    11: [13]
}


def bfs(graph_to_search, start, end):
    queue = [[start]]
    visited = set()

    while queue:
        # Gets the first path in the queue
        path = queue.pop(0)

        # Gets the last node in the path
        vertex = path[-1]

        # Checks if we got to the end
        if vertex == end:
            return path
        # We check if the current node is already in the visited nodes set in order not to recheck it
        Elif vertex not in visited:
            # enumerate all adjacent nodes, construct a new path and Push it into the queue
            for current_neighbour in graph_to_search.get(vertex, []):
                new_path = list(path)
                new_path.append(current_neighbour)
                queue.append(new_path)

            # Mark the vertex as visited
            visited.add(vertex)


print bfs(graph, 1, 13)

Le résultat du programme sera:

[1, 4, 7, 11, 13]

Sans la revérification de la nettececery ..

20
Or Kazaz

J'ai pensé essayer de coder ceci pour le plaisir:

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, forefront, end):
    # assumes no cycles

    next_forefront = [(node, path + ',' + node) for i, path in forefront if i in graph for node in graph[i]]

    for node,path in next_forefront:
        if node==end:
            return path
    else:
        return bfs(graph,next_forefront,end)

print bfs(graph,[('1','1')],'11')

# >>>
# 1, 4, 7, 11

Si vous voulez des cycles, vous pouvez ajouter ceci:

for i, j in for_front: # allow cycles, add this code
    if i in graph:
        del graph[i]
8
robert king

Code très facile. Vous continuez à ajouter le chemin chaque fois que vous découvrez un nœud.

graph = {
         'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])
         }
def retunShortestPath(graph, start, end):

    queue = [(start,[start])]
    visited = set()

    while queue:
        vertex, path = queue.pop(0)
        visited.add(vertex)
        for node in graph[vertex]:
            if node == end:
                return path + [end]
            else:
                if node not in visited:
                    visited.add(node)
                    queue.append((node, path + [node]))
6
SeasonalShot

J'aime à la fois la première réponse de @Qiao et l'addition de @Or. Pour un peu moins de traitement, j'aimerais ajouter à la réponse de Ou.

Dans la réponse de @Or, le suivi du nœud visité est excellent. Nous pouvons également permettre au programme de se terminer plus tôt qu’il ne l’est actuellement. À un moment donné dans la boucle for, le current_neighbour devra être le end, et une fois que cela se produit, le chemin le plus court est trouvé et le programme peut revenir.

Je voudrais modifier la méthode comme suit, porter une attention particulière à la boucle

graph = {
1: [2, 3, 4],
2: [5, 6],
3: [10],
4: [7, 8],
5: [9, 10],
7: [11, 12],
11: [13]
}


    def bfs(graph_to_search, start, end):
        queue = [[start]]
        visited = set()

    while queue:
        # Gets the first path in the queue
        path = queue.pop(0)

        # Gets the last node in the path
        vertex = path[-1]

        # Checks if we got to the end
        if vertex == end:
            return path
        # We check if the current node is already in the visited nodes set in order not to recheck it
        Elif vertex not in visited:
            # enumerate all adjacent nodes, construct a new path and Push it into the queue
            for current_neighbour in graph_to_search.get(vertex, []):
                new_path = list(path)
                new_path.append(current_neighbour)
                queue.append(new_path)

                #No need to visit other neighbour. Return at once
                if current_neighbour == end
                    return new_path;

            # Mark the vertex as visited
            visited.add(vertex)


print bfs(graph, 1, 13)

La sortie et tout le reste seront les mêmes. Cependant, le code prendra moins de temps à traiter. Ceci est particulièrement utile sur les grands graphiques. J'espère que cela aidera quelqu'un à l'avenir.

0
Darie Dorlus