Ya había trabajado anteriormente con los algoritmos de Dijkstra y Floyd-Warshall. En esta entrada se implementaron los algoritmos en Matlab. Esta vez usaremos Python 3.5 con la librería Numpy y el módulo de grafos dinámicos realizado anteriormente en esta entrada.
Dijkstra
El algoritmo de dijkstra es SSSP (Single Source Shortest Path), dado un nodo origen encontrar el camino más corto para todos los demás nodos. La función en el módulo es:
def sssp_dijkstra(self, source): total_vertex = len(self.vertex) Q = np.array(self.vertex) dist = np.zeros(total_vertex) dist.fill(np.inf) dist[self.vertex == source] = 0 while len(Q) != 0: min = np.inf u = 0 for q in Q: if dist[self.vertex == q] <= min: min = dist[self.vertex == q] u = q Q = np.delete(Q, np.argwhere(Q == u)) for v in self.target[self.source == u]: alt = dist[self.vertex == u] + self.get_weight(u, v) index_v = self.vertex == v if alt < dist[index_v]: dist[index_v] = alt
Pero para adaptarla para todos los nodos, es decir para el problema APSP (All Pairs Shortest Path), solomente hacemos un llamado a la función anterior para cada nodo del grafo, el algoritmo:
def apsp_dijkstra(self): result = np.full((self.vertex.size, self.vertex.size), np.inf) count = 0 for v in self.vertex: result[count] = self.sssp_dijkstra(v) count = count + 1 return result
Con esto podemos tener una matriz de distancias, probemos los algoritmos con el siguiente script:
from _graph.GraphPro import GraphPro as g from time import time import os os.system('clear') print("<--------Test Dijkstra------->\n") weights = [1, 2, 3, 4, 5] graph = g.creategraph(6, .75, weights, directed=False) graph.print_r() print('.........................') t = time() print(graph.apsp_dijkstra()) elapsed = time() - t print("Time: ", elapsed) graph.draw()
El script anterior genera un grafo aleatorio con seis nodos, con una probabilidad de armar arco de 75%, unos pesos específicos y no dirigidos. Guardamos el tiempo de resolución del algoritmo Dijkstra y dibujamos el grafo.

