Este documento presenta un resumen del algoritmo de Johnson. En primer lugar, introduce el algoritmo de Johnson como una estrategia para encontrar los caminos más cortos en un grafo ponderado, incluso cuando contiene aristas con pesos negativos, siempre que no contenga ciclos negativos. Luego, explica los pasos del algoritmo de Johnson, que incluyen agregar un nuevo vértice, ejecutar el algoritmo de Bellman-Ford, reponderar los pesos de las aristas, y ejecutar el algoritmo de Dijkstra. Finalmente, presenta un ejemplo res
1. UNIVERSIDAD TECNOLÓGICA
DE LOS ANDES
MONOGRAFÍA
Algoritmo de Johnson
CURSO
Algoritmos y programación II
DOCENTE
Ing. Godofredo Poccori Umeres
ESTUDIANTES
Huaman Ataulluco Ricardo Manuel
Alvarez Cayo Marco Antonio
Quispe Mamani Jhon Anderson
Quispe Herrera Erick Jeandet
Huaman Jara William
Cusco 2023 - I
CARRERA PROFESIONAL DE INGENIERÍA DE
SISTEMAS E INFORMÁTICA FILIAL CUSCO
2. INTRODUCCIÓN
En el mundo de programación, los algoritmos para identificar son diversos
es una rama crucial destaca como una herramienta fundamental en el
campo de la teoría de grafos y la programación de algoritmos. Su
importancia radica en su capacidad para encontrar eficientemente los
caminos más cortos entre todos los pares de vértices en un grafo, incluso
cuando se enfrenta a la presencia de aristas con pesos negativos, siempre
que no existan ciclos negativos. Este algoritmo se posiciona como una
solución valiosa en problemas de optimización de rutas y programación de
tareas en sistemas heterogéneos.
El algoritmo de Johnson es una técnica avanzada para encontrar los
caminos más cortos entre todos los pares de vértices en un grafo, incluso
cuando este contiene aristas con pesos negativos, siempre y cuando no
tenga ciclos negativos si tendría eso no se podrá. Su eficiencia radica en
su enfoque de reponderación, transformando los pesos negativos a
valores no negativos para aprovechar el algoritmo de Dijkstra. La
complejidad asintótica del algoritmo es (O(V^2*logV+VE)), haciéndolo
especialmente eficaz para grafos dispersos. Utiliza subrutinas como los
algoritmos de Dijkstra y Bellman-Ford, y su implementación implica un
preprocesamiento que toma (O(VE)) tiempo. Proporcionando una
herramienta esencial para desarrolladores y científicos de datos que
buscan optimizar la eficiencia en redes y sistemas complejos. La
capacidad del algoritmo para abordar una variedad de situaciones lo
posiciona como una contribución significativa en el arsenal de
herramientas de la informática teórica y la optimización algorítmica.
Este trabajo monográfico se adentrará en las definiciones y conceptos
fundamentales del algoritmo de Johnson según diversos expertos en el
campo. A través de métodos y técnicas de investigación formativa, como
parafraseo, fichas textuales, resúmenes y comentarios, se presentará
información válida con un formato APA para asegurar la precisión y la
integridad del conocimiento compartido.
Para llegar al cabo la finalidad del desarrollo del trabajo monográfico
daremos a conocer información valida empezando con el capítulo I, que
informa definiciones, conceptos y pasos de cómo aplicar el método de
algoritmo de Johnson, según autores, en el capítulo II, se proporcionará un
ejemplo que se resolverá con el algoritmo de Johnson, posteriormente en
el capítulo III, se planteará el pseudocódigo, código y ejecución en Python
del ejemplo, finalmente plantearemos nuestras conclusiones.
3. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
CAPITULO I
DEFINICIÓN Y CONCEPTOS
Donald Bruce Johnson (16/12/1933 – 10/09/1994) fue un científico informático
estadounidense, investigador en el diseño y análisis de algoritmos y presidente fundador
del departamento de informática de Dartmouth. Universidad. Johnson recibió su
doctorado, desde Universidad de Cornell en 1973. Cuando se fundó el departamento de
informática de Dartmouth en 1994, se convirtió en su primer presidente. (Endre, 1983)
1.1. Concepto de algoritmos de Johnson
El algoritmo de Johnson es una estrategia sofisticada en teoría de grafos
que aborda el problema de encontrar los caminos más cortos entre todos
los pares de vértices en un grafo ponderado, incluso en presencia de
aristas con pesos negativos, siempre que no haya ciclos negativos. Según
(Salazar, 2019) indica con relación a la industria que “la regla de Johnson
es un algoritmo heurístico utilizado en la industria para resolver situaciones
de secuenciación de procesos que operan en dos o más órdenes y pasan
a través de dos máquinas o centros de trabajo. Su principal objetivo es
minimizar el tiempo de procesamiento total del grupo de trabajos.”
1.2. Modelos matemáticos en Algoritmos de Johnson
Según (Cormen, Leiserson, Rivest, & Stein, 2001) consideramos un grafo
dirigido y ponderado G = (V, E) con una función de peso w: E → {R} y una
función adicional h: V → {R}. Definimos una nueva función de peso wy: E
→ {R} según la relación wy(u, v) = w(u, v) + h(u) - h(v)
La prueba demuestra que para cualquier ruta p desde el vértice v0 hasta el
vértice vk, el camino es más corto con respecto a la función de peso w si y
solo si es más corto con respecto a wy. Es decir, w(p) = δ(v0, vk) si y solo
si wy(p) = δy(v0, vk), donde δ y δy representan las longitudes más cortas
utilizando las funciones de peso w y wy respectivamente. La prueba
establece que el grafo tiene un ciclo de peso negativo utilizando la función
de peso w si y solo si tiene un ciclo de peso negativo utilizando la función
de peso wy.
Podemos acertar que la relación entre las funciones de peso w y wy
mantiene la propiedad de caminos más cortos y la presencia de ciclos de
peso negativo en el grafo.
1.3. Lema de Johnson
Como menciona el mismo (Johnson, 1977), “El lema de Johnson es un
resultado teórico que permite transformar un grafo que no contiene ciclos
de peso negativo en un grafo equivalente que no contiene aristas de peso
negativo, lo que habilita la utilización del algoritmo de Dijkstra para
encontrar el camino más corto entre todos los pares de vértices en un grafo
dirigido disperso.”
4. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
CAPITULO II
APLICACICIONES DEL ALGORITMOS
2.1. Optimización de rutas y procesos
Los algoritmos de Johnson tienen aplicaciones significativas en la
optimización de rutas y procesos, tanto en el ámbito de la programación
de computadoras como en entornos industriales.
“... son relevantes para la optimización de rutas y procesos en
diversos contextos. En el ámbito de la programación de
computadoras, el algoritmo de Johnson se utiliza para encontrar el
camino más corto entre todos los pares de vértices en un grafo
dirigido disperso, lo que es fundamental en la optimización de rutas
en sistemas de información geográfica, redes de comunicación,
planificación de rutas de transporte, entre otros” (Reyes, 2016)
Fuente de la imagen: (Movertis, 2023)
2.2. Resolución de problemas de grafos con pesos negativos
En relación con grafos de pesos negativos el algoritmo de Johnson tiene
esta accesibilidad, como aclara (Fillottrani, 2017) que, una técnica utilizada
para resolver el problema de encontrar el camino más corto entre todos los
pares de vértices de un grafo dirigido disperso, permitiendo que las aristas
tengan pesos negativos, aunque no permite ciclos de peso negativo. Este
algoritmo funciona mediante una transformación del grafo inicial que
elimina todas las aristas de peso negativo, lo que permite utilizar el
algoritmo de Dijkstra en el grafo transformado.
Fuente de la imagen: (Olimpiada Informática Española, 2019)
5. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
El funcionamiento del Algoritmo de Johnson se puede entender fácilmente
con los siguientes pasos:
i. Un nuevo vértice
Se agrega un nuevo vértice “s” al gráfico. Luego, se conecta con
todos los vértices del gráfico. Los bordes están dirigidos desde el
nuevo vértice y los nuevos pesos de los bordes son cero. Así, con
las nuevas aristas no se modifica ningún camino más corto existente
entre los vértices originales del gráfico.
ii. Ejecución del algoritmo Bellman-Ford
A continuación, ejecutamos el algoritmo de Bellman-Ford para
después de este paso, obtenemos los caminos más cortos desde el
vértice “s” para cada vértice del gráfico. Se mantendrá las rutas más
cortas en una matriz, donde la distancia será la ruta más corta de “s”
hasta un vértice “v”.
iii. Re ponderar los bordes
Luego, modificamos los pesos de los bordes, donde se tendrá
nuevos pesos, pero no serán negativos para toda arista que esté
relacionado con el nuevo vértice, y esta modificación del peso no
cambia las rutas más cortas en el gráfico original. Es decir, los pesos
de las rutas en el gráfico realmente cambian, pero los caminos más
cortos antes y después de la modificación del peso siguen siendo
los mismos.
iv. Ejecutando el algoritmo de Dijkstra:
En el último paso, ejecutamos el algoritmo de Dijkstra para cada
vértice del gráfico con los bordes modificados. Después de este
paso, tendremos los caminos más cortos para cada par de vértices.
Los pesos de ruta más cortos se calculan utilizando los pesos de
borde modificados. Para calcular los pesos originales de los caminos
más cortos, necesitamos mantener en paralelo los pesos originales
de los caminos durante el algoritmo de Dijkstra. Alternativamente,
podemos convertir los pesos de las rutas más cortas después de
obtener las rutas más cortas.
Podemos acertar que, es una joya en el mundo de los grafos, el algoritmo
de Johnson es como una coreografía matemática, donde cada paso se
ejecuta con precisión para desentrañar los secretos de los caminos más
cortos en un mundo de conexiones complejas. Un enfoque brillante para
resolver problemas en grafos ponderados, transformando los desafíos en
soluciones eficientes.
6. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
CAPITULO III
ALGORITMO DE JOHNSON EN PYTHON
3.1. Ejemplo desarrollado
El ejemplo fue con los datos de (Cajic, 2020).
Dado que este gráfico contiene aristas negativas, el algoritmo de Dijkstra
aún no se le puede aplicar. Para empezar, los enlaces de borde deben
transformarse para que contengan números no negativos.
El algoritmo de Johnson comienza seleccionando un vértice fuente. El
problema de elegir un vértice existente es que es posible que no pueda
alcanzar todos los vértices del gráfico. Para garantizar que un único vértice
de origen pueda alcanzar todos los vértices del gráfico, se introduce un
nuevo vértice. El nuevo vértice, S, se introduce en todos los vértices del
gráfico. Aunque el peso desde la fuente hasta cada vértice no importa
siempre que todos sean consistentes, por convención se aplica un peso
de 0 a cada borde desde la fuente.
7. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
Para empezar, todos los bordes de salida se registran en una tabla en
orden alfabético.
El algoritmo de Johnson calcula los caminos más cortos desde el vértice S
hasta todos los vértices del gráfico. El algoritmo de Johnson utiliza un
algoritmo de ruta más corta de fuente única para obtener los nuevos
valores. Dado que hay pesos de arista negativos en el gráfico dirigido,
Bellman-Ford es el algoritmo que se utiliza para procesar este cálculo.
El vértice de origen es innecesario para el resto de este ejemplo, por lo que
eliminaremos la S y sus aristas.
8. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
Luego, el algoritmo de Johnson aplica la siguiente fórmula para los
cálculos de reponderación:
En inglés, la nueva longitud (C'e) es igual a la longitud original (Ce) más el
peso de su cola (p u) menos el peso de su cabeza (p v). El algoritmo de
Johnson hace esto para cada una de las aristas.
El gráfico se actualiza con los nuevos pesos.
9. 3.2. Pseudocodigo
función minimaDistancia(dist, visitados):
(minima, verticeMinimo) = (MAX_INT, 0)
para cada vertice en rango de longitud(dist):
si minima > dist[vertice] y no visitados[vertice]:
(minima, verticeMinimo) = (dist[vertice], vertice)
devolver verticeMinimo
función Dijkstra(grafo, grafoModificado, origen):
num_vertices = longitud(grafo)
conjuntoSPT = inicializar_conjuntoSPT()
distancias = inicializar_distancias(num_vertices)
distancias[origen] = 0
para cada contador en rango de num_vertices:
verticeActual = minimaDistancia(distancias, conjuntoSPT)
conjuntoSPT[verticeActual] = verdadero
para cada vertice en rango de num_vertices:
si (no conjuntoSPT[vertice]) y
(distancias[vertice] > (distancias[verticeActual] +
grafoModificado[verticeActual][vertice])) y
(grafo[verticeActual][vertice] != 0):
distancias[vertice] = (distancias[verticeActual] +
grafoModificado[verticeActual][vertice])
para cada vertice en rango de num_vertices:
imprimir 'Vértice ' + caracter(65 + vertice) + ': ' +
convertir_a_cadena(distancias[vertice])
función BellmanFord(aristas, grafo, num_vertices):
distancias = inicializar_distancias(num_vertices + 1)
distancias[num_vertices] = 0
para cada i en rango de num_vertices:
aristas.agregar([num_vertices, i, 0])
para cada i en rango de num_vertices:
para cada (origen, destino, peso) en aristas:
si (distancias[origen] != MAX_INT) y
(distancias[origen] + peso < distancias[destino]):
distancias[destino] = distancias[origen] + peso
devolver distancias[0:num_vertices]
10. función algoritmoJohnson(grafo):
aristas = []
para cada i en rango de longitud(grafo):
para cada j en rango de longitud(grafo[i]):
si grafo[i][j] != 0:
aristas.agregar([i, j, grafo[i][j]])
modificarPesos = BellmanFord(aristas, grafo, longitud(grafo))
grafoModificado = inicializar_grafo_modificado(longitud(grafo))
para cada i en rango de longitud(grafo):
para cada j en rango de longitud(grafo[i]):
si grafo[i][j] != 0:
grafoModificado[i][j] = (grafo[i][j] + modificarPesos[i] - modificarPesos[j])
imprimir 'Grafo Modificado: ' + convertir_a_cadena(grafoModificado)
para cada origen en rango de longitud(grafo):
imprimir 'nDistancia más corta con el vértice ' + caracter(65 + origen) + ' como
origen:n'
Dijkstra(grafo, grafoModificado, origen)
grafo = [
[0, 0, 0, 11, 0, 0, 0],
[-7, 0, 0, -5, 0, 3, 0],
[17, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 12, 9],
[0, 0, 5, 0, 0, 0, 0],
[0, 0, -5, 0, 4, 0, 0],
[0, 0, 0, 0, -3, 0, 0]
]
algoritmoJohnson(grafo)
11. 3.3. Codificación y ejecución del programa (Python)
from collections import defaultdict
MAX_INT = float('Inf')
def minimaDistancia(dist, visitados):
(minima, verticeMinimo) = (MAX_INT, 0)
for vertice in range(len(dist)):
if minima > dist[vertice] and visitados[vertice] == False:
(minima, verticeMinimo) = (dist[vertice], vertice)
return verticeMinimo
def Dijkstra(grafo, grafoModificado, origen):
num_vertices = len(grafo)
conjuntoSPT = defaultdict(lambda: False)
distancias = [MAX_INT] * num_vertices
distancias[origen] = 0
for contador in range(num_vertices):
verticeActual = minimaDistancia(distancias, conjuntoSPT)
conjuntoSPT[verticeActual] = True
for vertice in range(num_vertices):
if ((conjuntoSPT[vertice] == False) and
(distancias[vertice] > (distancias[verticeActual] +
grafoModificado[verticeActual][vertice])) and
(grafo[verticeActual][vertice] != 0)):
distancias[vertice] = (distancias[verticeActual] +
grafoModificado[verticeActual][vertice]);
for vertice in range(num_vertices):
print ('Vértice ' + chr(65 + vertice) + ': ' + str(distancias[vertice]))
def BellmanFord(aristas, grafo, num_vertices):
distancias = [MAX_INT] * (num_vertices + 1)
distancias[num_vertices] = 0
for i in range(num_vertices):
aristas.append([num_vertices, i, 0])
for i in range(num_vertices):
for (origen, destino, peso) in aristas:
if((distancias[origen] != MAX_INT) and
12. (distancias[origen] + peso < distancias[destino])):
distancias[destino] = distancias[origen] + peso
return distancias[0:num_vertices]
def algoritmoJohnson(grafo):
aristas = []
for i in range(len(grafo)):
for j in range(len(grafo[i])):
if grafo[i][j] != 0:
aristas.append([i, j, grafo[i][j]])
modificarPesos = BellmanFord(aristas, grafo, len(grafo))
grafoModificado = [[0 for x in range(len(grafo))] for y in
range(len(grafo))]
for i in range(len(grafo)):
for j in range(len(grafo[i])):
if grafo[i][j] != 0:
grafoModificado[i][j] = (grafo[i][j] +
modificarPesos[i] - modificarPesos[j]);
print ('Grafo Modificado: ' + str(grafoModificado))
for origen in range(len(grafo)):
print ('nDistancia más corta con el vértice ' +
chr(65 + origen) + ' como origen:n')
Dijkstra(grafo, grafoModificado, origen)
grafo = [
[0, 0, 0, 11, 0, 0, 0],
[-7, 0, 0, -5, 0, 3, 0],
[17, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 12, 9],
[0, 0, 5, 0, 0, 0, 0],
[0, 0, -5, 0, 4, 0, 0],
[0, 0, 0, 0, -3, 0, 0]
]
algoritmoJohnson(grafo)
14. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
CONCLUSIONES
En el mundo de la programación y la teoría de grafos, los algoritmos
desempeñan un papel crucial, siendo herramientas fundamentales para
resolver diversos problemas.
El Algoritmo de Johnson destaca por su capacidad para encontrar
eficientemente los caminos más cortos entre todos los pares de vértices en
un grafo, incluso en presencia de aristas con pesos negativos, siempre que no
haya ciclos negativos.
El Algoritmo de Johnson se presenta como una solución valiosa en
problemas de optimización de rutas y programación de tareas en sistemas
heterogéneos.
Su eficiencia radica en su enfoque de reponderación, transformando los
pesos negativos a valores no negativos para aprovechar el algoritmo de
Dijkstra.
La complejidad asintótica del algoritmo es (O(V^2*logV+VE)), lo que lo
hace especialmente eficaz para grafos dispersos.
Utiliza subrutinas como los algoritmos de Dijkstra y Bellman-Ford, y su
implementación implica un preprocesamiento que toma (O(VE)) tiempo.
El Algoritmo de Johnson encuentra aplicaciones en la optimización de rutas
y procesos, tanto en programación informática como en entornos
industriales. Su utilidad se extiende a la resolución de problemas de grafos
con pesos negativos, permitiendo trabajar con aristas de este tipo sin ciclos
negativos. Se destaca el Lema de Johnson como un resultado teórico que
habilita la transformación de un grafo sin ciclos de peso negativo en un grafo
equivalente sin aristas de peso negativo, permitiendo el uso del algoritmo de
Dijkstra.
Se proporciona una explicación detallada de los pasos del Algoritmo de
Johnson, desde la introducción de un nuevo vértice hasta la ejecución del
algoritmo de Dijkstra con bordes modificados.
El Algoritmo de Johnson se destaca como una "joya en el mundo de los
grafos", con su capacidad para resolver eficientemente problemas en grafos
ponderados y transformar desafíos en soluciones eficientes.
15. UNIVERSIDAD TECNOLÓGICA DE LOS ANDES
Escuela Profesional de Ingeniería de Sistemas e Informática
Bibliografía
Cajic, D. (2020). El algoritmo de Johnson explicado visualmente. Estados
Unidos: Medium.
Cormen, T., Leiserson, C., Rivest, R., & Stein, C. (2001). Introduction to
Algorithms. London, England: McGraw-Hill.
Endre, R. (1983). Data Structures and Network Algorithms. Estados Unidos:
CBMS-NSF Regional Conference Series in Applied Mathematics.
Fillottrani, P. (2017). Algoritmos y Complejidad. Departamento de Ciencias e
Ingeniería de la Computación: Blanca, Argentina.
Johnson, D. (1977). Efficient algorithms for shortest paths in sparse
networks. Township, Pensilvania: AMC.
Movertis. (2023). ¿Sabes cómo optimizar tus rutas de transporte? España:
Redacción Movertis.
Olimpiada Informática Española. (2019). Camino más corto entre nodos.
España: OI.
Reyes, N. (2016). Modelo de optimización de programación de rutas para
una empresa logística peruana usando herramientas FSMVRPTW. Lima,
Perú: UNMSM.
Salazar, B. (2019). Regla de Johnson. Colombia: Ingenieria industrial.