Este proyecto implementa un grafo dirigido ponderado con múltiples funcionalidades, incluyendo la detección y eliminación de ciclos negativos y la búsqueda de caminos más cortos teniendo en cuenta múltiples objetivos (costo y tiempo de entrega).
-
Grafo dirigido ponderado: Se utiliza un grafo dirigido representado mediante una lista de adyacencia con nodos que tienen costos y tiempos asociados. Esto permite almacenar eficientemente las conexiones y atributos del grafo.
-
Estructuras auxiliares: Se definen estructuras como
Edge
para representar aristas yLabel
para gestionar las etiquetas en la estrategia multiobjetivo. -
Listas de adyacencia: Almacenar el grafo en listas de adyacencia en lugar de matrices de adyacencia reduce significativamente el consumo de memoria, especialmente en grafos dispersos.
-
Eliminación de ciclos negativos: Se utiliza una modificación del algoritmo Bellman-Ford para detectar y eliminar ciclos negativos. Esta técnica asegura que los caminos calculados sean viables en términos de costo y tiempo.
-
Uso de
deque
: Para mejorar el rendimiento del algoritmo, utilicédeque
(doble cola) en conjunto con las estrategias SLF (Smallest Label First) y LLL (Large Label Last). SLF prioriza el procesamiento de los nodos con la menor distancia actual, mientras que LLL retrasa la expansión de los nodos con etiquetas grandes. Esto ayuda a que las actualizaciones de distancia sean más eficientes. -
Optimización de caminos múltiples: El algoritmo emplea un enfoque basado en etiquetas y una cola de prioridad con ordenamiento parcial para encontrar caminos eficientes bajo dos criterios (costo y tiempo). Esto permite la comparación simultánea y eliminación de soluciones sub-óptimas.
Hice uso de una modificación del algoritmo Bellman-Ford llamada SPFA (Single-Source Shortest Path First) ya que las instrucciones era poder detectar ciclos negativos y eliminarlos, junto con esto debía trabajar con grafos muy grandes, por lo cual entre muchos algoritmos como Dijkstra, Johnson, o D'Esopo-Pape, etcétera, decidí utilizar el algoritmo SPFA con mejoras para conseguir el mejor rendimiento sin perder la eficacia del algoritmo.
El método removeNegativeCycles
implementa una variante del algoritmo Bellman-Ford, los pasos son los siguientes:
- Inicializar distancias y predecesores.
- Relajar aristas múltiples veces y detectar ciclos negativos si el número de actualizaciones excede un límite.
- Marcar nodos y aristas en ciclos negativos para su posterior eliminación.
- Remover los nodos y aristas involucradas en ciclos negativos, asegurándose que el grafo final no contenga dichos ciclos.
- Optimización de rendimiento:
- Utilización de
deque
para gestionar eficientemente los nodos. - Implementación de estrategias SLF (Smallest Label First) y LLL (Large Label Last) para optimizar el ordenamiento de los nodos en la cola, priorizando los de menor costo y retrasando los de costo mayor. Esto mejora la eficiencia de las actualizaciones de las distancias.
- Utilización de
El método multiObjectiveShortestPath
calcula rutas óptimas considerando múltiples objetivos (costo y tiempo):
- Se inicializa un conjunto de soluciones por nodo y una cola de prioridad para gestionar las etiquetas.
- Se evalúan todos los nodos utilizando la cola de prioridad, expandiendo y comparando soluciones para evitar las sub-óptimas.
- Se mantienen las mejores soluciones en términos de costo y tiempo para cada nodo, y se eliminan las dominadas.
- Al final del proceso, se presentan rutas óptimas ordenadas para un número máximo de nodos especificado por el usuario.
Implementé diversas pruebas unitarias usando gtest
para comprobar:
- La correcta inicialización del grafo y adición de aristas (
Constructor
,AddEdge
). - La lectura y manejo de archivos (
FromFileValid
,FromFileInvalid
,FromFileNonexistent
). - La eliminación de ciclos negativos (
RemoveNegativeCycles
). - La ejecución del algoritmo multi-objetivo (
MultiObjectiveShortestPath
,DisconnectedGraph
,LargeGraph
). - El manejo de casos de entrada inválidos (
InvalidInputs
).
Este enfoque asegura la robustez del código y permite detectar y corregir errores de manera eficiente.
Para realizar las pruebas, utilicé un servidor con las siguientes especificaciones:
- CPU: Intel Xeon Scalable - 4 núcleos
- RAM: 32 GB
Este servidor está alojado en Google Cloud Platform (GCP) y cuenta con un procesador optimizado para tareas de alto rendimiento en entornos de servidor.
Prueba | Descripción | Resultado | Ejecución |
---|---|---|---|
Grafo simple con tiempos | Grafo con una variación entre más costoso y menor tiempo, el algoritmo debe mostrar ambos resultados especificando cuál es más costoso y cuál tiene un tiempo reducido | Pasa | ./GraphMain 1 0 5 |
Grafo con ciclo nomal | El algoritmo debe validar que el grafo no tiene ningún ciclo negativo ya que la suma de los pesos es 1 en la prueba | Pasa | ./GraphMain 2 0 5 |
Grafo con un solo ciclo negativo | Grafo de 10 nodos con un ciclo negativo del nodo 0 al 4, el algoritmo debe eliminar los nodos de 0 al 4 pero permitiendo ver las rutas del 5 al 9 | Pasa | ./GraphMain 3 0 5 - ./GraphMain 3 5 10 |
Grafo de gran escala | El algoritmo debe poder procesar un grafo de 100 mil nodos y 1 millón de aristas para comprobar el rendimiento del mismo | Pasa | ./GraphMain 6 9584 10 |
Grafo sin diferencia de tiempo | El grafo no debe contar con diferencias en tiempos de entrega en ninguna ruta, permitiendo encontrar la mejor ruta de una manera tradicional sin contar el tiempo | Pasa | ./GraphMain 2 0 5 |
Clonar el proyecto:
git clone https://github.com/KloutDevs/technical_challenge.git
cd technical_challenge
Necesitarás tener instalado g++
y cmake
.
Para compilar y ejecutar el proyecto:
cmake .
make
cd build
./GraphMain Opción Nodo_Origen MáximoResultados
Para ejecutar las pruebas necesitarás tener instalado gtest
:
Ejecutar las pruebas:
./GraphTest