Figura 1. Grafo generado aleatoriamente para el algoritmo Dijkstra
El resultado de los caminos más cortos, se presenta por consola:
<--------Test Dijkstra-------> Source: [ 0. 1. 0. 2. 0. 3. 0. 5. 1. 2. 1. 3. 1. 4. 1. 5. 2. 3. 2. 4. 3. 4. 3. 5. 4. 5.] Target: [ 1. 0. 2. 0. 3. 0. 5. 0. 2. 1. 3. 1. 4. 1. 5. 1. 3. 2. 4. 2. 4. 3. 5. 3. 5. 4.] Weight: [ 2. 2. 2. 2. 4. 4. 1. 1. 3. 3. 2. 2. 4. 4. 4. 4. 2. 2. 1. 1. 3. 3. 5. 5. 1. 1.] Vertex: [ 0. 1. 2. 3. 4. 5.] ......................... [[ 0. 2. 2. 4. 2. 1.] [ 2. 0. 3. 2. 4. 3.] [ 2. 3. 0. 2. 1. 2.] [ 4. 2. 2. 0. 3. 4.] [ 2. 4. 1. 3. 0. 1.] [ 1. 3. 2. 4. 1. 0.]] Time: 0.006327390670776367
El resultado del algoritmo se lee colocando los vértices de izquierda a derecha y de abajo hacia arriba. Por ejemplo para el camino más corto en el grafo de la Figura 1 del nodo 2 al nodo 5 es de 2.0.
Floyd-Warshall
Para el algoritmo de Floyd-Warshall tenemos:
def floyd_warshall(self): total_vertex = len(self.vertex) dist = np.zeros((total_vertex, total_vertex)) dist.fill(np.inf) for idx in range(self.source.size): index_s = self.vertex == self.source[idx] index_t = self.vertex == self.target[idx] dist[index_s, index_t] = self.weight[idx] for index in range(self.vertex.size): dist[index, index] = 0 for i in range(self.vertex.size): for j in range(self.vertex.size): for k in range(self.vertex.size): if dist[i, j] > dist[i, k] + dist[k, j]: dist[i, j] = dist[i, k] + dist[k, j] return dist
Para este caso pasamos el mismo grafo para comprobar que sea la misma respuesta, editamos nuestro script de prueba:
from _graph.GraphPro import GraphPro as g from time import time import os os.system('clear') print("<--------Test Dijkstra------->\n") weights = [1, 2, 3, 4, 5] #graph = g.creategraph(6, .75, weights, directed=False) sources = [0, 1, 0, 2, 0, 3, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 3, 4, 3, 5, 4, 5] targets = [1, 0, 2, 0, 3, 0, 5, 0, 2, 1, 3, 1, 4, 1, 5, 1, 3, 2, 4, 2, 4, 3, 5, 3, 5, 4] weights = [2, 2, 2, 2, 4, 4, 1, 1, 3, 3, 2, 2, 4, 4, 4, 4, 2, 2, 1, 1, 3, 3, 5, 5, 1, 1] graph = g(sources, targets, weights) graph.print_r() print('.........................') t = time() print(graph.floyd_warshall()) elapsed = time() - t print("Time: ", elapsed) graph.draw()
Los resultados fueron idénticos:
<--------Test Floyd-Warshall-------> Source: [0 1 0 2 0 3 0 5 1 2 1 3 1 4 1 5 2 3 2 4 3 4 3 5 4 5] Target: [1 0 2 0 3 0 5 0 2 1 3 1 4 1 5 1 3 2 4 2 4 3 5 3 5 4] Weight: [2 2 2 2 4 4 1 1 3 3 2 2 4 4 4 4 2 2 1 1 3 3 5 5 1 1] Vertex: [0 1 2 3 4 5] ......................... [[ 0. 2. 2. 4. 2. 1.] [ 2. 0. 3. 2. 4. 3.] [ 2. 3. 0. 2. 1. 2.] [ 4. 2. 2. 0. 3. 4.] [ 2. 4. 1. 3. 0. 1.] [ 1. 3. 2. 4. 1. 0.]] Time: 0.0016369819641113281
Bueno, no tan idénticos. El Floyd-Warshall duró significativamente menos. Aunque en el anterior entrada de Matlab se notaba que el algoritmo de Dijkstra se demoraba mucho más. También debemos de probarlo en Python 3.5
Comparación de algoritmos
Para la comparación de los algoritmos se implementa una clase para pruebas
from _graph.GraphPro import GraphPro as g from time import time import numpy as np class GraphTest: def __init__(self, from_num_nodes=5, until_num_nodes=20, probability_edges=.5, num_tries=3000, weights=[1, 2, 3, 4, 5, 6]): self.from_num_nodes = from_num_nodes self.until_num_nodes = until_num_nodes self.probability_edges = probability_edges self.num_tries = num_tries self.weights = weights def mean(self, num_nodes): mean_dijkstra = 0 mean_floyd = 0 if self.num_tries <= 0: return 0 for times in range(self.num_tries): graph = g.creategraph(num_nodes, self.probability_edges, self.weights) t = time() graph.apsp_dijkstra() lapsed = time() - t mean_dijkstra = mean_dijkstra + lapsed t = time() graph.floyd_warshall() lapsed = time() - t mean_floyd = mean_floyd + lapsed return [mean_dijkstra / self.num_tries, mean_floyd / self.num_tries] def node_increments(self): num_nodes = self.from_num_nodes num_tries = self.until_num_nodes-self.from_num_nodes results = np.zeros((2, num_tries)) for x in range(num_tries): print('Size nodes: ', num_nodes) times = self.mean(num_nodes) results[0, x] = times[0] results[1, x] = times[1] num_nodes = num_nodes + 1 return results
Lo que realiza este script es muy similar al que realiza mi anterior entrada en Matlab. Genera miles de veces grafos aleatorios con cierta cantidad de nodos. Cuando se promedio el tiempo de resolución del algoritmo, se incrementa el número de nodos a generar y se analiza otra vez miles de veces y se promedia el tiempo.
La función mean( num_nodes ) es la que intenta varias veces con grafos aleatorios en cierta cantidad de nodos. Los algoritmos se ejecutan uno después del otro para que analizen el mismo grafo.
La función node_increments( ) es la que llama la función anterior mientras va incrementando el número de nodos. Los resultados se van almacenando y se retornan.
El siguiente script utiliza los métodos anteriores:
import matplotlib.pyplot as plt from GraphTest import GraphTest import numpy as np import os os.system('clear') print("<--------Test ------->\n") test = GraphTest(num_tries=1000) results = test.node_increments() num_nodes = np.arange(test.from_num_nodes, test.until_num_nodes, 1) np.savetxt('test_algoritms_apsp.out', (num_nodes, results[0], results[1])) plt.plot(num_nodes, results[0], 'ro', num_nodes, results[1], 'r--') plt.ylabel('Tiempo en promedio para resolver') plt.xlabel('Número de nodos') plt.show()
Este script indica que intenté 1000 veces grafos aleatorios con un mismo número de nodos. Los resultados se almacenan en un archivo, por si el usuario desea analizar los resultados con otra herramienta. Los resultados se grafican con la función de matplotlib. La gráfica:

Figura 2. Comparación entre Dijkstra y Floyd-Warshall
La Figura 2 muestra una comparación entre los tiempos de resolución de APSP para varios grafos aleatorios con distintos números de nodos, desde 5 nodos hasta 20 nodos. El código fuente se encuentra en mi repositorio público: https://github.com/arturoverbel/graph_python.
Conclusiones
Así como en la entrada anterior, donde se realiza la comparación de Dijkstra y Floyd-Warshall en Matlab, Floyd-Warshall muestra un crecimiento menos pronunciado que Dijkstra.
1 Pingback