LAB 10 Algoritmos sobre grafos
Contents
!wget --no-cache -O init.py -q https://raw.githubusercontent.com/rramosp/20192.L3/master/init.py
import init; init.init(force_download=False); init.get_weblink()
import init
from local.lib.rlxmoocapi import submit, session
import inspect
student = session.Session(init.endpoint).login( course_id=init.course_id,
session_id="UDEA",
lab_id="lab_10" )
LAB 10 Algoritmos sobre grafos¶
Ejercicio 1¶
Implementa al algoritmo de Dijsktra según la descripción más abajo. Las siguientes funciones te serán de ayuda para crear grafos aleatorios y visualizar grafos
import networkx as nx
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def create_random_graph(directed=False, w_size=200, n_cities=7, prob_connected=0.5):
import itertools
cities = (np.random.random((n_cities,2))*w_size).astype(int)
g = nx.DiGraph() if directed else nx.Graph()
for node_id, location in enumerate(cities):
g.add_node(node_id, pos=location)
for i,j in itertools.product(range(len(cities)), range(len(cities))):
if i<j and np.random.random()<prob_connected:
g.add_edge(i,j,weight=np.round(np.linalg.norm(cities[i]-cities[j]),2))
if directed and np.random.random()>.5:
g.add_edge(j,i,weight=np.round(np.linalg.norm(cities[i]-cities[j]),2))
return g
def draw_graph(g,node_size=500, font_color="white",
show_edge_labels=True, edge_units=" km",
x_units="km lon", y_units="km lat"):
positions = {i: g.nodes[i]["pos"] for i in g.nodes} if "pos" in [k for k in g.nodes.values()][0].keys() else None
nx.drawing.draw(g, with_labels=True, pos=positions,
node_alpha=.5, node_color="blue", width=2,
node_size=node_size, font_color=font_color)
if show_edge_labels and positions is not None:
nx.draw_networkx_edge_labels(g, pos=positions,
edge_labels={i:"%.1f%s"%(g.get_edge_data(*i)["weight"],edge_units) for i in g.edges});
plt.axis("on")
plt.xlabel(x_units)
plt.ylabel(y_units)
plt.grid()
plt.axis("equal");
INPUT:
nodes
: una lista con los nombres de los nodosedges
: un diccionario con:key una tupla con los nodos que participan en un edge
value el peso (distancia) de ese edge
start
: el nodo desde el que se quiere empezartarget
: el nodo al que se quiere llegar
observa que estamos con un grafo dirigido. Observa el ejemplo más abajo para ver cómo serían edges
y nodes
VARIABLES:
puedes usar dos diccionarios para llevar la cuenta del progreso del algoritmo
mincost
: un diccionario con un key por cada nodo cuyo valor asociado va a ser el coste (distancia) mínimo que vamos calculando desdestart
hasta el el nodo keyconfirmed
: un diccionario con un key por cada nodo cuyo valor asociado seráTrue
oFalse
que indique de qué nodos ya sabemos el coste mínimo de llegar desdestart
por ejemplo
valor de
confirmed
:{0: False, 1: True, 2: True, 3: True, 4: False, 5: False}
valor de
mincost
:{0: inf, 1: 0, 2: 146.24, 3: 168.91000000000003, 4: inf, 5: 255.36}
indica que:
ya sabemos que el coste menor de llegar al nodo 3 desde
start
es de 168.91ya sabemos que el coste menor de llegar al nodo 2 desde
start
es de 146.24el nodo
start
es el nodo 1, ya que el costo de llegar a él es de 0todavía no sabemos cómo llegar a los nodos 0 y 4 (o si esto es posible) ya que su valor es
inf
existe un camino de llegada al nodo 5 con coste 255.36 pero todavía no sabemos si habrá otro camino menos costoso.
PSEUDOCÓDIGO:
inicializar el diccionario mincost con el coste del edge (
start
, nodo) para cualquier nodo directamente conectado constart
y coninf
para cualquier otro nodoinicializar el diccionario confirmed con todos los nodos a
False
excepto el nodostart
que se establece aTrue
repetir tantas veces como número de nodos menos 1:
escoger nodo
w
tal quemincost[w]
sea el menor de los elementos demincost
cuyoconfirmed[w]
sea Falsoconfirmed[w]
\(\leftarrow\)True
para todo nodo
k
conconfirmed[k]=False
:mincost[k]:= min(mincost[k], mincost[k] + edges[(w,k)])
donde tienes que tener en cuenta que edges[(w,k)]
no estará definido para los casos en los que no hay un edge entre w
y k
(que es equivalente a que su coste fuera infinito)
OUTPUT:
el coste \(\in \mathbb{R}\) más pequeño de viajar desde start
hasta target
, este coste se encuentra en mincost[target]
Fíjate cómo verías en edges
y nodes
el siguiente grafo.
g = create_random_graph(n_cities=6, prob_connected=.7, directed=True)
g = nx.DiGraph()
g.add_node("S", pos=(0,5))
g.add_node("A", pos=(10,10))
g.add_node("B", pos=(10,0))
g.add_node("C", pos=(20,15))
g.add_node("D", pos=(20,5))
g.add_edge("S","A", weight=5)
g.add_edge("S","B", weight=10)
g.add_edge("A","B", weight=3)
g.add_edge("A","C", weight=7)
g.add_edge("A","D", weight=12)
g.add_edge("B","D", weight=1)
plt.figure(figsize=(3,3))
draw_graph(g, edge_units="")
plt.axis("off")
nodes = [i for i in g.nodes.keys()]
edges = {k:v["weight"] for k,v in g.edges.items()}
print("\n**** ESTA SERIA LA ENTRADA A TU FUNCION ****\n")
print("nodes", nodes)
print("edges", edges)
def dijkstra_shortest_path(nodes, edges, start, target):
import numpy as np
# inicializacion
mincost = #... TU CODIGO AQUI ...
confirmed = #... TU CODIGO AQUI ...
for _ in range(len(nodes)-1):
# .. TU CODIGO AQUI ...
# PASOS 3.A y 3.B del pseudocodigo
for k in nodes:
# ... TU CODIGO AQUI ...
# PASO 3.C del pseudocodigo
return mincost[target]
comprueba tu código. Observa cómo creamos un grafo aleatorio y seleccionamos también aleatoriamente nodos a los que medirle la distancia más corta. Comparando esa métrica con lo devuelto por networx
, debería de dar el mismo valor. Incluyendo los infititos.
g = create_random_graph(n_cities=10, prob_connected=.4, directed=True)
draw_graph(g, edge_units="")
plt.axis("off")
print ("type of g",type(g))
print ("Content of g: ", g)
print ("type of g.nodes", type(g.nodes))
print ("g.nodes= ", g.nodes)
print (g.nodes.keys())
print ("correct networkx tu_implementacion")
for _ in range(10):
source, target = 5, 2
source, target = np.random.permutation(g.nodes)[:2]
try:
nd = nx.dijkstra_path_length(g, source, target)
except nx.NetworkXNoPath:
nd = np.inf
nodes = [i for i in g.nodes.keys()]
edges = {k:v["weight"] for k,v in g.edges.items()}
md = dijkstra_shortest_path(nodes, edges, source, target)
print (nd==md, "%12.2f"%nd,"%12.2f"%md)
registra tu solución en línea¶
student.submit_task(globals(), task_id="task_01");
Ejercicio 2¶
Implementa el algoritmo de Dijkstra que además de calcular la longitud del camino más corto, obtenga los nodos por los que ese camino pasa.
ESTRATEGIA SUGERIDA
Observa que cada vez que modificamos mincost[k]
en el paso 3.C del pseudocódigo anterior, es que hemos encontrado un camino más corto hasta el nodo k
desde mincost[w]
(respecto a lo que conocemos en este punto del algoritmo). Es decir, que el camino más corto para llegar a k
, pasa por w
justo antes de llegar a k
.
Por tanto, si guardamos el valor de w
cada vez que actualizamos mincost
tendremos, para cada nodo, cual es el nodo inmediatamente anterior en el camino más corto.
Mantendremos entonces una variable wayto
que contenga un diccionario con un key por cada nodo cuyo valor asociado va a ser el nodo por el que se pasó inmediatamente antes de llegar al nodo key por el camino más corto.
En el paso 1 del pseudocódigo inicializamos wayto
de modo que wayto[i]=start
si hay un camino directo de start
a i
. En otro caso, establecemos wayto[i]=None
En el paso 3.C, cuando actualizemos mincost[k]
para algún nodo k
, actualizamos también wayto[k]=w
, en cualquier otro caso, no tocamos wayto
.
Con esto, tendremos al final del proceso por ejemplo un wayto
con el siguiente contenido, con start=6
y target=5
:
{0: 9, 1: 7, 2: 7, 3: 0, 4: 7, 5: 4, 6: None, 7: 6, 8: 3, 9: 6}
esto indica que:
el camino más corto para llegar al 5 pasa inmediatamente antes por el 4
el camino más corto para llegar al 4 pasa inmediatamente antes por el 7
el camino más corto para llegar al 7 pasa inmediatemente antes por el 6, que es nuestro
start
por tanto, el camino más corto para llegar desde el 6 al 5 es [6,7,4,5]
Como último paso tendrás que reconstruir el path
desde wayto
antes de acabar tu algoritmo y adjuntarlo al valor de retorno.
def dijkstra_shortest_path(nodes, edges, start, target):
import numpy as np
# inicializacion
mincost = #... TU CODIGO AQUI ...
confirmed = #... TU CODIGO AQUI ...
wayto = #... TU CODIGO AQUI ...
for _ in range(len(nodes)-1):
# .. TU CODIGO AQUI ...
# PASOS 3.A y 3.B del pseudocodigo
for k in nodes:
# ... TU CODIGO AQUI ...
# PASO 3.C del pseudocodigo .. acuerdate de actualizar wayto
path = # ... RECONSTRUYE EL PATH DESDE wayto ...
return mincost[target], path
prueba tu código. observa el grafo generado y prueba con distintos start
y target
#g = create_random_graph(n_cities=10, prob_connected=.4, directed=True)
nodes = [i for i in g.nodes.keys()]
edges = {k:v["weight"] for k,v in g.edges.items()}
draw_graph(g, edge_units="")
source, target = 6, 5
#source, target = 4,3
print ("STUDENT", dijkstra_shortest_path(nodes, edges, source, target))
print ("NETWORKX", (nx.dijkstra_path_length(g, source, target), nx.dijkstra_path(g,source,target)))
prueba tu código de manera más exhausitva. Obseva cómo generamos grafos y start
, target
aleatorios y se compara tu resultado con el de networkx
. Tu columna correct
ha de estar siempre a True
. Si no, observa el caso en el que no sea así y revisa tu implementación.
g = create_random_graph(n_cities=15, prob_connected=.2, directed=True)
plt.figure(figsize=(7,7))
draw_graph(g, edge_units="", show_edge_labels=False)
plt.axis("off")
print("src tgt correct networkx tu_implementacion")
for _ in range(20):
source, target = np.random.permutation(g.nodes)[:2]
try:
ndist = nx.dijkstra_path_length(g, source, target)
npath = nx.dijkstra_path(g, source, target)
except nx.NetworkXNoPath:
npath = []
ndist = np.inf
nodes = [i for i in g.nodes.keys()]
edges = {k:v["weight"] for k,v in list(g.edges.items())}
mdist,mpath = dijkstra_shortest_path(nodes, edges, source, target)
ok = (mdist==ndist) and (len(npath)==len(mpath)) and np.allclose(mpath, npath)
print("%3d"%source, "%3d"%target, ok, "%12.2f %20s"%(ndist,npath),"%12.2f %20s"%(mdist, mpath))
registra tu solución en linea¶
student.submit_task(globals(), task_id="task_02");
Ejercicio 3¶
Realiza la implementación del algoritmo de Floyd-Warshal que nos devuelve una matriz con el camino más corto entre todos los nodos. Si \(n\) es el número de nodos del grafo, tu solución ha de devolver un pandas.DataFrame
de \(n\times n\) en el que las columnas y los índices son los nodos.
Lee la descripción del algoritmo en Wikipedia para adquirir una intuición de cómo se implementa.
SUGERENCIA DE IMPLEMENTACIÓN¶
usa
collections.defaultdict
para crear un diccionario llamadomincost
con el mismo contenido deedges
pero con un valor por defectonp.inf
para cualquier par de nodos que que inicialmente no esté en edges.implementa el siguiente pseudocódigo:
mincost es un defaultdict inicializado vacío para cada edge entre nodos i,j: mincost[(i,j)] = edges[(i,j)] para cada nodo k: para cada nodo i: para cada nodo j: costo_ijk = mincost[(i,k)]+mincost[(k,j)] if costo_ijk < mincost[(i,j)]: mincost[(i,j)] = cost_ijk establece el valor 0 para mincost[(t,t)] para cada nodo t construye una matriz de n x n con los contenidos de mincost. construye un dataframe de pandas según indicado.
def floyd_warshall(nodes, edges):
import numpy as np
import pandas as pd
from collections import defaultdict
n_edges = len(edges)
mincost = defaultdict(lambda: np.inf)
# 1. construye el mincost inicial
# 2. realiza el triple bucle para actualizar el mincost de cada nodo
# 3. establece mincost[(t,t)]=0 para todos los nodos
# 4. constriuye un pandas dataframe segun indicado
result = pd.DataFrame ( # ... TU CODIGO AQUI .... )
return rr
prueba tu código con algún grafo sencillo
g = create_random_graph(n_cities=10, prob_connected=.4, directed=True)
nodes = [i for i in g.nodes.keys()]
edges = {k:v["weight"] for k,v in g.edges.items()}
draw_graph(g, edge_units="")
nodes = [i for i in g.nodes.keys()]
edges = {k:v["weight"] for k,v in g.edges.items()}
stfw = floyd_warshall(nodes, edges)
nxfw = np.array(nx.floyd_warshall_numpy(g))
print("TU SOLUCION")
print(stfw.values)
print("\nNETWORKX")
print(nxfw)
print("\nCOMPARATIVA:", np.allclose(stfw.values, nxfw))
prueba tu código de manera más exhaustiva
for _ in range(20):
g = create_random_graph(n_cities=np.random.randint(20)+5, prob_connected=.3, directed=True)
nxfw = np.array(nx.floyd_warshall_numpy(g))
nodes = [i for i in g.nodes.keys()]
edges = {k:v["weight"] for k,v in g.edges.items()}
stfw = floyd_warshall(nodes, edges)
ok = np.allclose(nxfw, stfw.values)
if not ok:
print("*** INCORRECT RESULT ON GRAPH ***")
print("graph")
print(nodes)
print(edges)
print("TU SOLUCION")
print(stfw)
print("\nNETWORKX")
print(nxfw)
print("\nCOMPARATIVA:", np.allclose(stfw, nxfw))
else:
print("ok", end=' ')
submit your answer (you must be connected to internet)¶
student.submit_task(globals(), task_id="task_03");