Uno de los algoritmos clásicos de ruta más corta Dijkstra
Prefacio
Hoy hablamos de la ruta más corta de búsqueda de gráficos. La ruta más corta aquí incluye la distancia o el consumo de tiempo que debe considerarse de un nodo a otro nodo, es decir, el tiempo (u otras formas de consumo) requerido para llegar al El final desde el punto de partida es el mínimo. , A diferencia del camino más corto de la categoría de búsqueda del laberinto que mencionamos anteriormente, hay menos nodos en el camino (es decir, se supone que la distancia entre los nodos es 1)
Ps: El artículo original es el tweet de WeChat del blogger, algunos de los cuales se pondrán en contacto, ahora se mudan a CSDN ~
1. Explicación del principio del algoritmo
Lenguaje de implementación: Python
Para los problemas anteriores, miramos la siguiente imagen:
En este momento, parece que necesitamos atravesar los nodos en el gráfico. La ruta más corta obtenida por la búsqueda BFS convencional no es necesariamente la que requiere menos tiempo.
Aquí presentamos el algoritmo clásico de búsqueda de ruta más corta de una sola fuente: el algoritmo de Dijkstra . Fuente única significa que puede obtener la distancia más corta desde un punto de inicio específico a todos los demás nodos.
Esencialmente es un algoritmo de optimización de bucles . En cada bucle, completa los siguientes pasos:
1. Encuentre el nodo con la distancia más corta conocida desde el punto de inicio, es decir, el punto que se puede alcanzar más rápido2. Actualice el costo de distancia de los nodos circundantes a través de otros nodos conectados a este punto, es decir, la distancia desde el punto de partida a estos nodos circundantes
Tomando el diagrama anterior como ejemplo, usemos un diagrama para mostrar un flujo de algoritmo simple:
En primer lugar, el nodo más corto que conocemos primero es el punto de partida en sí. Al principio, tenemos que configurar todos los puntos para que sean infinitos y luego usar los bordes conocidos para actualizar estos valores, pero el gráfico que di aquí tiene todos los pesos de borde Dado. A partir del punto de partida M, se encuentran A, B, N y se actualizan los pesos.
Al comienzo del segundo ciclo, después de encontrar el punto más cercano al punto de inicio como B, comience a buscar otros nodos conectados a B. Recuerde que estos nodos no pueden ser los puntos más cercanos marcados por el ciclo anterior, porque los puntos más cercanos determinados por los ciclos anteriores son para esta vuelta No tiene sentido actualizar su distancia al punto de partida, ya es el más cercano.
Podemos ver que hay A y N que cumplen con las condiciones, y luego actualizan su distancia al punto de partida. Se encuentra que MB + BA <MA, es decir, hay una forma más cercana de llegar al punto A desde el punto de partida, por lo que se actualiza el valor de MA, y también se actualiza el valor de MN.
En el tercer ciclo, el punto más cercano al punto de inicio es A. Encuentre el nodo que está conectado a A y no ha sido marcado. Solo hay N, pero MA + AN = 10> MN = 8, entonces el valor de distancia de MN no está actualizado.
El siguiente ciclo puede salir después de alcanzar el punto final, por lo que se puede obtener la distancia más corta desde el punto de inicio a todos los demás nodos, incluido el punto de inicio al punto final.
Dos, implementación de código
def Dijkstra(directed=False):
"""
Functions: Implementation of Dijkstra using Python
Args:
directed: whether the graph is directed or not
return:
None
"""
# init distance
limit = 10000
# number of nodes, number of links, start_index, end_index
N, K, s, e = list(map(int, input().split()))
# graph mat
ad_mat = [[0 for i in range(N)] for j in range(N)]
# distance to start_node
Dis = [limit for i in range(N)]
# tags array
vis = [0 for i in range(N)]
# use links to fresh graph mat
for i in range(K):
u, v, w = list(map(int, input().split()))
ad_mat[u][v] = w
if directed == False:
ad_mat[v][u] = w
# init distance of start_node
Dis[s] = 0
# core logic
for i in range(N):
# select nodes with shortest distance to start from unchecked nodes
min_ind = Dis.index(min([Dis[k] for k in range(len(Dis)) if vis[k] == 0]))
# sign the node
vis[min_ind] = 1
for j in range(N):
# fresh distance from start_node to adjacent nodes
# unchecked, exist links, shorter then original dis
if vis[j] == 0 \
and ad_mat[min_ind][j] != 0 \
and Dis[j] > (Dis[min_ind]+ad_mat[min_ind][j]):
Dis[j] = Dis[min_ind]+ad_mat[min_ind][j]
print('Shortest distance from s to e: {}'.format(Dis[e]))
return
Detalles del código explicados
min_ind = Dis.index(min([Dis[k] for k in range(len(Dis)) if vis[k] == 0]))
Esta oración es para encontrar el índice del nodo más cercano al punto de partida de los nodos que no están marcados como el punto más cercano por el bucle anterior.
if vis[j] == 0 and ad_mat[min_ind][j] != 0 \
and Dis[j] > (Dis[min_ind]+ad_mat[min_ind][j]):
Dis[j] = Dis[min_ind]+ad_mat[min_ind][j]
Este es el paso central para actualizar la distancia (también llamado proceso de relajación) : después de encontrar el punto A más cercano de este ciclo, busque el no marcado de sus nodos vecinos (si se ha marcado, significa que es el punto más cercano y actualizado Nodos circundantes), hay un vínculo de ruta con A, y la distancia entre el punto de inicio y A y A al nodo y al nodo que es menor que la distancia desde el punto de inicio actual al nodo, y luego el valor de distancia desde se actualiza el punto de partida al punto.
complejidad del tiempo
A través de la introducción del código, podemos encontrar que la complejidad temporal de la implementación dada es O (n ^ 2)
Prueba de muestra
Ejecutemos un ejemplo de prueba:
>>> Dijkstra(directed=False)
7 9 0 6
0 1 9
1 2 12
2 3 6
1 3 5
3 4 14
4 5 3
3 5 8
5 6 10
6 1 7
Shortest distance from s to e: 16
Time used: 0.01053s
>>> Dijkstra(directed=True)
7 9 0 6
0 1 9
1 2 12
2 3 6
1 3 5
3 4 14
4 5 3
3 5 8
5 6 10
6 1 7
Shortest distance from s to e: 32
Time used: 0.01255s
El algoritmo de Dijkstra anterior es aplicable tanto a gráficos dirigidos como no dirigidos. A continuación, dibujamos la ruta más corta en dos casos:
3. BFS se da cuenta de Dijkstra
Idea de realización
A continuación, utilizaremos la idea de BFS combinada con colas de prioridad para implementar el algoritmo de Dijkstra . A través de nuestra explicación anterior, podemos encontrar que cada ciclo es para encontrar el punto más cercano al punto de inicio que no ha sido marcado. Este proceso se puede abstraer para encontrar la ruta más corta en BFS.
Sin embargo, debido a que los nodos agregados a la cola están desordenados, la cola de prioridad debe usarse para ordenar las operaciones y definir un objeto que se pueda comparar.
De esta forma, cada vez que lleguemos al punto más cercano al punto de partida a través del BFS, y luego realicemos la operación de relajación, se reducirá mucho el proceso de comparación, en general, cada borde solo se ha verificado una vez.
Primero tenemos que definir un objeto de cola comparable :
# comparable object definition
class compare_obj:
def __init__(self, s, dis):
self.s = s # node_index
self.dis = dis # distance from start node to this
def __lt__(self, other):
return self.dis < other.dis
La configuración de algunas matrices y variables temporales sigue siendo la misma que antes, y se agrega una matriz G para registrar el número de nodos adyacentes para cada nodo.
Código
# 使用BFS结合优先队列实现Dijkstra
def BFS_Dijkstra(directed=False):
"""
Functions: Implementation of Dijkstra using BFS and PriorityQueue
Args:
directed: whether the graph is directed or not
return:
None
"""
import time
from queue import PriorityQueue
# comparable object definition
class compare_obj:
def __init__(self, s, dis):
self.s = s
self.dis = dis
def __lt__(self, other):
return self.dis < other.dis
# init distance
limit = 10000
# number of nodes, number of links, start_index, end_index
N, K, s, e = list(map(int, input().split()))
start = time.time()
# graph mat
ad_mat = [[0 for i in range(N)] for j in range(N)]
# distance to start_node
Dis = [limit for i in range(N)]
# tags array
vis = [0 for i in range(N)]
# number of adjacent nodes of one node
G = [[] for i in range(N)]
# use links to fresh graph mat
for i in range(K):
u, v, w = list(map(int, input().split()))
ad_mat[u][v] = w
G[u].append(v)
if directed == False:
ad_mat[v][u] = w
G[v].append(u)
# init distance of start_node
Dis[s] = 0
# BFS with Priority Queue
Q = PriorityQueue()
Q.put(compare_obj(s, Dis[s]))
while Q.qsize() != 0:
node = Q.get_nowait()
s, dis = node.s, node.dis
# if check, continue
if vis[s]:
continue
# sign the node
vis[s] = True
# fresh distance
for i in range(len(G[s])):
ad_node = G[s][i]
if not vis[ad_node] and Dis[ad_node] > (ad_mat[s][ad_node]+Dis[s]):
Dis[ad_node] = ad_mat[s][ad_node]+Dis[s]
Q.put_nowait(compare_obj(ad_node, Dis[ad_node]))
print('Shortest distance from s to e: {}'.format(Dis[e]))
print('Time used: {:.5f}s'.format(time.time()-start))
return
Muestra de prueba
Echemos un vistazo a los resultados de la prueba:
>>> BFS_Dijkstra(True)
7 9 0 6
0 1 9
1 2 12
2 3 6
1 3 5
3 4 14
4 5 3
3 5 8
5 6 10
6 1 7
Shortest distance from s to e: 32
Time used: 0.00573s
Se puede ver que el tiempo de ejecución del algoritmo Dijkstra implementado por BFS es dos veces más rápido que la implementación del bucle for en el ejemplo dado.
Cuatro, resumen:
Además del algoritmo de Dijkstra, hay otros algoritmos clásicos de búsqueda de ruta más corta, y continuaré llenando los huecos más adelante.
Las ideas de relajación entre diferentes algoritmos son básicamente las mismas, es decir, encontrar un camino más corto y actualizar el valor de la distancia. Pero la estrategia de búsqueda y los tipos de problemas que son adecuados para resolver varían.
Referencia
[1] Pequeña columna de algoritmos: búsqueda de ruta más corta de Dijkstra