Ya vimos cómo realizar los algoritmos de Dijkstra & Floyd-Warshall en Matlab, Algoritmos Dijkstra & Floyd-Warshall, ahora veremos cómo hacer estos algoritmos en Python. Pero antes de empezar es importante conocer las siguientes librerías:

  • NetworkX: Como dice su sitio oficial. Es un paquete de Python para la creación, manipulación y estudio de la estructura, dinámica y funciones de redes complejas. Pueden encontrar documentación de esta librería en: Overview – NetworkX
  • Matplotlib: Es una biblioteca de trazado 2D de Python que produce figuras en una variedad de formatos y entornos. Lo utilizaremos para graficar el grafo, aunque no es tan intuitivo como las gráficas de Matlab. Matplotlib.

El código fuente está público en mi repositorio:

https://github.com/arturoverbel/graph_python

Ahora comenzemos

Dijkstra

El pseudocódigo de Dijkstra es:

function Dijkstra(Graph, source):

       create vertex set Q

       for each vertex v in Graph:             // Initialization
           dist[v] ← INFINITY                  // Unknown distance from source to v
           add v to Q                         // All nodes initially in Q (unvisited nodes)

       dist[source] ← 0                // Distance from source to source

       while Q is not empty:
           u ← vertex in Q with min dist[u]    // Node with the least distance
                                               // will be selected first
           remove u from Q

           for each neighbor v of u:           // where v is still in Q.
               alt ← dist[u] + length(u, v)
               if alt < dist[v]:               // A shorter path to v has been found
                   dist[v] ← alt

       return dist[], prev[]

El código en Python sería:

def dijkstra(self):
    nodes = list(self.graph.nodes)

    for i in nodes: #Set first values
        dict_i = {}
        for j in nodes:
            if i == j:
                dict_i[j] = 0
            else:
                dict_i[j] = float("inf")
        self.distances[i] = dict_i

    for oneNode in nodes: #Start algoritm
        Q = []
        for node in nodes: Q.append(node)

        while len(Q) != 0:
            v = 0
            min = float("inf")
            for node_q in Q:
                if self.distances[oneNode][node_q] <= min:
                    min = self.distances[oneNode][node_q]
                    v = node_q

            Q.remove(v)

            neighbors = list(self.graph.neighbors(v))

            for neighborV in neighbors:
                w = self.graph[v][neighborV]['weight']
                alt = self.distances[oneNode][v] + w
                if alt < self.distances[oneNode][neighborV]:
                    self.distances[oneNode][neighborV] = alt

    return self.distances

Debido a que el código original está en una clase en un archivo Python, nos referimos al grafo como self.graph y el resultado en un diccionario de Python como self.distances .

El código es más fácil de digerir que el código Matlab. Tener en cuenta que el algoritmo Dijkstra está diseñado para encontrar el camino más cerca de un nodo fuente a un nodo destino. Pero como en este ejercicio necesitamos calcular el camino más corto de todos los pares, entonces cojeremos uno por uno.

La obtención de los vecinos de un nodo como los pesos es mucho más fácil de encontrar que en Matlab pero la creación de un grafo es mucho más fácil en Matlab. Tenemos la función para generar un grafo:

def create_network(self, source, target, weight):

    self.graph = nx.DiGraph()
    count = len(source)

    edges = []

    for i in range(0, count):
        edges.append( (source[i], target[i], weight[i]) )

    self.graph.add_weighted_edges_from(edges)
    return self.graph

Podemos entonces probar utilizando el siguiente código:

import matplotlib.pyplot as plt
import networkx as nx
import graph as g
import os

os.system('cls')

source = [1,2,2,2,3,4]
target = [2,1,3,4,4,3]
weight = [1,1,2,4,1,1]

G = g.graph()
G.create_network(source, target, weight)
print("Dijkstra\n")

G.dijkstra()
G.print_distances()

El código fuente puede ser descargado desde el repositorio de mi cuenta personal de GitHub:

https://github.com/arturoverbel/graph_python

El grafo de prueba sería:

Figura 1. Grafo generado con Python

 

Floyd Warshall

Pseudocódigo:

Pseudocodigo:

let dist be a |V| × |V| array of minimum distances initialized to ∞ (infinity)
for each vertex v
    dist[v][v] ← 0
for each edge (u,v)
   dist[u][v] ← w(u,v) // the weight of the edge (u,v)
for k from 1 to |V|
   for i from 1 to |V|
      for j from 1 to |V|
         if dist[i][j] > dist[i][k] + dist[k][j]
            dist[i][j] ← dist[i][k] + dist[k][j]
         end if

En Python:

def floyd_warshall(self):
    nodes = list(self.graph.nodes)

    for i in nodes:
        dict_i = {}
        for j in nodes:
            if i == j:
                dict_i[j] = 0
                continue
            try:
                dict_i[j] = self.graph[i][j]['weight']
            except:
                dict_i[j] = float("inf")

        self.distances[i] = dict_i

    for i in nodes:
        for j in nodes:
            for k in nodes:
                ij = self.distances[i][j]
                ik = self.distances[i][k]
                kj = self.distances[k][j]

                if ij > ik + kj:
                    self.distances[i][j] = ik + kj

    return self.distances

Para crear la primera versión de la matriz, el pseudocódigo utiliza dos ciclos para llenarlos con las aristas establecidas del grafo y los ceros ( tres ciclos si contamos rellenar con infinitos como en la primera linea) Este código crea la primera matriz con un solo ciclo y utliza una pequeña estrategía con la captura de excepciones, debido a que la librería de networkx no posee un método de verificación de aristas.

Los resultados para el grafo ejemplo tanto en Dijkstra como en Floyd-Warshall son exactamente iguales, podemos comprobarlos con el método para imprimir el diccionario resultado:

def print_distances(self):
    printt = ""
    for i in self.distances:
        printt = printt + str(i) + ": \t"
        for j in self.distances[i]:
            printt = printt + str(self.distances[i][j]) + "\t"
        printt = printt + "\n"
    print("\n------------------------------------")
    print(printt)
    return

Los resultados:

Figura 2. Resultados