martes, 29 de octubre de 2019

Camino mas corto: Algoritmo de dijkstra

Para el problema de la ruta corta tenemos varios algoritmos, en esta oportunidad se explicará el algoritmo de dijkstra el cual usa una técnica voraz (greedy). Al final del articulo se encuentran adjuntas las implementaciones en C++ y JAVA.
Descripción
El algoritmo de dijkstra determina la ruta más corta desde un nodo origen hacia los demás nodos para ello es requerido como entrada un grafo cuyas aristas posean pesos. Algunas consideraciones:
  • Si los pesos de mis aristas son de valor 1, entonces bastará con usar el algoritmo de BFS.
  • Si los pesos de mis aristas son negativos no puedo usar el algoritmo de dijsktra, para pesos negativos tenemos otro algoritmo llamado Algoritmo de Bellmand-Ford.
Como trabaja
Primero marcamos todos los vértices como no utilizados. El algoritmo parte de un vértice origen que será ingresado, a partir de ese vértices evaluaremos sus adyacentes, como dijkstra usa una técnica greedy – La técnica greedy utiliza el principio de que para que un camino sea óptimo, todos los caminos que contiene también deben ser óptimos- entre todos los vértices adyacentes, buscamos el que esté más cerca de nuestro punto origen, lo tomamos como punto intermedio y vemos si podemos llegar más rápido a través de este vértice a los demás. Después escogemos al siguiente más cercano (con las distancias ya actualizadas) y repetimos el proceso. Esto lo hacemos hasta que el vértice no utilizado más cercano sea nuestro destino. Al proceso de actualizar las distancias tomando como punto intermedio al nuevo vértice se le conoce como relajación (relaxation).
Dijkstra es muy similar a BFS, si recordamos BFS usaba una Cola para el recorrido para el caso de Dijkstra usaremos una Cola de Prioridad o Heap, este Heap debe tener la propiedad de Min-Heap es decir cada vez que extraiga un elemento del Heap me debe devolver el de menor valor, en nuestro caso dicho valor será el peso acumulado en los nodos.
Tanto java como C++ cuentan con una cola de prioridad ambos implementan un Binary Heap aunque con un Fibonacci Heap la complejidad de dijkstra se reduce haciéndolo mas eficiente, pero en un concurso mas vale usar la librería que intentar programar una nueva estructura como un Fibonacci Heap, claro que particularmente uno puede investigar y programarlo para saber como funciona internamente.
Algoritmo en pseudocódigo
Considerar distancia[ i ] como la distancia mas corta del vértice origen ingresado al vértice i.
método Dijkstra(Grafo,origen):
2      creamos una cola de prioridad Q
3      agregamos origen a la cola de prioridad Q
4      mientras Q no este vacío:
5          sacamos un elemento de la cola Q llamado u
6          si u ya fue visitado continuo sacando elementos de Q    
7          marcamos como visitado u
8          para cada vértice v adyacente a u en el Grafo:
9              sea w el peso entre vértices ( u , v )  
10             si v no ah sido visitado:
11                Relajacion( u , v , w )

1  método Relajacion( actual , adyacente , peso ):
2      si distancia[ actual ] + peso < distancia[ adyacente ]
3         distancia[ adyacente ] = distancia[ actual ] + peso
4         agregamos adyacente a la cola de prioridad Q
Ejemplo y Código paso a paso
Tengamos el siguiente grafo, cuyos ID están en color negro encima de cada vértice, los pesos esta en color azul y la distancia inicial en cada vértice es infinito
Algunas consideraciones para entender el código que se explicara junto con el funcionamiento del algoritmo.
1
2
3
#define MAX 10005 //maximo numero de vértices
#define Node pair< int , int > //definimos el nodo como un par( first , second ) donde first es el vertice adyacente y second el peso de la arista
#define INF 1<<30 //definimos un valor grande que represente la distancia infinita inicial, basta conque sea superior al maximo valor del peso en alguna de las aristas
Inicializamos los valores de nuestros arreglos
Donde:
  • Vértices: ID de los vértices.
  • Distancia[ u ]: Distancia mas corta desde vértice inicial a vértice con ID = u.
  • Visitado[ u ]: 0 si el vértice con ID = u no fue visitado y 1 si ya fue visitado.
  • Previo[ u ]: Almacenara el ID del vértice anterior al vértice con ID = u, me servirá para impresión del camino mas corto.
