4. #IC
● Grafos
● Representaciones
● Matrices de Adyacencia
● Listas de Adyacencia
● Objetos
● Camino más corto y Distancia mínima
● Algoritmo de Dijkstra
● Algoritmo de Floyd
● Árbol recubridor mínimo
● Algoritmo de Kruskal
● Algoritmo de Prim
● Ejercicios
AGENDA
5. #IC
GRAFOS
La ciudad de Königsberg, en Prusia, tenía 7 puentes.
¿Existe alguna forma de atravesar la ciudad pasando
por cada puente una sola vez?
6. #IC
GRAFOS
Un grafo es una representación
de relaciones entre pares de
objetos.
A cada uno de esos objetos se
los denomina nodo o vértice.
Las conexiones entre nodos se
denominan aristas o arcos.
7. #IC
GRAFOS
Las aristas de un grafo pueden
ser dirigidas si tiene un origen
y un destino definido. Si no,
las aristas se denominan no
dirigidas.
El grafo será clasificado como
dirigido o no dirigido,
dependiendo de las
características de sus aristas.
8. #IC
GRAFOS
Además de una dirección, las
aristas pueden tener asociados
valores llamados pesos.
Dos nodos pueden estar
conectados por más de una
arista, cada una con su peso
determinado.
Los pesos de que conectan dos
nodos en sentidos opuestos
también pueden diferir
9. #IC
CAMINOS
• Un camino es la secuencia de nodos y aristas que
unen dos nodos particulares.
• En un grafo dirigido, si existe un camino que una el
nodo u con el nodo v, se dice que “el nodo v es
accesible desde el nodo u”.
• Por más que v sea accesible desde u, no
necesariamente se da la relación inversa.
• En un grafo no dirigido, se dice que los nodos están
conectados.
10. #IC
REPRESENTACIONES
• Existen 3 formas de representación :
• Matrices de adyacencia
• Listas de adyacencia
• Listas anidadas
• Diccionarios
• Objetos
11. #IC
MATRICES DE ADYACENCIA
• A cada nodo de un grafo se le asigna
un número desde 0 hasta V, siendo V el
número de nodos en el grafo.
• Se construye una matriz de tamaño V *
V, donde:
Aij
1si iy jestán conectados
0 en otro caso
Aij
el peso de la uniónentre iy j
0 en otro caso
Grafos no pesados Grafos pesados
16. #IC
LISTAS DE ADYACENCIA
• Los grafos también pueden representarse
como múltiples listas, una por cada nodo.
• Cada lista posee todos los nodos conectados
al nodo correspondiente.
• En Python, dado que no existe nativamente el
tipo de dato “matriz”, las implementaciones
de matrices y listas de adyacencias son muy
similares.
17. #IC
LISTAS DE ADYACENCIA
sfo = []
bos = ['JFK', 'MIA']
jfk = ['SFO', 'DFW', 'MIA']
ord = ['DFW']
dfw = ['ORD', 'LAX']
lax = ['ORD']
mia = ['DFW', 'LAX']
lista_ady = [sfo, bos, jfk, ord, dfw, lax, mia]
for i in lista_ady[3]:
if i =='DFW':
print(True)
18. #IC
LISTAS DE ADYACENCIA
• Las listas de
adyacencia también
pueden
implementarse
usando
diccionarios.
• Esta estrategia hace
más simple acceder
a los distintos
nodos.
dicc_ady = {'SFO': {},
'BOS': {'JFK': 75, 'MIA': 205},
'JFK': {'SFO': 365, 'DFW': 225, 'MIA': 175},
'ORD': {'DFW': 145},
'DFW': {'ORD': 135, 'LAX': 200},
'LAX': {'ORD': 230},
'MIA': {'DFW': 185, 'LAX': 330}
}
print('ORD-DFW: ', dicc_ady['ORD']['DFW'])
19. #IC
OBJETOS
• Otra forma de implementar grafos es definiendo
clases específicas para nodos y aristas.
• La clase nodo contiene el elemento del nodo.
• Dependiendo del tipo de grafo, la clase arista va
a contener distintos elementos:
• Origen
• Destino
• Peso
• …
20. #IC
RECORRIDO DE GRAFOS
• Similar a los árboles, los recorridos pueden ser por
anchura o por profundidad.
• En el recorrido por anchura, se define el nodo de
comienzo y se añaden todos los nodos unidos a él a
una cola.
• Los siguientes nodos se recorren siguiendo el orden en
la cola.
• Se debe llevar la cuenta de qué nodos ya fueron
visitados, para no entrar en bucles infinitos.
21. #IC
RECORRIDO POR ANCHURA
Entrada: grafo, nodo inicio.
agregar inicio a la cola de nodo por visitar
mientras que haya nodos en la cola:
quitar nodo de la cola
agregar nodo a la lista de visitados
por cada nodo conectado a nodo visitando:
si no fue visitado, agregar a la cola de nodos por
visitar
22. #IC
RECORRIDO POR ANCHURA
def anchura(grafo, inicio):
cola = [inicio]
visitados = []
while len(cola) != 0:
visitando = cola.pop(0)
if visitando not in visitados:
visitados.append(visitando)
for n in grafo[visitando]:
cola.append(n)
return visitados
23. #IC
RECORRIDO POR PROFUNDIDAD
• En el recorrido por profundidad, se
define el nodo de comienzo y,
recursivamente, se visitan todos los
nodos conectados a ese inicio.
• Nuevamente, se debe llevar la cuenta de
qué nodos ya fueron visitados, para no
entrar en bucles infinitos.
24. #IC
RECORRIDO POR PROFUNDIDAD
Entrada: grafo, nodo inicio, lista visitados.
agregar inicio a visitados
por cada nodo conectado a nodo visitando:
si nodo no está en visitados:
visitar(grafo, nodo, visitados)
devolver visitados
25. #IC
RECORRIDO POR PROFUNDIDAD
def profundidad(grafo, inicio, visitados = []):
visitados.append(inicio)
for n in grafo[inicio]:
if n not in visitados:
visitados = profundidad(grafo, n, visitados)
return visitados
26. #IC
CAMINO MÁS CORTO
• Un camino es la
secuencia de
nodos y aristas que
unen dos nodos
particulares.
• El peso de un
camino es la suma
de todos los pesos
que lo componen.
Caminos entre “JFK” y “LAX”:
● 'JFK', 'DFW', 'LAX': 425
● 'JFK', 'MIA', 'DFW', 'LAX': 560
● 'JFK', 'MIA', 'LAX': 505
27. #IC
CAMINO MÁS CORTO
• Para calcular todas las distancias de un nodo
a los otros, se podría seguir una estrategia de
fuerza bruta:
• Elegir un nodo inicio.
• Para cada nodo n dentro del grafo,
calcular todas las distancias desde inicio
hasta n.
• Elegir la menor distancia dentro de todos
los resultados.
28. #IC
CAMINO MÁS CORTO
• Para Una forma de optimizar el código sería
aplicando la estrategia de la poda.
• Elegir un nodo inicio.
• Para cada nodo n dentro del grafo,
calcular cada distancia desde inicio hasta
n.
• Si la distancia intermedia es mayor a la
distancia menor conocida, no continuar.
29. #IC
ALGORITMO DE DIJKSTRA
• El algoritmo de Dijkstra permite calcular el menor
camino desde un inicio a todos los nodos de un grafo.
• Es un algoritmo ávido que consiste en calcular las
distancias desde un nodo a los adyacentes, y continuar
el cálculo desde el nodo de menor distancia.
• El algoritmo comienza con el nodo de inicio y termina
cuando todos los nodos accesibles fueron visitados.
• Para cada nodo n se almacena la distancia mínima
desde el inicio y el nodo anterior que conduce a n.
30. #IC
ALGORITMO DE DIJKSTRA
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO Inf -
MIA Inf -
ORD Inf -
DFW Inf -
LAX Inf -
Actual = “JFK”
Visitados = [“JFK”]
31. #IC
ALGORITMO DE DIJKSTRA
Actual = “JFK”
Visitados = [“JFK”]
¿D > D
mia jfk-mia
?
¿Inf > 175 ?
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO Inf -
MIA Inf -
ORD Inf -
DFW Inf -
LAX Inf -
32. #IC
ALGORITMO DE DIJKSTRA
Actual = “JFK”
Visitados = [“JFK”]
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD Inf -
DFW 225 JFK
LAX Inf -
33. #IC
ALGORITMO DE DIJKSTRA
Actual = “MIA”
Visitados = [“JFK”,”MIA”]
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD Inf -
DFW 225 JFK
LAX Inf -
34. #IC
ALGORITMO DE DIJKSTRA
Actual = “MIA”
Visitados = [“JFK”,”MIA”]
¿D > D + D
dfw jfk-mia mia-dfw
?
¿225 > 175 + 185 ?
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD Inf -
DFW 225 JFK
LAX 475 MIA
35. #IC
ALGORITMO DE DIJKSTRA
Actual = “DFW”
Visitados = [“JFK”,”MIA”]
¿D > D + D
lax jfk-dfw dfw-lax
?
¿475 > 225 + 200 ?
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD 132 DFW
DFW 225 JFK
LAX 475 MIA
36. #IC
ALGORITMO DE DIJKSTRA
Actual = “DFW”
Visitados = [“JFK”,”MIA”]
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD 357 DFW
DFW 225 JFK
LAX 445 DFW
37. #IC
ALGORITMO DE DIJKSTRA
Actual = “ORD”
Visitados = [“JFK”,”MIA”,”DFW”]
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD 357 DFW
DFW 225 JFK
LAX 445 DFW
38. #IC
ALGORITMO DE DIJKSTRA
Actual = “SFO”
Visitados = [“JFK”,”MIA”,”DFW”,”ORD”]
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD 357 DFW
DFW 225 JFK
LAX 445 DFW
39. #IC
ALGORITMO DE DIJKSTRA
Actual = “LAX”
Visitados = [“JFK”,”MIA”,”DFW”,”ORD”,”SFO”]
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD 357 DFW
DFW 225 JFK
LAX 445 DFW
40. #IC
ALGORITMO DE DIJKSTRA
Entrada: grafo, nodo inicio
Inicializar tabla de resultados con distancias y anteriores:
Si n == inicio:
distancia = 0
anterior = None
Si no:
distancia = Inf
anterior = None
Inicializar nodo actual = inicio
Inicializar lista visitados como lista vacía
41. #IC
ALGORITMO DE DIJKSTRA
Mientras que actual no sea None:
Agregar actual a visitados
Por cada nodo n adyacente a actual:
Calcular distancia desde n a inicio pasando por actual
(almacenado en tabla de resultados)
Si la distancia nueva es menor a la conocida:
actualizar valores
Elegir siguiente nodo
Si existe:
menor distancia dentro de resultados y que no haya sido visitado
Sino
retornar None
42. #IC
ALGORITMO DE DIJKSTRA
def distancia_menor(resultados, visitados):
dist = float('inf')
nodo = None
for r in resultados:
if resultados[r]["Distancia"] < dist and r not in visitados:
dist = resultados[r]["Distancia"]
nodo = r
return nodo
43. #IC
ALGORITMO DE DIJKSTRA
def dijkstra(grafo, inicio):
actual = inicio
visitados = []
resultados = {}
for g in grafo:
if g == inicio:
resultados[g] = {"Distancia": 0, "Anterior": None}
else:
resultados[g] = {"Distancia": float("inf"), "Anterior": None}
while actual != None:
visitados.append(actual)
for g in grafo[actual]:
if resultados[g]["Distancia"] > resultados[actual]["Distancia"] + grafo[actual][g]:
resultados[g]["Distancia"] = resultados[actual]["Distancia"] + grafo[actual][g]
resultados[g]["Anterior"] = actual
actual = distancia_menor(resultados, visitados)
return resultados
44. #IC
RECONSTRUYENDO EL CAMINO MÍNIMO
Para encontrar el camino
mínimo a un nodo, se debe
recorrer la lista de nodos
anteriores hasta llegar al inicio.
Por ejemplo, el camino
hasta “ORD” se calcularía
cómo:
“ORD”
“DFW” (anterior a “ORD”)
“JFK” (anterior a “DFW”)
Nodo Distancia Anterior
JFK 0 None
BOS Inf -
SFO 365 JFK
MIA 175 JFK
ORD
357
DFW
DFW 225 JFK
LAX 445 DFW
45. #IC
RECONSTRUYENDO EL CAMINO MÍNIMO
def recontruir_camino(resultados, inicio, fin):
camino = []
actual = fin
while actual != inicio:
camino = [actual] + camino
actual = resultados[actual]["Anterior"]
camino = [inicio] + camino
return camino
46. #IC
DISTANCIA MÍNIMA
• El algoritmo de Diijkstra permite encontrar
las distancias de un nodo particular al resto
de los nodos accesibles.
• Si se quisieran conocer todas las distancias,
se debería ejecutar el código n veces.
• Una alternativa para disminuir el número de
cálculos es el algoritmo de Floyd.
47. #IC
ALGORITMO DE FLOYD
• El algoritmo de Floyd utiliza una matriz de adyacencia para
calcular las distancias entre todos los pares de nodos.
• Este algoritmo tiene muchas coincidencias con el algoritmo
de Dijkstra.
• Para facilitar los cálculos, al comienzo las distancias entre
nodos no adyacentes se establece como infinita.
• Luego se recorre la matriz completa y se comprueba si la
distancia conocida entre dos nodos es mayor a la
distancia entre esos nodos, pasando por un nodo
adicional:
¿ Dij > Dik + Dkj ?
48. #IC
ALGORITMO DE FLOYD
SFO BOS JFK ORD DFW LAX MIA
SFO 0 Inf Inf Inf Inf Inf Inf
BOS Inf 0 75 Inf Inf Inf 205
JFK 365 Inf 0 Inf 225 Inf 175
ORD Inf Inf Inf 0 145 Inf Inf
DFW Inf Inf Inf 132 0 200 Inf
LAX Inf Inf Inf 230 Inf 0 Inf
MIA Inf Inf Inf Inf 185 300 0
i
j
Calculando la distancia mínima entre BOS y SFO:
i = 1;j = 0; D10 = Inf
k = 0: D10 = Inf; D00 = 0 => No cambiar
k = 1: D11 = 0; D10 = Inf => No cambiar
k = 2: D12 = 75; D20 = 365 => Cambiar
Dij = 440
k = 3: D13 = Inf; D30 = Inf => No cambiar
k = 4: D14 = Inf; D40 = Inf => No cambiar
k = 5: D15 = Inf; D50 = Inf => No cambiar
k = 6: D16 = 205; D60 = Inf => No cambiar
49. #IC
ALGORITMO DE FLOYD
SFO BOS JFK ORD DFW LAX MIA
SFO 0 Inf Inf Inf Inf Inf Inf
BOS Inf 0 75 Inf Inf Inf 205
JFK 365 Inf 0 Inf 225 Inf 175
ORD Inf Inf Inf 0 145 Inf Inf
DFW Inf Inf Inf 132 0 200 Inf
LAX Inf Inf Inf 230 Inf 0 Inf
MIA Inf Inf Inf Inf 185 300 0
i
j Calculando la distancia mínima entre JFK y LAX:
i = 2; j = 5; D25 = Inf
k = 0: D20 = 365; D05 = Inf => No cambiar
k = 1: D21 = Inf; D15 = Inf => No cambiar
k = 2: D22 = 0; D25 = Inf => No cambiar
k = 3: D23 = Inf; D35 = Inf => No cambiar
k = 4: D24 = 225; D45 = 200 => Cambiar
Dij = 425
k = 5: D25 = Inf; D55 = Inf => No cambiar
k = 6: D26 = 175; D65 = 300 => No cambiar
50. #IC
ALGORITMO DE FLOYD
Entrada: matriz de adyacencia con 0,
distancias e infinitos
Para cada nodo intermedio (k):
Para cada nodo de inicio (i):
Para cada nodo de fin (j):
Calcular Dij y la suma entre Dik y Dkj
Si corresponde, actualizar valores
51. #IC
ALGORITMO DE FLOYD
def preparar_matriz(matriz):
for i in range (0, len(matriz)):
for j in range (0, len(matriz)):
if i != j and matriz[i][j] == 0:
matriz[i][j] = float("inf")
return matriz
def floyd(matriz):
for k in range (0, len(matriz)):
for i in range (0, len(matriz)):
for j in range (0, len(matriz)):
if matriz[i][j] > matriz[i][k] + matriz[k][j]:
matriz[i][j] = matriz[i][k] + matriz[k][j]
return matriz
52. #IC
ÁRBOL RECUBRIDOR MÍNIMO
Se desean conectar en red todas las
computadoras de un aula.
¿Cómo podría calcular el orden en que
deberían conectarse para utilizar la
menor cantidad de cable posible?
53. #IC
ÁRBOL RECUBRIDOR MÍNIMO
• Un árbol recubridor contiene
todos los nodos de un grafo
no dirigido y conectado.
• Si el grafo posee n nodos, el
árbol recubridor posee n-1
aristas.
• El árbol recubridor mínimo es
el árbol con menor peso total.
54. #IC
ALGORITMO DE KRUSKAL
• Es un algoritmo ávido.
• La heurística en la que se basa es seleccionar el arco de
menor peso.
• A medida que se seleccionan nodos, se van formando
distintos subárboles, que serán combinados en un árbol
final.
• Si la arista de menor peso conecta nodos ya presentes en un
mismo árbol, se descarta.
• El algoritmo termina cuando se forma un solo árbol de n-1
aristas.
64. #IC
ALGORITMO DE KRUSKAL
Entrada: grafo, no dirigido, ponderado
armar tabla de pesos, ordenada
mientras que el número de aristas en el árbol != n-1:
elegir arista de menor peso, quitar de la lista
si los nodos no están en ningún subárbol:
crear subárbol
si están en distintos subárboles:
combinar subárboles
si están en el mismo árbol:
descartar arista
65. #IC
ALGORITMO DE PRIM
• También es un algoritmo ávido.
• Se comienza eligiendo la arista de menor peso, y se
continúa con la arista de menor peso conectada a los
nodos ya visitados.
• Si la arista de menor peso lleva a un nodo ya visitado, se
descarta.
• El algoritmo termina cuando todos los nodos fueron
visitados
75. #IC
ALGORITMO DE PRIM
Entrada: grafo, no dirigido, ponderado
seleccionar arista de menor peso
añadir arista la árbol, nodos a la lista de visitados
mientras queden nodos por visitar:
seleccionar arista de menor peso conectada a nodos
visitados
si el nodo de destino no fue visitado:
añadir arista al árbol, nodo de destino a lista de visitados
76. #IC
ALGORITMO DE KRUSKAL
from operator import itemgetter
def kruskal (grafo):
arm = {}
arboles = []
aristas = 0
lista_ordenada = ordenar_lista(grafo)
while aristas != len(grafo)-1:
inicio, fin, peso = lista_ordenada.pop(0)
agregar = anadir_nodos(arboles, inicio, fin)
if agregar == True:
print(inicio, fin, peso)
aristas += 1
if inicio not in arm:
arm[inicio] = {}
arm[inicio][fin] = peso
return arm
def ordenar_lista (grafo):
resultado = []
for i in grafo:
for f in grafo[i]:
resultado.append((i, f, grafo[i][f]))
resultado.sort(key=itemgetter(2))
return resultado
77. #IC
ALGORITMO DE KRUSKAL
def anadir_nodos(arboles, inicio, fin):
indice_inicio = indice_fin = None
for i in range(0, len(arboles)):
if inicio in arboles[i]:
indice_inicio = i
if fin in arboles[i]:
indice_fin = i
if indice_inicio == None and indice_fin != None:
arboles[indice_fin].append(inicio)
return True
elif indice_inicio != None and indice_fin == None:
arboles[indice_inicio].append(fin)
return True
elif indice_inicio == None and indice_fin == None:
arboles.append([inicio, fin])
return True
elif indice_inicio != None and indice_fin != None and indice_inicio != indice_fin:
arboles.append(arboles[indice_inicio]+arboles[indice_fin])
quitar_inicio = arboles[indice_inicio]
quitar_fin = arboles[indice_fin]
arboles.remove(quitar_inicio)
arboles.remove(quitar_fin)
return True
78. #IC
ALGORITMO DE PRIM
def prim (grafo):
arm = {} visitados = []
while len(visitados) < len(grafo):
inicio, fin, peso = encontrar_arista_menor(grafo, visitados)
print(inicio, fin, peso)
if inicio not in visitados:
visitados.append(inicio)
if fin not in visitados:
visitados.append(fin)
if inicio not in arm:
arm[inicio] = {}
arm[inicio][fin] = peso
return arm
79. #IC
ALGORITMO DE PRIM
def encontrar_arista_menor (grafo, visitados):
nodo_inicio = ""
nodo_fin = ""
menor_peso = float("inf")
if len(visitados) == 0:
for inicio in grafo:
for fin in grafo[inicio]:
if grafo[inicio][fin] < menor_peso:
menor_peso = grafo[inicio][fin]
nodo_inicio = inicio
nodo_fin = fin
else:
for inicio in visitados:
for fin in grafo[inicio]:
if grafo[inicio][fin] < menor_peso and fin not in visitados:
menor_peso = grafo[inicio][fin]
nodo_inicio = inicio
nodo_fin = fin
return nodo_inicio, nodo_fin, menor_peso
82. #IC
EJERCICIO 1
A partir del siguiente grafo, calcule los árboles recubridores
mínimos usando los algoritmos de Kruskal y de Prim.
A
B
H G
C D
F
I E
4
8 7
1
1 2
6
1
8
2
4
14
9
10
7