2012-01-19 21 views
66

¿Cómo se traza la trayectoria de una búsqueda primero en amplitud, de tal manera que en el ejemplo siguiente:¿Cómo rastrear la ruta en una búsqueda de primer orden?

Si la búsqueda de clave 11, devuelve la lista más corta de conexión 1 a 11.

[1, 4, 7, 11] 
+4

En realidad, era una tarea pasada que estaba ayudando a un amigo hace meses, basada en la Ley Kevin Bacon. Mi solución final fue muy descuidada, básicamente hice otra búsqueda para "rebobinar" y retroceder. Quiero encontrar una mejor solución. –

+17

Excelente. Considero que revisar un viejo problema en un intento de encontrar una mejor respuesta es un rasgo admirable en un ingeniero. Te deseo lo mejor en tus estudios y carrera. –

+1

Gracias por el elogio, solo creo que si no lo aprendo ahora, me enfrentaré nuevamente con el mismo problema. –

Respuesta

128

Primero debe tener en cuenta http://en.wikipedia.org/wiki/Breadth-first_search.


A continuación se muestra una implementación rápida, en la que he usado una lista de la lista para representar a la cola de caminos.

# 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') 

Otro enfoque sería el mantenimiento de una correspondencia de cada nodo para su matriz, y al inspeccionar el nodo adyacente, constancia de su padre. Cuando se realiza la búsqueda, simplemente rastree según el mapeo padre.

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

Los códigos anteriores se basan en la suposición de que no hay ciclos.

+2

¡Esto es excelente! Mi proceso de pensamiento me llevó a creer en la creación de algún tipo de tabla o matriz, todavía tengo que aprender sobre gráficos. Gracias. –

+0

También traté de utilizar un método de rastreo posterior, aunque parece mucho más limpio. ¿Sería posible hacer un gráfico si solo conoce el inicio y el final pero ninguno de los nodos intermedios? O incluso otro enfoque además de gráficos? –

+0

@ChristopherM No pude entender su pregunta :( – qiao

8

pensé que iba a tratar este código para la diversión:

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 desea ciclos se podría añadir lo siguiente:

for i, j in for_front: # allow cycles, add this code 
    if i in graph: 
     del graph[i] 
+4

+1, me recordó una cosa: 'asumir que no hay ciclos' :) – qiao

+0

¿Dónde agregaría este fragmento de código? – Liondancer

+0

después de haber creado el next_for_front. Una pregunta de seguimiento, ¿y si el gráfico contiene bucles? P.ej. si el nodo 1 tenía un borde conectado de nuevo a sí mismo? ¿Qué pasa si el gráfico tiene múltiples bordes entre dos nodos? –

14

me gustó la primera respuesta de Qiao mucho! Lo único que falta aquí es marcar los vértices como visitados.

¿Por qué tenemos que hacerlo?
imaginemos que existe otro número de nodo 13 conectado desde el nodo 11. Ahora nuestro objetivo es encontrar el nodo 13.
Después de un poco de una carrera de la cola se verá así:

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

Nota que hay DOS rutas con el nodo número 10 al final.
Lo que significa que las rutas del nodo 10 se verificarán dos veces. En este caso, no se ve tan mal porque el nodo número 10 no tiene hijos ... Pero podría ser realmente malo (incluso aquí revisaremos ese nodo dos veces sin motivo ...)
Número de nodo 13 isn ' t en esas rutas para que el programa no regrese antes de llegar a la segunda ruta con el nodo número 10 al final ... Y lo volveremos a comprobar ...

Lo único que falta es un conjunto para marcar la visita nodos y no para comprobar de nuevo ..
Este es el código de Qiao después de la modificación:

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) 

La salida del programa será:

[1, 4, 7, 11, 13] 

Sin las comprobaciones innecesarias.

+3

Podría ser útil usar 'collections.deque' para' queue' como list.pop (0) incurrir en 'O (n)' movimientos de memoria. Además, por el bien de la posteridad, si quiere hacer DFS simplemente configure 'path = queue.pop()' en cuyo caso la variable 'queue' en realidad actúa como' stack'. – Sudhi

0

Me gustan tanto la primera respuesta de @Qiao como la adición de @O. Por un poco menos de procesamiento me gustaría agregar a la respuesta de Or.

En la respuesta de @ Or, el seguimiento del nodo visitado es excelente. También podemos permitir que el programa salga antes de lo que actualmente es. En algún punto del bucle for el current_neighbour tendrá que ser el end, y una vez que eso suceda, se encontrará la ruta más corta y el programa podrá regresar.

que modificaría el método de la siguiente manera, prestar mucha atención al bucle for

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 salida y todo lo demás será el mismo. Sin embargo, el código tardará menos tiempo en procesarse. Esto es especialmente útil en gráficos más grandes. Espero que esto ayude a alguien en el futuro.

Cuestiones relacionadas