En cuanto al código los declaramos de la siguiente manera:
1
2
3
4
5
6
vector< Node > ady[ MAX ]; //lista de adyacencia
int distancia[ MAX ];      //distancia[ u ] distancia de vértice inicial a vértice con ID = u
bool visitado[ MAX ];      //para vértices visitados
int previo[ MAX ];         //para la impresion de caminos
priority_queue< Node , vector<Node> , cmp > Q; //priority queue propia del c++, usamos el comparador definido para que el de menor valor este en el tope
int V;                     //numero de vertices
Y la función de inicialización será simplemente lo siguiente
1
2
3
4
5
6
7
8
//función de inicialización
void init(){
    for( int i = 0 ; i <= V ; ++i ){
        distancia[ i ] = INF;  //inicializamos todas las distancias con valor infinito
        visitado[ i ] = false; //inicializamos todos los vértices como no visitado
        previo[ i ] = -1;      //inicializamos el previo del vértice i con -1
    }
}
De acuerdo al vértice inicial que elijamos cambiara la distancia inicial, por ejemplo la ruta más corta partiendo del vértice 1 a todos los demás vértices:
El vértice 1 es visitado, la distancia de vértice 1 -> vértice 1 es 0 por estar en el mismo lugar.
1
2
Q.push( Node( inicial , 0 ) ); //Insertamos el vértice inicial en la Cola de Prioridad
distancia[ inicial ] = 0;      //Este paso es importante, inicializamos la distancia del inicial como 0
Extraemos el tope de la cola de prioridad
1
2
3
while( !Q.empty() ){                   //Mientras cola no este vacia
        actual = Q.top().first;            //Obtengo de la cola el nodo con menor peso, en un comienzo será el inicial
        Q.pop();                           //Sacamos el elemento de la cola
Si el tope ya fue visitado entonces no  tengo necesidad de evaluarlo, por ello continuaría extrayendo elementos dela cola:
1
if( visitado[ actual ] ) continue; //Si el vértice actual ya fue visitado entonces sigo sacando elementos de la cola
En este caso al ser el tope el inicial no esta visitado por lo tanto marcamos como visitado.
1
visitado[ actual ] = true;         //Marco como visitado el vértice actual
Hasta este momento la tabla quedaría de la siguiente manera
Ahora vemos sus adyacentes que no hayan sido visitados. Tendríamos 2 y 4.
1
2
3
4
for( int i = 0 ; i < ady[ actual ].size() ; ++i ){ //reviso sus adyacentes del vertice actual
    adyacente = ady[ actual ][ i ].first;   //id del vertice adyacente
    peso = ady[ actual ][ i ].second;        //peso de la arista que une actual con adyacente ( actual , adyacente )
    if( !visitado[ adyacente ] ){        //si el vertice adyacente no fue visitado
Evaluamos primero para vértice 2
Vemos que la distancia actual desde el vértice inicial a 2 es ∞, verifiquemos el paso de relajación:
distancia[ 1 ] + 7 < distancia[ 2 ]      ->       0 + 7 < ∞        ->         7 < ∞
El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 2 y agregando el vértice en la cola de prioridad con nueva distancia quedando:
El paso de relajación se daría en la siguiente parte:
1
2
3
4
5
6
7
for( int i = 0 ; i < ady[ actual ].size() ; ++i ){ //reviso sus adyacentes del vertice actual
    adyacente = ady[ actual ][ i ].first;   //id del vertice adyacente
    peso = ady[ actual ][ i ].second;        //peso de la arista que une actual con adyacente ( actual , adyacente )
    if( !visitado[ adyacente ] ){        //si el vertice adyacente no fue visitado
        relajacion( actual , adyacente , peso ); //realizamos el paso de relajacion
    }
}
Donde la función de relajación seria
1
2
3
4
5
6
7
8
9
//Paso de relajacion
void relajacion( int actual , int adyacente , int peso ){
    //Si la distancia del origen al vertice actual + peso de su arista es menor a la distancia del origen al vertice adyacente
    if( distancia[ actual ] + peso < distancia[ adyacente ] ){
        distancia[ adyacente ] = distancia[ actual ] + peso;  //relajamos el vertice actualizando la distancia
        previo[ adyacente ] = actual;                         //a su vez actualizamos el vertice previo
        Q.push( Node( adyacente , distancia[ adyacente ] ) ); //agregamos adyacente a la cola de prioridad
    }
}
Ahora evaluamos al siguiente adyacente que es el vértice 4:
De manera similar al anterior vemos que la distancia actual desde el vértice inicial a 4 es ∞, verifiquemos el paso de relajación:
distancia[ 1 ] + 2 < distancia[ 4 ]      ->      0 + 2 < ∞        ->        2 < ∞
El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 4 quedando:
En cuanto a la cola de prioridad como tenemos un vértice con menor peso este nuevo vértice ira en el tope de la cola:
Revisamos sus adyacentes no visitados que serian vértices 2, 3 y 5.
Empecemos por el vértice 2:
Ahora vemos que la distancia actual del vértice inicial al vértice 2 es 7, verifiquemos el paso de relajación:
distancia[ 4 ] + 3 < distancia[ 2 ]      ->      2 + 3 < 7         ->         5 < 7
En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al vértice 2, actualizamos la distancia en el vértice 2 y actualizamos el vértice previo al actual quedando:
En cuanto a la cola de prioridad como tenemos un vértice con menor peso este nuevo vértice ira en el tope de la cola, podemos ver que tenemos 2 veces el mismo vértice pero como usamos una técnica greedy siempre usaremos el valor óptimo:
Continuamos con los Vértices 3 y 5 como tienen valor ∞ si será posible relajarlos por lo que sería similar a los pasos iniciales solo que en los pasos iniciales distancia[ 1 ] era 0 en este caso distancia[ 4 ] es 2, quedando:
Hemos terminado de evaluar al vértice 4, continuamos con el tope de la cola que es vértice 2, el cual marcamos como visitado.
Los adyacentes no visitados del vértice 2 son los vértices 3 y 5. Comencemos con el vértice 3
Ahora vemos que la distancia actual del vértice inicial al vértice 3 es 10, verifiquemos el paso de relajación:
distancia[ 2 ] + 1 < distancia[ 3 ]      ->      5 + 1 < 10         ->         6 < 10
En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al vértice 3, dicha ruta sería 1 -> 4 -> 2 -> 3 cuyo peso es 6 que es mucho menor que la ruta 1 – > 4 -> 3 cuyo peso es 10, actualizamos la distancia en el vértice 3 quedando:
El siguiente vértice de la cola de prioridad es el vértice 3 y su único adyacente no visitado es el vértice 5.
Vemos que la distancia actual del vértice inicial al vértice 5 es 7, verifiquemos el paso de relajación:
distancia[ 3 ] + 5 < distancia[ 5 ]      ->      6 + 5 < 7         ->         11 < 7
En esta oportunidad se no cumple por lo que no relajamos el vértice 5, por lo que la tabla en cuanto a distancias no sufre modificaciones y no agregamos vértices a la cola:
Ahora tocaría el vértice 2 pero como ya fue visitado seguimos extrayendo elementos de la cola, el siguiente vértice será el 5.
Al ser el ultimo vértice a evaluar no posee adyacentes sin visitar por lo tanto hemos terminado el algoritmo. En el grafico anterior observamos que 2 aristas no fueron usadas para la relajación, las demás si fueron usadas. La tabla final quedaría de la siguiente manera:
De la tabla si deseo saber la distancia mas corta del vértice 1 al vértice 5, solo tengo que acceder al valor del arreglo en su índice respectivo (distancia[ 5 ]).
Shortest Path Tree
En el proceso anterior usábamos el arreglo previo[ u ] para almacenar el ID del vértice previo al vértice con ID = u, ello me sirve para formar el árbol de la ruta mas corta y además me sirve para imprimir caminos de la ruta mas corta.
 
Impresión del camino encontrado.
Para imprimir el camino mas corto deseado usamos el arreglo previo[ u ], donde u tendrá el ID del vértice destino, o sea si quiero imprimir el camino mas corto desde vértice 1 -> vértice 3 partiré desde previo[ 3 ] hasta el previo[ 1 ]. De manera similar a lo que se explico en el algoritmo BFS, en este caso se realizara de manera recursiva:
1
2
3
4
5
6
//Impresion del camino mas corto desde el vertice inicial y final ingresados
void print( int destino ){
    if( previo[ destino ] != -1 )    //si aun poseo un vertice previo
        print( previo[ destino ] );  //recursivamente sigo explorando
    printf("%d " , destino );        //terminada la recursion imprimo los vertices recorridos
}
Veamos gráficamente el funcionamiento, desde el grafo comenzamos en 3
El previo de 3 es el vértice 2, por lo tanto ahora evaluó 2:
Ahora el previo de 2 es el vértice 4:
El previo de 4 es el vértice inicial 1
Como el previo de 1 es -1 terminamos el recorrido, ahora en el retorno de las llamadas recursivas imprimo el camino: 1 4 2 3

miércoles, 9 de octubre de 2019

Método de transporte de Vogel

El método de Vogel, o aproximación de Vogel, es un método que permite llegar a una solución inicial factible del problema de transporte.
El procedimiento de este método es el siguiente:
  1. Tener los valores de costos de envíos desde cada origen a cada destino tabulados (matriz de costos). En caso de que la matriz no este equilibrada (el numero de filas es diferentes del numero de columnas), agregar una fila o columna de ceros según corresponda. Esto quiere decir que según sea el caso se creara un origen o un destino ficticio.
  2. Realizar el cálculo de las penalizaciones para cada fila y columna. Las penalizaciones se calculan restando los dos valores más pequeños de cada fila y cada columna. Las penalizaciones tienen valor absoluto.
  3. Identificar la fila o columna con la mayor penalización (en caso de que exista un empate en las penalizaciones, se puede elegir cualquiera de las que tiene el mayor valor), y asignar la mayor cantidad de material posible a la casilla con el menor costo en esa fila o columna.
  4. Se sombrean (eliminan) las filas o columnas que hayan sido satisfechas, reduciendo así la matriz.
  5. Se repite el procedimiento desde en paso 2.
  6. Una vez satisfechos todos los orígenes y destinos (sombreadas todas las filas y columnas) se puede proceder a calcular el costo del programa de envió encontrado mediante este método (cabe resaltar que la solución factible encontrada con este método no es necesariamente la optima).

EL PROBLEMA

Una empresa energética colombiana dispone de cuatro plantas de generación para satisfacer la demanda diaria eléctrica en cuatro ciudades, Cali, Bogotá, Medellín y Barranquilla. Las plantas 1,2,3 y 4 pueden satisfacer 80, 30, 60 y 45 millones de KW al día respectivamente. Las necesidades de las ciudades de Cali, Bogotá, Medellín y Barranquilla son de 70, 40, 70 y 35 millones de Kw al día respectivamente.

Los costos asociados al envío de suministro energético por cada millón de KW entre cada planta y cada ciudad son los registrados en la siguiente tabla.
Método vogel
formule un modelo de programación lineal que permita satisfacer las necesidades de todas las ciudades al tiempo que minimice los costos asociados al transporte.



SOLUCIÓN PASO A PASO


El primer paso es determinar las medidas de penalización y consignarlas en el tabulado de costos, tal como se muestra a continuación.

Método de vogel
El paso siguiente es escoger la mayor penalización, de esta manera:
Método de Vogel
El paso siguiente es escoger de esta columna el menor valor, y en una tabla paralela se le asigna la mayor cantidad posible de unidades, podemos observar como el menor costo es "2" y que a esa celda se le pueden asignar como máximo 60 unidades "que es la capacidad de la planta 3".
Método de Vogel
Dado que la fila de la "Planta 3" ya ha asignado toda su capacidad (60 unidades) esta debe desaparecer.
Método de Vogel
Se ha llegado al final del ciclo, por ende se repite el proceso
Método de Vogel
Iniciamos una nueva iteración
Método de Vogel
www.ingenieriaindustrialonline.com
Continuamos con las iteraciones,
Método de Vogel
www.ingenieriaindustrialonline.com
Iniciamos otra iteración
Método de Vogel
Al finalizar esta iteración podemos observar como el tabulado queda una fila sin tachar y con valores positivos, por ende asignamos las variables básicas y hemos concluido el método.
Método de Vogel
Los costos asociados a la distribución son:
Método de Vogel
Método de Vogel
www.ingenieriaindustrialonline.com
De esta manera hemos llegado a la solución a la cual también llegamos mediante programación lineal, definitivamente desarrollar la capacidad para modelar mediante programación lineal y apoyarse de una buena herramienta como WinQSB, STORM, LINGOTORA etc. termina siendo mucho más eficiente que la utilización de los métodos heurísticos para problemas determinísticos

viernes, 4 de octubre de 2019

Problemas de transporte

Un problema de transporte surge cuando se necesita un modelo costo-efectividad que permita transportar ciertos bienes desde un lugar de origen a un destino que necesita aquellos bienes, con ciertas restricciones en la cantidad que se puede transportar

El PT es un caso particular de la PL
  • Se debe determinar un esquema óptimo de transporte que se origina en los lugares de oferta donde la existencia de cierta mercancía es conocida, y llega a los lugares de donde se conoce la cantidad requerida. El costo de cada envió es proporcional a la cantidad transportada y, el costo total es la suma de los costos individuales.


EL PROBLEMA DE TRANSPORTE
  • Corresponde a un problema de flujo de mínimo costo
  • Supongamos que deseamos enviar productos desde las bodegas a los lugares de venta
Ejemplo:
  • 3 bodegas
  • 4 puntos de venta
  • ai: oferta en bodega i
  • bj: demanda de vendedor j
  • cij: costo de envio de j
  • Sea xij la cantidad enviada de j
  • Formule el LP
  • TECNICA DE TRANSPORTE.
    Los pasos básicos de la técnica de transporte son:
    Paso 1: determínese una solución factible.
    Paso 2: determínese la variable que entra, que se elige entre las variables no básicas. Si todas estas variables satisfacen la condición de optimidad (del método simplex), deténgase; de lo contrario, diríjase al paso 3.
    Paso 3: determínese la variable que sale (mediante el uso de la condición de factibilidad) de entre las variables de la solución básica actual; después obténgase la nueva solución básica. Regrese al paso 2.
    OBTENCIÓN DE SOLUCIONES BÁSICAS FACTIBLES PARA PROBLEMAS DE TRANSPORTES
    Podemos obtener una solución básica factible (sbf) para un problema de transporte balanceado mediante el método de la esquina Noroeste, el método de costo mínimo, o el método de Vogel.
    Para obtener una sbf mediante el método de la esquina noroeste, empiece en la esquina superior izquierda del cuadro del transporte y haga a X11 lo más grande posible.
    Naturalmente, X11 no puede ser mayor que el menor valor Si y así X11 S1 tache el primer renglón del cuadro de transporte; Esto indica que si habrá más variables básicas del renglón 1 del cuadro. También d1-S1 . Si X11=d1, tache la primera la columna del cuadro de transporte y cambie S1 – d1.
    Si X11= S1 = d1, tache o el renglón 1, o la columna 1 (pero no ambos), del cuadro de transporte. Si tacha el renglón 1, cambie d1 por cero; si tacha columna 1, cambie S 1 por 0.
    Continúe aplicando este procedimiento a la celda mas noroeste del cuadro que no cae en un renglón eliminado o en una columna eliminada.
    Finalmente, llegara un momento en el cual solo queda una celda a la cual se puede asignar un valor.
    Asigne a esta celda un valor igual a la oferta de su renglón o a la demanda de su columna, y tache el renglón y la columna de la celda. Se obtiene de esta manera una solución básica factible.
    OBTENER LA SOLUCIÓN ÓPTIMA PARA UN PROBLEMA DE TRANSPORTE
    Paso 1: Si el problema no está balanceado, balancéelo.
    Paso 2: Utilice uno de los métodos descritos anteriormente para obtener una solución básica factible.
    Paso 3: Utilice el hecho de que U1=0, y Ui+Vj=Cij en todas las variables básicas para encontrar (U1,U2…Um V1,V2…Vn) para la sbf actual.
    Paso 4: Si Ui + Vj – Cij es menor o igual a cero, para todas las variables no básicas, entonces la sbf actual es óptima. Si no es así se introduce la variable con valor más positivo de Ui + Vj –Cij en la base. Para hacer esto, encuentre un circuito cerrado (se puede demostrar que solamente existe un circuito cerrado) que contiene la variable que entra y algunas de las variables básicas. Después, tomando en cuenta solamente las celdas en el circuito cerrado marque las que se encuentren alejadas en número par (0,2,4,6,…) de celdas de la variable que entra como celdas pares. También marque las celdas en el circuito cerrado, que se encuentra un número impar de celdas de la variable que entra como celdas impares. Ahora encuentre la celda impar cuya variable toma el menor valor. Llame este valor teta. La variable correspondiente a esta celda impar saldrá de la base. Para realizar el pivoteo, disminuye el valor de cada celda impar en teta y aumenta el valor de cada celda par en teta. Los valores de las variables que no se encuentran en el circuito cerrado permanecen sin cambio. Ahora se completó el bloqueo.
    Sí teta es igual a cero, la variable que entra será igual a cero, y una variable impar que tiene un valor actual de cero, saldrá de la base. En este caso, existía un sbf degenerada antes del pivoteo y resultará después del pivoteo.
    Si más de una celda impar en el circuito cerrado es igual a teta. Puede escoger arbitrariamente una de estas celdas impares para que salga de la base; se obtendrá una vez más una sbf degenerada. El pivoteo produce una nueva sbf.
    Paso 5: Regrese a los pasos 3 y 4, utilizando la nueva sbf. Para un problema de maximización, proceda como se especificó, pero cambie el paso 4 por el paso 4’.
    Paso 6: Si Ui + Vj –Cij es mayor o igual a cero, para todas las variables no básicas, entonces, la sbf actual es óptima. De otra manera, coloque la variable con el valor más negativo de Ui + Vj – Cij en la base mediante el procedimiento de pivoteo.


miércoles, 2 de octubre de 2019

Ejercicios método simplex

Ejercicio 1.
Un negocio se dedica a la fabricación de sillas y mesas para fabricar cada una de ellas se consume una determinada cantidad de recursos en los departamentos de corte y ensamble. Los recursos están eh horas hombres y son 120 horas, para corte y 90 horas para el ensamble. Cada unidad fabricada ofrece la siguiente ganancia: 50 pesos para las mesas y 80 pesos para una silla.


Ejercicio 2.
Una empresa vitivinicultora ha adquirido recientemente un terreno de 110 hectáreas. Se puede vender toda la producción de uvas blanck y chardonai. Se desea conocer cuanto plantar de cada variedad en las 110 hectáreas, dados los costos, beneficios netos y requerimientos de mano de obra según los datos.


Ejercicio 3.
Un taller tiene 3 tipos de maquina A, B Y C; puede fabricar dos productos 1 y 2 , todos los productos tienen que ir a cada maquina y cada uno va en el mismo orden, primero a la A luego a la B y C. Que permita la máxima ganancia del taller.