Apuntes Unidad I Grafos PTR

94
UNIDAD I GRAFOS 1.1 INTRODUCCIÓN A GRAFOS La Teoría de Grafos es parte de Matemáticas Discreta que tiene un marcado enfoque práctico, aplicado y computacional, además de un acentuado carácter formativo. El contenido referido a esta temática se plantea como respuesta a una variada serie de problemas de la «vida real» (diseño de bloques, flujo de redes, diseño de circuitos, transporte de viajeros, asignaciones horarias o de tareas, programación, etc. G1 es un grafo no simple, pues consta de dos lados paralelos e2 y e3 y dos lazos e1 y e4, pues e2 = (V1, V2), e3 = (V1, V2) y e1 es un lazo, pues e1 = (V1, V1) GRAFOS Existen varios tipos de grafos: a) Un GRAFO NO DIRIGIDO G consiste en un conjunto V de vértices (o nodos) y un conjunto E de aristas (o arcos) tal que cada arista e ∈ E se asocia con un par no ordenado de vértices. Entonces se puede decir que si existe una arista e entre un par de vértices v y w, esta puede ser igual a: e = ( v, w) o e = ( w, v)

Transcript of Apuntes Unidad I Grafos PTR

Page 1: Apuntes Unidad I Grafos PTR

UNIDAD I GRAFOS

1.1 INTRODUCCIÓN A GRAFOS

La Teoría de Grafos es parte de Matemáticas Discreta que tiene un marcado enfoque práctico, aplicado y computacional, además de un acentuado carácter formativo. El contenido referido a esta temática se plantea como respuesta a una variada serie de problemas de la «vida real» (diseño de bloques, flujo de redes, diseño de circuitos, transporte de viajeros, asignaciones horarias o de tareas, programación, etc.

G1 es un grafo no simple, pues consta de dos lados paralelos e2 y e3 y dos lazos e1 y e4, pues e2 = (V1, V2), e3 = (V1, V2) y e1 es un lazo, pues e1 = (V1, V1)

GRAFOS

Existen varios tipos de grafos:

a) Un GRAFO NO DIRIGIDO G consiste en un conjunto V de vértices (o nodos) y un conjunto E de aristas (o arcos) tal que cada arista e ∈ E se asocia con un par no ordenado de vértices. Entonces se puede decir que si existe una arista e entre un par de vértices v y w, esta puede ser igual a:

e = ( v, w) o e = ( w, v)

Un grafo esta formado de vértices y aristas (lados), por lo tanto G = {V, E}

Los vértices del grafo G son: V_1, V_2, V_3, V_4 entonces V = {V_1, V_2, V_3, V_4}

Las aristas del grafo son: e_1, e_2, e_3, e_4 entonces E = {e_1, e_2, e_3, e_4}

Entonces podemos decir que G = {{V_1, V_2, V_3, V_4}, {e_1, e_2, e_3, e_4}}

Como el grafo G no tiene lazos ni lados paralelos entonces es un grafo simple.

Page 2: Apuntes Unidad I Grafos PTR

Otro ejemplo de grafo no dirigido es el siguiente:

El grafo no dirigido permite que diferentes aristas se asocien con el mismo par de vértices. Como podemos apreciar todas las aristas que se asocien con el mismo par de vértices se denomina aristas paralelas, para el gráfico serian e1 y e2 pues convergen en los vértices (V1, V2). Denominamos lazo a aquella arista que inciden en un mismo vértice para la grafica sería e3, debido a que incide en el vértice V2; e3 = (V2, V2). Un vértice como V4 que no incide en ninguna arista se llama vértice aislado.

Un grafo que contiene lazos y aristas paralelas se llama grafo no simple.

b) Un GRAFO DIRIGIDO G consiste en un conjunto V de vértices (o nodos) y un conjunto E de aristas (o arcos) tal que cada arista e ∈ E se asocia con un par ordenado de vértices. Si hay una única arista e asociada con el par ordenado (v, w) de vértices, se escribe e = (v, w), que denota una arista de v a w. En la grafica dirigida sus aristas dirigidas se indican por flechas.

La arista e_3 se asocia con el par ordenado de vértices (V_4,V_3) debido a que tiene como vértice origen a V_4 y vértice destino a V_3, y se denota por: e_3 = {V_4, V_3}. No se puede decir que: e_3 = {V_3, V_4} porque la dirección de la arista e_3 no es esa.

Page 3: Apuntes Unidad I Grafos PTR

c) Un GRAFO PONDERADO G es aquel en donde se muestran los pesos de cada una de sus aristas. Peso es el valor asignado a cada arista. En un grafo ponderado la longitud de una ruta es la suma de los pesos de las aristas en la ruta.

Ejercicio

Complete el cuadro, con la longitud de las diferentes rutas del grafo G expuesto arriba.

d) GRAFO COMPLETO sobre n vértices, denotada por Kn, es la gráfica simple con n vértices en la que hay una arista entre cada par de vértices distintos.

Page 4: Apuntes Unidad I Grafos PTR

G de K_4 y tiene solo una arista entre cada par de vértices.

e) GRAFO DE SIMILITUD se refiere a un grafo que tiende a agrupar objetos semejantes en clases, con base en las propiedades de los objetos. En el textos base pagina 323 se muestra un ejercicio sugiero revisarlo.

SUBGRAFOS

Se puede obtener de un grafo un subgrafo. Por ejemplo

Sea G = (V, E) una gráfica. (V’, E’) es una subgráfica de G si:

a) V’ ⊆ V y E’ ⊆ E

b) Para toda arista e’ ∈ E’, si e’ incide en v’ y w’, entonces v’, w’ ∈ V’

Por lo antes mencionado podemos determinar que G’ es un subgrafo de G.

V = {V_1, V_2, V_3, V_4} E = {e_1, e_2, e_3, e_4, e_5}

Page 5: Apuntes Unidad I Grafos PTR

V’ = {V_1, V_2, V_3} E‘ = {e_1, e_2, e_5}

V’ ⊆ V E’ ⊆ E

Por lo tanto G’ ⊆ G

GRADO DE UN VERTICE (δ)

El grado de un vértice v, δ(v), es el número de aristas que inciden en v.

δ (V1) = 3

δ (V2) = 5

δ (V3) = 4

δ (V4) = 1

δ (V5) = 5

δ (V6) = 2

δ (V7) = 2

δ (V8) = 0

1.2 CAMINOS Y CICLOS

Camino o trayectoria es el recorrido desde un V0 (vértice inicial) a Vn (vértice destino) de longitud n, es una sucesión alternante de n + 1 vértices y n aristas que comienza en el vértice V0 y termina en el Vn . Formalmente significa: comience en el vértice v0; recorra la arista e1 hasta v1; siga por la arista e2 hasta v2, y así sucesivamente.

(v_0', e_1', v_1', e_2', v_2', …. v_{n - 1}, e_n, v_n)

Page 6: Apuntes Unidad I Grafos PTR

Donde la arista e_i es incidente sobre los vértices v_{i - 1} y v_i para i = 1, … n.

Ejemplo:

Construyamos un camino en G desde el vértice 1 hasta el vértice 2 de longitud 4.

Camino o trayectoria de G: (1, e_1, 2, e_2, 3, e_3, 4, e_4, 2)

Además, el mismo camino se puede representar así: (1, 2, 3, 4, 2)

En caso de que no se repita ningún vértice entonces recibe el nombre de camino simple, por ejemplo: (1, 2, 3,4) y (7, 6, 5,2)

IMPORTANTE

Sean v y w vértices en un grafo G.

Un camino simple o trayectoria de v a w es una ruta de v a w sin vértices repetidos.

Un ciclo (o circuito) es un camino de longitud diferente de cero de v a v sin aristas repetidas.

Un ciclo simple es un ciclo de v a v en el que no hay vértices repetidos, excepto por el inicio y el fin que son iguales a v.

Tomando como referencia la grafica anterior, vamos a determinar si tiene caminos simples, ciclo y ciclo simple.

Page 7: Apuntes Unidad I Grafos PTR

Una vez aprendido las definiciones anteriores y comprendido el ejemplo, pasamos a estudiar lo que son las gráficas conexas.

Una grafica G es conexa si dados cualesquiera dos vértices v y w en G existe una trayectoria de v a w.

La grafica que se muestra no es conexa por cuanto no existe trayectoria de v_3 a v_5

CICLO DE EULER Y DE HAMILTON

CICLO DE EULER

Si una grafica G tiene un ciclo de Euler, entonces G es conexa y todo vértice tiene grado par (2, 4, 6 …).

Ejemplo:

Verificar si la siguiente gráfica tiene ciclo de Euler.

Page 8: Apuntes Unidad I Grafos PTR

Observamos que la grafica es conexa, puesto que sus vértices tienen grado par así:

δ(v1) = δ (v2) = δ (v3)= δ (v5) = 4

δ (v4) = 6

δ (v6) = δ (v7) = 2

Por lo tanto si existe ciclo de Euler el mismo que es: (v6, v4, v7, v5, v1, v3, v4, v1, v2, v5, v4, v2, v3, v6), si se dan cuenta una característica importante de este camino es que ninguna de sus aristas se repiten.

IMPORTANTE

Si G no tiene aristas entonces tiene un solo vértice por lo tanto contiene un ciclo de Euler.

1.3 CICLO DE HAMILTON

En honor de Hamilton, decimos que un ciclo en una grafica G que contiene cada vértice en G exactamente una vez, excepto por el vértice inicial y final que aparece dos veces, recibe el nombre de ciclo hamiltoniano.

Ejemplo:

Partiendo de la gráfica que se muestra trace un camino de (a) a (a) que contenga todos los vértices sin repetir ninguno.

Page 9: Apuntes Unidad I Grafos PTR

El camino de Hamilton solicitado sería: (a, f, g, p, q, r, s, t, o, n, m, l, k, j, i, h, b, c, d, e, a). Como pueden observar no se repite vértice alguno, excepto el inicial y final que es el mismo.

1.4 ALGORITOMOS DE LA RUTA MÁS CORTA

Problema del camino más corto

Ejemplo de Grafo Ponderado

En la Teoría de grafos, el problema de los caminos más cortos es el problema que consiste en encontrar un camino entre dos vértices (o nodos) de tal manera que la suma de los pesos de las aristas que lo constituyen es mínima. Un ejemplo es encontrar el camino más rápido para ir de una

Page 10: Apuntes Unidad I Grafos PTR

ciudad a otra en un mapa. En este caso, los vértices representan las ciudades, y las aristas las carreteras que las unen, cuya ponderación viene dada por el tiempo que se emplea en atravesarlas.

Introducción

Formalmente, dado un grafo ponderado (que es un conjunto V de vértices, un conjunto E de aristas y una función de variable real ponderada f : E → R) y un elemento v ∈ V encuentra un camino P de v a v' ∈ V, tal que:

es el mínimo entre todos los caminos que conectan v y v'.

El problema es también conocido como el problema de los caminos más cortos entre dos nodos, para diferenciarlo de la siguiente generalización:

El problema de los caminos más cortos desde un origen en el cual tenemos que encontrar los caminos más cortos de un vértice origen v a todos los demás vértices del grafo.

El problema de los caminos más cortos con un destino en el cual tenemos que encontrar los caminos más cortos desde todos los vértices del grafo a un único vértice destino, esto puede ser reducido al problema anterior invirtiendo el orden.

El problema de los caminos más cortos entre todos los pares de vértices, el cual tenemos que encontrar los caminos más cortos entre cada par de vértices (v , v') en el grafo.

Algoritmos

Los algoritmos más importantes para resolver este problema son:

Algoritmo de Dijkstra, resuelve el problema de los caminos más cortos entre dos vértices, desde un origen y un único destino.

Algoritmo de Bellman - Ford, resuelve el problema de los caminos más cortos desde un origen si la ponderación de las aristas es negativa.

Algoritmo de Búsqueda A*, resuelve el problema de los caminos más cortos entre un par de vértices usando la heurística para intentar agilizar la búsqueda.

Algoritmo de Floyd - Warshall, resuelve el problema de los caminos más cortos entre todos los vértices.

Algoritmo de Johnson, resuelve el problema de los caminos más cortos entre todos los vértices y puede ser más rápido que el de Floyd - Warshall en grafos de baja densidad.

Teoría perturbacional, encuentra en el peor de los casos el camino más corto a nivel local.

Page 11: Apuntes Unidad I Grafos PTR

Aplicaciones

Los algoritmos de los caminos más cortos se aplican para encontrar direcciones de forma automática entre localizaciones físicas, tales como direcciones en mapas callejeros.

Si un algoritmo representa una máquina abstracta no determinista como un grafo, donde los vértices describen estados, y las aristas posibles transiciones, el algoritmo de los caminos más cortos se usa para encontrar una secuencia óptima de opciones para llegar a un cierto estado final o para establecer límites más bajos en el tiempo, necesario para alcanzar un estado dado. Por ejemplo, si los vértices representan los estados de un puzzle como el Cubo de Rubik, cada arista dirigida corresponde a un simple movimiento o giro. El algoritmo de los caminos más cortos se usa para encontrar la solución que utiliza el mínimo número posible de movimientos.

En el argot de las telecomunicaciones, a este algoritmo es también conocido como el problema del mínimo retraso, y con frecuencia se compara con el problema de los caminos más anchos.

Una aplicación más coloquial es la teoría de los "Seis grados de separación", a partir de la cual se intenta encontrar el camino más corto entre dos personas cualesquiera.

Otras aplicaciones incluyen la Investigación de operaciones, instalaciones y facilidad de diseño, robótica, transporte y VLSI de diseño.

Problemas Relacionados

El problema de viajante de comercio, es el problema que trata de encontrar el camino más corto que pasa sólo una vez por cada vértice y regresa al comienzo. A diferencia de los caminos más cortos, el cual puede ser resuelto en un tiempo polinomial en grafos sin ciclos negativos, este problema es NP-completo, y como tal, no tiene una resolución eficiente (ver P=Problema NP).

El problema de encontrar el camino más largo es también NP-completo.

Anexo:Ejemplo de Algoritmo de Dijkstra

Page 12: Apuntes Unidad I Grafos PTR

Grafo inicial

Camino mínimo final

Hay diferentes algoritmos para hallar un camino de longitud mínima entre dos vértices de un grafo ponderado. Presentaremos un algoritmo descubierto por el matemático holandés Edsger Dijkstra en 1959. La versión que descubriremos resuelve este problema para grafos ponderados no dirigidos si todos los pesos son positivos. Este algorimo puede adaptarse fácilmente para resolver problemas de caminos de longitud mínima en grafo dirigidos.

A este algoritmo se le llama Algoritmo de Dijkstra:

Ejemplo

El siguiente ejemplo se desarrollará con el fin de encontrar el camino más corto desde a hasta z:

Leyenda:

Rojo: Aristas y vértices pertenecientes a la solución momentánea. Azul: Aristas y vértices candidatos.

Paso 1

Page 13: Apuntes Unidad I Grafos PTR

En este primer paso, podemos apreciar que hay tres candidatos: Los vértices b, c y d. En este caso, hacemos el camino desde el vértice a, hasta el vértice d, ya que es el camino más corto de los tres.jump!!!

Solución momentánea:

Camino: AD Distancia:5

Paso 2

Ahora, vemos que se añade un nuevo candidato, el vértice e, y el vértice c, pero esta vez a través del d. Pero el camino mínimo surge al añadir el vértice c.

Solución momentánea:

Camino: ADC Distancia:9

Paso 3

Page 14: Apuntes Unidad I Grafos PTR

En este paso no se añade ningún candidato más puesto que el último vértice es el mismo que en el paso anterior. En este caso el camino mínimo hallado es el siguiente:

Solución momentánea:

Camino: ADCB Distancia:11

Paso 4

Como podemos comprobar, se han añadido dos candidatos nuevos, los vértices f y g, ambos a través del vértice b. El mínimo camino hallado en todo el grafo hasta ahora es el siguiente:

Solución momentánea:

Camino: ADCBF Distancia:15

Paso 5

Page 15: Apuntes Unidad I Grafos PTR

En este antepenúltimo paso, se añaden tres vértices candidatos, los vértices g, z y e. Este último ya estaba pero en esta ocasión aparece a través del vértice f. En este caso el camino mínimo, que cambia un poco con respecto al enterior, es:

Solución momentánea:

Camino: ADCBF Distancia:17

Paso 6

En el penúltimo paso, vuelve a aparecer otro candidato: el vértice z, pero esta vez a través del vértice g. De todas formas, el camino mínimo vuelve a cambiar para retomar el camino que venía siguiendo en los pasos anteriores:

Solución momentánea:

Camino: ADCBFE Distancia:18

Page 16: Apuntes Unidad I Grafos PTR

Paso 7

Por fin, llegamos al último paso, en el que sólo se añade un candidato, el vértice z a través del e. El camino mínimo y final obtenido es:

Solución Final:

Camino: ADCBFEZ Distancia:23

Algoritmo de Dijkstra

Ejecución del algoritmo de Dijkstra.

El algoritmo de Dijkstra, también llamado algoritmo de caminos mínimos, es un algoritmo para la determinación del camino más corto dado un vértice origen al resto de vértices en un grafo dirigido y

Page 17: Apuntes Unidad I Grafos PTR

con pesos en cada arista. Su nombre se refiere a Edsger Dijkstra, quien lo describió por primera vez en 1959.

La idea subyacente en este algoritmo consiste en ir explorando todos los caminos más cortos que parten del vértice origen y que llevan a todos los demás vértices; cuando se obtiene el camino más corto desde el vértice origen, al resto de vértices que componen el grafo, el algoritmo se detiene. El algoritmo es una especialización de la búsqueda de costo uniforme, y como tal, no funciona en grafos con aristas de costo negativo (al elegir siempre el nodo con distancia menor, pueden quedar excluidos de la búsqueda nodos que en próximas iteraciones bajarían el costo general del camino al pasar por una arista con costo negativo).

Algoritmo

Teniendo un grafo dirigido ponderado de N nodos no aislados, sea x el nodo inicial, un vector D de tamaño N guardará al final del algoritmo las distancias desde x al resto de los nodos.

1. Inicializar todas las distancias en D con un valor infinito relativo ya que son desconocidas al principio, exceptuando la de x que se debe colocar en 0 debido a que la distancia de x a x sería 0.

2. Sea a = x (tomamos a como nodo actual).3. Recorremos todos los nodos adyacentes de a, excepto los nodos marcados, llamaremos a

estos vi.4. Si la distancia desde x hasta vi guardada en D es mayor que la distancia desde x hasta a

sumada a la distancia desde a hasta vi; esta se sustituye con la segunda nombrada, esto es:si (Di > Da + d(a, vi)) entonces Di = Da + d(a, vi)

5. Marcamos como completo el nodo a.6. Tomamos como próximo nodo actual el de menor valor en D (puede hacerse almacenando los

valores en una cola de prioridad) y volvemos al paso 3 mientras existan nodos no marcados.

Una vez terminado al algoritmo, D estará completamente lleno.

Complejidad

Orden de complejidad del algoritmo: O(|V|2+|E|) = O(|V|2) sin utilizar cola de prioridad, O((|E|+|V|) log |V|) utilizando cola de prioridad (por ejemplo un montículo).

Podemos estimar la complejidad computacional del algoritmo de Dijkstra (en términos de sumas y comparaciones). El algoritmo realiza a lo más n-1 iteraciones, ya que en cada iteración se añade un vértice al conjunto distinguido. Para estimar el número total de operaciones basta estimar el número de operaciones que se llevan a cabo en cada iteración. Podemos identificar el vértice con la menor etiqueta entre los que no están en Sk realizando n-1 comparaciones o menos. Después hacemos una suma y una comparación para actualizar la etiqueta de cada uno de los vértices que no están en Sk. Por tanto, en cada iteración se realizan a lo sumo 2(n-1) operaciones, ya que no puede haber más de

Page 18: Apuntes Unidad I Grafos PTR

n-1 etiquetas por actualizar en cada iteración. Como no se realizan más de n-1 iteraciones, cada una de las cuales supone a lo más 2(n-1) operaciones, llegamos al siguiente teorema.

TEOREMA: El Algoritmo de Dijkstra realiza O(n2) operaciones (sumas y comparaciones) para determinar la longitud del camino más corto entre dos vértices de un grafo ponderado simple, conexo y no dirigido con n vértices.

Pseudocódigo

Estructura de datos auxiliar: Q = Estructura de datos Cola de prioridad (se puede implementar con un montículo)

DIJKSTRA (Grafo G, nodo_fuente s) para u ∈ V[G] hacer distancia[u] = INFINITO padre[u] = NULL distancia[s] = 0 Encolar (cola, grafo) mientras que cola no es vacía hacer u = extraer_minimo(cola) para v ∈ adyacencia[u] hacer si distancia[v] > distancia[u] + peso (u, v) hacer distancia[v] = distancia[u] + peso (u, v) padre[v] = u

Otra versión en pseudocódigo sin cola de prioridad

función Dijkstra (Grafo G, nodo_salida s) //Usaremos un vector para guardar las distancias del nodo salida al resto entero distancia[n] //Inicializamos el vector con distancias iniciales booleano visto[n] //vector de boleanos para controlar los vertices de los que ya tenemos la distancia mínima para cada w ∈ V[G] hacer Si (no existe arista entre s y w) entonces distancia[w] = Infinito //puedes marcar la casilla con un -1 por ejemplo Si_no distancia[w] = peso (s, w) fin si fin para distancia[s] = 0 visto[s] = cierto //n es el número de vertices que tiene el Grafo mientras que (no_esten_vistos_todos) hacer vertice = coger_el_minimo_del_vector distancia y que no este visto;

Page 19: Apuntes Unidad I Grafos PTR

visto[vertice] = cierto; para cada w ∈ sucesores (G, vertice) hacer si distancia[w]>distancia[vertice]+peso (vertice, w) entonces distancia[w] = distancia[vertice]+peso (vertice, w) fin si fin para fin mientrasfin función

Al final tenemos en el vector distancia en cada posición la distancia mínima del vertice salida a otro vertice cualquiera.

Implementación

C++

#include <cmath>#include <cstring>#include <iostream>using namespace std; int destino, origen, vertices = 0;int *costos = NULL; void dijkstra(int vertices, int origen, int destino, int *costos) { int i, v, cont = 0; int *ant, *tmp; int *z; /* vertices para los cuales se conoce el camino minimo */ double min; double *dist = new double[vertices]; /* vector con los costos de dos caminos */ /* aloca las lineas de la matriz */ ant = new int[vertices]; tmp = new int[vertices]; z = new int[vertices]; for (i = 0; i < vertices; i++) { if (costos[(origen - 1) * vertices + i] !=- 1) { ant[i] = origen - 1; dist[i] = costos[(origen-1)*vertices+i]; } else { ant[i]= -1;

Page 20: Apuntes Unidad I Grafos PTR

dist[i] = HUGE_VAL; } z[i]=0; } z[origen-1] = 1; dist[origen-1] = 0; /* Bucle principal */ do { /* Encontrando el vertice que debe entrar en z */ min = HUGE_VAL; for (i=0;i<vertices;i++) if (!z[i]) if (dist[i]>=0 && dist[i]<min) { min=dist[i];v=i; } /* Calculando las distancias de los nodos vecinos de z */ if (min != HUGE_VAL && v != destino - 1) { z[v] = 1; for (i = 0; i < vertices; i++) if (!z[i]) { if (costos[v*vertices+i] != -1 && dist[v] + costos[v*vertices+i] < dist[i]) { dist[i] = dist[v] + costos[v*vertices+i]; ant[i] =v; } } } } while (v != destino - 1 && min != HUGE_VAL); /* Muestra el resultado de la búsqueda */ cout << "\tDe " << origen << " para "<<destino<<" \t"; if (min == HUGE_VAL) { cout <<"No Existe\n"; cout <<"\tCoste: \t- \n"; } else { i = destino; i = ant[i-1]; while (i != -1) { // printf("<-%d",i+1); tmp[cont] = i+1;

Page 21: Apuntes Unidad I Grafos PTR

cont++; i = ant[i]; } for (i = cont; i > 0; i--) { cout<< tmp[i-1]<<" -> "; } cout << destino; cout <<"\n\tCoste: " << dist[destino-1] <<"\n"; } delete (dist); delete (ant); delete (tmp); delete (z);} int menu(void) {

int opcion; cout <<" Implementacion del Algoritmo de Dijkstra\n"; cout <<" Menu:\n"; cout <<" >> 1. Crear el grafo\n >> 2. Determinar el menor camino del grafo\n >> 0. Salir del programa\n"; cout <<endl;

cout << " Opcion: ";cin>>opcion;while(opcion<0 || opcion>2){

cout<<" Opcion Invalida. Digitela nuevamente: ";cin>>opcion;

}return opcion;

} void add(void) { do { cout <<"\nIngrese el numero de vertices ( no minimo de 2 ): "; cin>>vertices; } while (vertices < 2 ); if (!costos) delete(costos);

Page 22: Apuntes Unidad I Grafos PTR

costos = new int[vertices * vertices]; for (int i = 0; i <= vertices * vertices; i++) costos[i] = -1; cout <<" Nº Vertices = "<< vertices<<endl; cout <<"Ahora unamos los vertices:\n" ; bool sigo=true; int origen; int destino; while (sigo){ cout << " Escoja el primer vertice de la arista: " <<endl; do{ cin >> origen; if (origen>vertices){ cout << " El numero del vertice debe ser menor de " << vertices<<endl; } }while(origen > vertices); cout << " Escoja el segundo vertice de la arista: " <<endl; do{ cin >> destino;

if (destino>vertices){ cout << " El numero de vertice debe ser menor de " << vertices<<endl; } }while(destino> vertices);

int peso=0;

cout <<" Peso: " <<endl; cin>>peso;

costos[(origen-1) * vertices + destino - 1] = peso;

costos[(destino-1) * vertices + origen - 1] = peso;

int seguir=1; cout << "Desea anadir otra arista? (0 - NO, 1 - SI, por defecto 1): " ; cin >>seguir;

Page 23: Apuntes Unidad I Grafos PTR

sigo = (seguir==1); } } void buscar(void) { int i, j; cout <<" Lista de los Menores Caminos en Grafo Dado: \n"; for (i = 1; i <= vertices; i++) { for (j = 1; j <= vertices; j++) dijkstra(vertices, i,j, costos); cout<<endl; } cout <<"<Presione ENTER para volver al menu principal. \n"; } int main(int argc, char **argv) { int opcion; do {

opcion = menu();switch(opcion) {

case 1:add();break;

case 2:buscar();break;

} } while (opcion!= 0);

delete(costos); cout<<"\nHasta la proxima...\n\n"; system("pause"); return 0;}

C++ un poco más simple con arreglos

Page 24: Apuntes Unidad I Grafos PTR

Otra posible implementación del algoritmo de Dijkstra incluye un arreglo para las distancias. Esto hace la implementación más simple, pero no es eficiente en uso de memoria y además la versión presentada está limitada en el número de nodos del grafo.

#include <iostream>#include <limits.h>#include <float.h>using namespace std; #define MAXNODOS 500 // tamaño máximo de la red#define INDEFINIDO -1 int N;float dist[MAXNODOS][MAXNODOS];float minimo[MAXNODOS];int anterior[MAXNODOS];bool visitado[MAXNODOS]; bool minimoNodoNoVisitado (int * nodo){

int candidato = -1;for (int n = 0; n < N; n++) {

if (!visitado[n] && (minimo[n] < minimo[candidato] || candidato == -1)) {candidato = n;

}}*nodo = candidato;return candidato != -1;

} void dijkstra (int origen, int destino) {

minimo[origen] = 0.0;int nodo;

while (minimoNodoNoVisitado(&nodo)) {

if (minimo[nodo] == FLT_MAX)return; // otros vértices son

inaccesiblesvisitado[nodo] = true;for (int n = 0; n < N; n++) {

if (!visitado[n] && distancia[nodo][n] < 1.0) {float posibleMin = minimo[nodo] + distancia[nodo][n];if (posibleMin < minimo[n]) {

Page 25: Apuntes Unidad I Grafos PTR

minimo[n] = posibleMin;anterior[n] = nodo;

}}

}}

} void camino_minimo (int inicio, int final){

if (inicio != final) {camino_minimo (inicio, anterior[final]);cout << ", ";

}cout << final;

} int main(int argc, char **argv){

int M, inicio, final;

// inicializar matriz de distancias y camino minimocin >> N;for (int k = 0; k < N; k++) {

anterior[k] = INDEFINIDO;visitado[k] = false;minimo[k] = MAXINT;for (int l = 0; l < N; l++) {

camino[k][l] = FLT_MAX;}

}

// leer distancias en arcos realescin >> M;for (int k = 0; k < M; k++) {

int i, j;cin >> i >> j;cin >> prob[i][j];

}

cin >> inicio >> final;

dijkstra(inicio, final);

Page 26: Apuntes Unidad I Grafos PTR

camino_minimo(inicio, final);

return 0;}

C++ mediante Heaps

Otra posible implementación del algoritmo de Dijkstra es mediante montículos binarios.

struct T_Heap{ A monticulo; int num_elem;}; void CrearHeap(T_Heap& heap){ heap.num_elem= 0; for (int i=0;i<MAX_HEAP;i++){ heap.monticulo[i]= NULL; }//for}void Intercambiar(T_Heap& heap, int i, int j){ T_Lista aux; aux= heap.monticulo[i]; heap.monticulo[i]= heap.monticulo[j]; heap.monticulo[j]= aux;}void Meter(T_Heap& heap, const T_Lista& elem){ int k; k= heap.num_elem; heap.monticulo[k]= elem; while(k != 0 || (heap.monticulo[k]->peso > heap.monticulo[((k-1)/ 2)]->peso){ Intercambiar(heap,k,((k-1)/2)); k= (k-1)/2; }//while heap.num_elem++;}void Sacar(T_Heap& heap, int& elem){ int k; elem= heap.monticulo[0]; heap.monticulo[0]= heap.monticulo[heap.num_elem-1]; heap.monticulo[heap.num_elem-1]= NULL;

Page 27: Apuntes Unidad I Grafos PTR

heap.num_elem--; k= 0; while(k<heap.num_elem && (heap.monticulo[k]->peso < heap.monticulo[2*k+1]->peso || heap.monticulo[k]->peso < heap.monticulo[2*k+2]->peso)){ if (heap.monticulo[k] < heap.monticulo[2*k+1]){ Intercambiar(heap,k,2*k+1); k= 2*k+1; }else{ Intercambiar(heap,k,2*k+2); k= 2*k+2; }//if }//while}bool HeapLleno(const T_Heap& heap){ return(heap.num_elem== MAX_HEAP);}bool HeapVacio(const T_Heap& heap){ return(heap.num_elem== 0);}void DestruirHeap(T_Heap& heap){ for (int i=0;i<MAX_HEAP;i++){ heap.monticulo[i]= NULL; }//for heap.num_elem= 0;}

Esta es una implementación del algoritmo de Dijkstra mediante montículos binarios, que es capaz de dar los mejores resultados para que el algoritmo de Johnson sea más eficiente. La implementación del algoritmo devuelve un array de elementos precedentes y otro de distancias, mediante el primero se puede seguir el camino de menor coste desde el nodo pasado como argumento a cualquier otro nodo del grafo, y si paralelamente vamos sumando las distancias del otro array, obtenemos el coste total de dichos caminos mínimos.

void Dijkstra(const T_Grafo& grafo, int origen, T_Vector& distancias, T_Vector& previos){ T_Vector marcados; T_Heap colap; T_Lista aux; InicializarVector(distancias); // inicializa los elementos a -1 InicializarVector(previos); InicializarVector(marcados); distancias[origen]= 0; marcados[origen]= 0;

Page 28: Apuntes Unidad I Grafos PTR

CrearHeap(colap); MeterAdyacentes(colap, grafo, origen, marcados); while (!HeapVacio(colap){ aux = Sacar(colap); marcados[aux->origen]= 0; MeterAdyacentes(colap, grafo, aux->origen, marcados); while (aux != NULL){ if (distancias[aux->destino] > (distancias[aux->origen] + aux->peso)){ distancias[aux->destino]= distancias[aux->origen] + aux->peso; padre[aux->destino] = aux->origen; }//if aux= aux->sig; }//while }//while}

1.5 REPRESENTACIÓN DE GRÁFICAS

Anteriormente analizamos las gráficas como un dibujo, pero cuando se requiere analizar una grafica mediante una computadora se necesita de una representación mas formal.

Conoceremos dos formas:

a) MATRIZ DE ADYACENCIA

Para formar la matriz, primero elegimos los vértices siguiendo un orden cualquiera. A continuación etiquetamos los renglones y las columnas de una matriz con los vértices ordenados. La entrada en esta matriz es 1 si los vértices del renglón y la columna son adyacentes y 0 en caso contrario.

Llamamos vértices adyacentes a aquellos vértices que están formados por la misma arista. Llamamos aristas adyacentes a aquellas aristas que convergen en un mismo vértice.

Page 29: Apuntes Unidad I Grafos PTR

De la gráfica que se muestra podemos decir que a y b son vértices adyacentes puesto que comparten la misma arista, así, también a y d. Las aristas adyacentes que convergen en el vértice a son: la que une a d con a y la que une a b con a. Los invito a analizar los demás vértices y aristas de la gráfica.

En la matriz de la grafica expuesta se pueden detectar algunas propiedades de la gráfica simple:

El grado de un vértice

δ(c) = 3 en la fila de c hay tres unos

δ (c) = 3 en la de b hay tres unos

δ (c) = 3 en la de e hay tres unos

- La matriz permite representar lazos

- No permite representar lados paralelos

Al elevar al cuadrado la matriz A obtenemos A2 que determina los caminos de longitud (2) que puede trazarse de cada vértice.

Al elevar al cuadrado (A2) obtenemos A4 que determina los caminos de longitud 4 que hay de cada vértice.

Page 30: Apuntes Unidad I Grafos PTR

Ejemplos

Los caminos de a - a de longitud 2 son: (2) (a, b, a),(a, d, a)

Los camino de b - d de longitud 2 son (2): (b, c, d),(b, a, d)

Los caminos de c - c de longitud 2 son (3): (c, b, c),(c, d, c),(c, e, c)

Los caminos de d - e de longitud 4 son 6: (d, a, d, c, e), (d, c, d, c, e), (d, a, b, c, e), (d, c, e, c, e), (d, c, e, b,

e), (d, c, b, c, e)

Los caminos de c - d de longitud 4 son 3: (c, e, b, a, d), (c, b, e, c, d), (c, e, b, c, d)

Le sugerimos que busque y encuentre algunos otros caminos:

a - b longitud 4 (3)

Page 31: Apuntes Unidad I Grafos PTR

d - e longitud 4 (6)

b) MATRIZ DE INCIDENCIA

Los elementos de esta matriz están dados por la incidencia entre un vértice y un lado. Cuando existe esta incidencia se adopta 1, caso contrario 0.

Propiedades:

- Permite representar lazos y lados paralelos

- La columna con único valor diferente de 0 es un lazo

- Las columnas que no son lazos deben tener (2) unos

- La valencia de un vértice es igual a la suma de la fila

1.6 ISOMORFISMO DE GRÁFICAS

Las graficas G1 y G2 son isomorfas si existe una función f uno a uno y sobre de los vértices de G1 a los vértices de G2 y una función g uno a uno y sobre de las aristas de G1 a las aristas de G2, de manera

Page 32: Apuntes Unidad I Grafos PTR

que una arista e es incidente en v y w en G1 si y solo si la arista g (e) es incidente en f (v) y f (w) en G2. El par de funciones f y g reciben el nombre de isomorfismo de G1 en G2.

El isomorfismo para las graficas G1 y G2 se define por:

f(a) = A, f(b) = B, f(c) = C, f(d) = D, f(e) = E

Dos graficas simples G1 y G2 son isomorfas si y solo si para cierto orden de sus vértices las matrices de adyacencia son iguales.

La matriz de adyacencia de la grafica G1 es la que se presenta a continuación.

La matriz de adyacencia de la grafica G2 es la que se presenta a continuación.

Page 33: Apuntes Unidad I Grafos PTR

De nuevo observamos que las gráficas G1 y G2 son isomorfas.

1.7 GRÁFICAS PLANAS

Una gráfica es plana si se puede dibujar en el plano sin que sus aristas se crucen.

Ejemplo:

Determiwne si la grafica es plana. Si lo es, dibújela de nuevo sin que se crucen las aristas.

Luego de analizar detenidamente la grafica observamos que si la podemos trazar sin que se crucen las aristas, quedando asi:

Ahora vamos a aplicar la fórmula para hacer la verificación correspondiente.

Gráfica plana conexa con f = 7 caras (A, B, C, D, E), e = 10 aristas y v = 5 vértices; f = e – v + 2

Entonces:

f = e – v + 2

7 = 10 – 5 + 2

7 = 7 Por lo tanto la grafica es plana.

Page 34: Apuntes Unidad I Grafos PTR

Además, es necesario conocer como demostrar que ciertas gráficas no son planas:

Se dice que una gráfica es plana si se puede dibujar en el plano sin que sus aristas se crucen, si y solo si No contiene una subgráfica homeomorfa a K5 o K3,3 (teorema de Kuratwski).

Dos graficas G1 y G2 son homeomorfas si G1 y G2 se pueden reducir a graficas isomorfas realizando varias reducciones en serie.

Existe isomorfismo de graficas cuando las figuras G1 y G2 definen las mismas graficas aunque parezcan diferentes. Entonces se puede decir que para que exista isomorfismo se debe tener el mismo número de vértices, de aristas, además, de que el grado de 2, luego de realizar una reducción de serie.

La reducción en serie se da cuando en una grafica G las aristas (v, v1) y (v, v2) están en serie, y al hacer reducción en serie desaparece v y solo queda v1, v2.

Apóyese del ejemplo planteado en la página 361 del texto base, para su mejor comprensión, utilizando el teorema de Kuratowski.

1.8 ALGORITMOS PARA USO GRAFOS:

Algoritmos importantes

Algoritmo de búsqueda en anchura (BFS) Algoritmo de búsqueda en profundidad (DFS) Algoritmo de búsqueda A* Algoritmo del vecino más cercano Ordenación topológica de un grafo Algoritmo de cálculo de los componentes fuertemente conexos de un grafo Algoritmo de Dijkstra Algoritmo de Bellman-Ford Algoritmo de Prim Algoritmo de Ford-Fulkerson Algoritmo de Kruskal Algoritmo de Floyd-Warshall

Aplicaciones

Page 35: Apuntes Unidad I Grafos PTR

Gracias a la teoría de grafos se pueden resolver diversos problemas como por ejemplo la síntesis de circuitos secuenciales, contadores o sistemas de apertura. Se utiliza para diferentes áreas por ejemplo, Dibujo computacional, en todas las áreas de Ingeniería.

Los grafos se utilizan también para modelar trayectos como el de una línea de autobús a través de las calles de una ciudad, en el que podemos obtener caminos óptimos para el trayecto aplicando diversos algoritmos como puede ser el algoritmo de Floyd.

Para la administración de proyectos, utilizamos técnicas como PERT en las que se modelan los mismos utilizando grafos y optimizando los tiempos para concretar los mismos.

La teoría de grafos también ha servido de inspiración para las ciencias sociales, en especial para desarrollar un concepto no metafórico de red social que sustituye los nodos por los actores sociales y verifica la posición, centralidad e importancia de cada actor dentro de la red. Esta medida permite cuantificar y abstraer relaciones complejas, de manera que la estructura social puede representarse gráficamente. Por ejemplo, una red social puede representar la estructura de poder dentro de una sociedad al identificar los vínculos (aristas), su dirección e intensidad y da idea de la manera en que el poder se transmite y a quiénes.

Los grafos son importantes en el estudio de la biología y hábitat. El vértice representa un hábitat y las aristas (o "edges" en inglés) representa los senderos de los animales o las migraciónes. Con esta información, los científicos pueden entender cómo esto puede cambiar o afectar a las especies en su hábitat.

Grafo conexo

En teoría de grafos, un grafo G se dice conexo, si para cualquier par de vértices a y b en G, existe al menos una trayectoria (una sucesión de vértices adyacentes que no repita vértices) de a a b.

Definiciones Relacionadas

Un grafo dirigido tal que para cualesquiera dos vértices a y b existe un camino dirigido de ida y de regreso se dice grafo fuertemente conexo.

Un conjunto de corte de vértices U en un grafo G, es un conjunto de vértices de G, tal que G-U no es conexo o trivial. Similarmente, un conjunto de corte de aristas F es un conjunto de aristas tal que G-F no es conexo.

Solución Computacional

El problema computacional de determinar si un grafo es conexo, puede ser resuelto con algunos algoritmos como el MFMC (max-flow, min-cut).

Algoritmo

Page 36: Apuntes Unidad I Grafos PTR

Ejemplo de algoritmo iterativo implementado en C++ para determinar si un grafo es conexo utilizando Búsqueda_en_profundidad.

bool Graph::is_connected(){ if( _n <= 1 ) return true; vector<bool> visit(_n); vector<bool>::iterator iter; for(iter=visit.begin();iter!= visit.end();iter++) *iter=false; set<int> forvisit; set<int>::iterator actual; forvisit.insert(0); while( !forvisit.empty() ) { actual = (forvisit.begin()); if( visit[*actual] == false ) { for(int i=0;i<_n;i++) { if( _graph[*actual][i] == 1 && !visit[i]) forvisit.insert(i); } } visit[*actual]= true; forvisit.erase(actual); } bool result; for(iter=visit.begin();iter!= visit.end();iter++) result = result && *iter; return result;}

Donde _n es la cantidad de vértices y _graph denota la matrix de adyacencia.

Ordenación topológica

Una ordenación topológica de un grafo acíclico G dirigido es una ordenación lineal de todos los nodos de G que conserva la unión entre vértices del grafo G original. La condición que el grafo no

Page 37: Apuntes Unidad I Grafos PTR

contenga ciclos es importante, ya que no se puede obtener ordenación topológica de grafos que contengan ciclos.

Usualmente, para clarificar el concepto se suelen identificar los nodos con tareas a realizar en la que hay una precedencia a la hora de ejecutar dichas tareas. La ordenación topológica por tanto es una lista en orden lineal en que deben realizarse las tareas.

Para poder encontrar la ordenación topológica del grafo G deberemos aplicar una modificación del algoritmo de búsqueda en profundidad (DFS).

Algoritmos

Los algoritmos usuales para el ordenamiento topológico tienen un tiempo de ejecución de la cantidad de nodos más la cantidad de aristas (O(|V|+|E|)).

Uno de los algoritmos, primero descrito por Kahn (1962 ), trabaja eligiendo los vértices del mismo orden como un eventual orden topológico. Primero, busca la lista de los "nodos iniciales" que no tienen arcos entrantes y los inserta en un conjunto S; donde al menos uno de esos nodos existe si el grafo es acíclico. Entonces:

L ← Lista vacía que contendrá luego los elementos ordenados.S ← Conjunto de todos los nodos sin aristas entrantes.

MIENTRAS [S no es vacío]:n ← nodo extraído de Sinsertar n en L

PARA CADA [nodo m con arista e de n a m]:e ← arista extraída del grafo

SI [m no tiene más aristas entrantes]:insertar m en S

SI [el grafo tiene más aristas]:error: el grafo tiene al menos un ciclo

SINO:RETORNAR L

Si respeta la definición de GAD, ésta es una solución posible, listada en L (no es la única solución). De lo contrario el grafo contiene al menos un ciclo y por lo tanto un ordenamiento topológico es imposible.

Ha de tenerse en cuenta que, debido a la falta de unicidad del orden resultante, la estructura S puede ser simplemente un conjunto, una cola o una pila.

Page 38: Apuntes Unidad I Grafos PTR

Dependiendo del orden que los nodos "n" son extraídos del conjunto S, hay una diferente posible solución.

Una alternativa al algoritmo visto para ordenamiento topológico está basado en DFS (del inglés búsqueda en profundidad). Para este algoritmo, las aristas están en dirección contraria al algoritmo anterior (y en dirección contraria a lo que muestra el diagrama del ejemplo). Hay un arco desde x a y si la tarea x depende de la tarea y (en otras palabras, si la tarea y debe completarse antes que la tarea x empiece). El algoritmo se repite a través de cada nodo del grafo, en un orden arbitrario, iniciando una búsqueda en profundidad que termina cuando llega a un nodo que ya ha sido visitado desde el comienzo del orden topológico.

La ordenación topológica no es única. Depende en qué orden recorras los nodos del grafo en el bucle for de la función ORDENACIÓN_TOPOLÓGICA. La nomenclatura adicional utilizada es: lista = Estructura de datos lista enlazada

ORDENACIÓN_TOPOLÓGICA(grafo G) for each vertice u ∈ V[G]do estado[u] = NO_VISITADO padre[u] = NULL tiempo =0 for each vertice u ∈ V[G]do if estado[u] = NO_VISITADO then TOPOLÓGICO-Visitar(u) TOPOLÓGICO-Visitar(nodo u) estado[u]=VISITADO tiempo = tiempo+1 distancia[u] = tiempo for each v ∈ Adyacencia[u] do if estado[v]=NO_VISITADO then padre[v]=u TOPOLÓGICO-Visitar(v) estado[u] = TERMINADO tiempo = tiempo+1 finalización[u] = tiempo insertar (lista, u)

Al final de la ejecución del algoritmo se devuelve la lista enlazada de nodos, que corresponde con la ordenación topológica del grafo .

Ejemplos

En rojo se muestran los siguientes tiempos: distancia[u] / finalización[u]

1. Ejecutamos el algoritmo ORDENACIÓN_TOPOLÓGICA (grafo G) sobre el siguiente grafo.

Page 39: Apuntes Unidad I Grafos PTR

2. El algoritmo nos devuelve una lista enlazada con los nodos del grafo en orden decreciente en tiempo de finalización.

Grafo ordenado topológicamente. En él se pueden ver claramente las precedencias de las tareas:

Ponerse la camisa antes que el cinturón y el jersey Ponerse el pantalón antes que los zapatos y el cinturón Ponerse los calcetines antes que los zapatos

La aplicación canónica del orden topológico es en programación, una secuencia de tareas; los algoritmos de ordenamiento topológico fueron estudiados por primera vez a los comienzos de los años 60 en el contexto de la técnica "PERT" (Técnica de Revisión y Evaluación de Programas del inglés) de programación en gestión de proyectos ((Jarnagin, ,1960)). Las tareas están representados por vértices, y hay un arco (o arista) desde x a y si la tarea x debe completarse antes que la tarea y comience (por ejemplo, cuando se lava la ropa, la lavadora debe terminar antes de ponerla a secar). Entonces, un orden topológico brinda un orden para ejecutar las tareas.

Page 40: Apuntes Unidad I Grafos PTR

El gráfico muestra que hay muchos tipos de ordenamiento posibles:

7, 5, 3, 11, 8, 2, 9, 10 (visto de izquierda a derecha, de arriba a abajo)

3, 5, 7, 8, 11, 2, 9, 10 (por números menores primero) 3, 7, 8, 5, 11, 10, 2, 9 5, 7, 3, 8, 11, 10, 9, 2 (por menor cantidad de aristas primero) 7, 5, 11, 3, 10, 8, 9, 2 (por números mayores primero) 7, 5, 11, 2, 3, 8, 9, 10

Componente fuertemente conexo

Un grafo dirigido, y sus componentes fuertemente conexos.

En la Teoría de los grafos, un grafo dirigido es llamado fuertemente conexo si para cada par de vértices u y v existe un camino de u hacia v y un camino de v hacia u. Los componentes fuertemente conexos (CFC) de un grafo dirigido son sus subgrafos máximos fuertemente conexos. Estos subgrafos forman una partición del grafo.

Un subgrafo fuertemente conexo es máximo si contiene todos los vértices del grafo o si al agregarle un vértice cualquiera deja de ser fuertemente conexo.

El cálculo de los componentes fuertemente conexos de un grafo es uno de los problemas fundamentales de la Teoría de los grafos. El primer algoritmo que trabaja en tiempo lineal para resolver este problema fue propuesto por Robert Tarjan 1 en 1970 a base de una búsqueda en profundidad (depth-first search). Otros algoritmos aparecen en los principales textos sobre algorítmica.2 3

La complejidad de este algoritmo es O(V+E).

Algoritmo

Sea un grafo dirigido:

1. Aplicar búsqueda en profundidad sobre G2. Calcular el grafo traspuesto.

Page 41: Apuntes Unidad I Grafos PTR

3. Aplicar búsqueda en profundidad sobre Gt (el grafo traspuesto) iniciando la búsqueda en los nodos de mayor a menor tiempo de finalización obtenidos en la primera ejecución de búsqueda en profundidad (paso 1)

4. El resultado será un bosque de árboles. Cada árbol es una componente fuertemente conexa.

Las dos búsquedas en profundidad y la construcción del grafo reverso consumen tiempo lineal, de manera que el tiempo total es también lineal. En 2002, se publicó4 una prueba de corrección de este algorítmo.

Búsqueda en anchura

En Ciencias de la Computación, Búsqueda en anchura (en inglés BFS - Breadth First Search) es un algoritmo para recorrer o buscar elementos en un grafo (usado frecuentemente sobre árboles). Intuitivamente, se comienza en la raíz (eligiendo algún nodo como elemento raíz en el caso de un grafo) y se exploran todos los vecinos de este nodo. A continuación para cada uno de los vecinos se exploran sus respectivos vecinos adyacentes, y así hasta que se recorra todo el árbol.

Formalmente, BFS es un algoritmo de búsqueda sin información, que expande y examina todos los nodos de un árbol sistemáticamente para buscar una solución. El algoritmo no usa ninguna estrategia heurística.

Si las aristas tienen pesos negativos aplicaremos el algoritmo de Bellman-Ford en alguna de sus dos versiones.

Procedimiento

Dado un vértice fuente s, Breadth-first search sistemáticamente explora los vértices de G para “descubrir” todos los vértices alcanzables desde s.

Calcula la distancia (menor número de vértices) desde s a todos los vértices alcanzables.

Después produce un árbol BF con raíz en s y que contiene a todos los vértices alcanzables.

El camino desde s a cada vértice en este recorrido contiene el mínimo número de vértices. Es el camino más corto medido en número de vértices.

Su nombre se debe a que expande uniformemente la frontera entre lo descubierto y lo no descubierto. Llega a los nodos de distancia k, sólo tras haber llegado a todos los nodos a distancia k-1.

Pseudocódigo

La nomenclatura adicional utilizada es: Q = Estructura de datos cola

Page 42: Apuntes Unidad I Grafos PTR

BFS(grafo G, nodo_fuente s) { // recorremos todos los vértices del grafo inicializándolos a NO_VISITADO, // distancia INFINITA y padre de cada nodo NULL for u ∈ V[G] do { estado[u] = NO_VISITADO; distancia[u] = INFINITO; /* distancia infinita si el nodo no es alcanzable */ padre[u] = NULL; } estado[s] = VISITADO; distancia[s] = 0; Encolar(Q, s); while !vacia(Q) do { // extraemos el nodo u de la cola Q y exploramos todos sus nodos adyacentes u = extraer(Q); for v ∈ adyacencia[u] do { if estado[v] == NO_VISITADO then { estado[v] = VISITADO; distancia[v] = distancia[u] + 1; padre[v] = u; Encolar(Q, v); } } } } *Falta recorrer vertices no adyacentes directa o indirectamente al vertice origen "s",pues cola queda vacia sin los adyacentes restantes.

El tiempo de ejecución es O(|V|+|E|). Nótese que cada nodo es puesto a la cola una vez y su lista de adyacencia es recorrida una vez también.

Anexo:Ejemplo de Algoritmo de Bellman - Ford

Page 43: Apuntes Unidad I Grafos PTR

Grafo inicial

Camino mínimo final de todos los nodos al primero

En este artículo se mostrará un ejemplo del Algoritmo de Bellman-Ford. Para ello se mostrará la siguiente tabla y a partir de esta se explicará el procedimiento para hallar el camino mínimo de todos los vértices a un único vértice destino.

EjemploGrafo inicial y Tabla de relaciones

En este ejemplo partimos de este grafo, cuyas relaciones están expuestas a su derecha:

Page 44: Apuntes Unidad I Grafos PTR

Tabla de resolución final

En esta tabla se muestran las soluciones parciales que se han ido obteniendo a través de la realización del algoritmo.

Page 45: Apuntes Unidad I Grafos PTR

Explicación del algoritmo

En la tabla anterior donde queda desarrollado el algoritmo paso por paso, podemos apreciar que la resolución del algoritmo viene dada por aplicar las fórmulas que vienen escritas en el paso n, a cada paso. El objetivo del algoritmo es encontrar el camino mínimo desde todos los nodos al vértice 1. En las fórmulas donde viene D, es la distancia mínima desde el nodo que aparece en el subíndice al vértice destino, en este caso, el vértice 1.

En el paso 0, inicializamos todas las distancias mínimas a INFINITO. En el paso 1, actualizamos el paso anterior, aplicando las fórmulas. En este caso ponemos la

distancia de los nodos que tienen accesos directos al vértice 1 y se la sumamos a la distancia mínima acumulada que hay hasta el vértice oportuno. Aquí esta distancia acumulada sería 0 para 1, debido a que sería la distancia a él mismo, e infinito para el resto porque no han sido analizados todavía.

En el paso 2, al saber ya una distancia mínima acumulada desde los nodos 2 y 3 hasta 1, podemos actualizar las distancias mínimas de los nodos 4 y 5.

En los pasos sucesivos, se van actualizando las distancias mínimas acumuladas (D) de los distintos vértices hasta 1, y se van utilizando en los pasos siguientes para optimizar el camino mínimo. El final del algoritmo se da cuando no hay ningún cambio de un paso a otro, es decir, cuando ya no se puede encontrar un camino más corto.

Algoritmo de Bellman-Ford

El algoritmo de Bellman-Ford (algoritmo de Bell-End-Ford), genera el camino más corto en un Grafo dirigido ponderado (en el que el peso de alguna de las aristas puede ser negativo). El algoritmo de Dijkstra resuelve este mismo problema en un tiempo menor, pero requiere que los pesos de las aristas no sean negativos. Por lo que el Algoritmo Bellman-Ford normalmente se utiliza cuando hay aristas con peso negativo. Este algoritmo fue desarrollado por Richard Bellman, Samuel End y Lester Ford.

Según Robert Sedgewick, “Los pesos negativos no son simplemente una curiosidad matemática; […] surgen de una forma natural en la reducción a problemas de caminos más cortos”, y son un ejemplo de una reducción del problema del camino hamiltoniano que es NP-completo hasta el problema de caminos más cortos con pesos generales. Si un grafo contiene un ciclo de coste total negativo entonces este grafo no tiene solución. El algoritmo es capaz de detectar este caso.

Si el grafo contiene un ciclo de coste negativo, el algoritmo lo detectará, pero no encontrará el camino más corto que no repite ningún vértice. La complejidad de este problema es al menos la del problema del camino más largo de complejidad NP-Completo.

Page 46: Apuntes Unidad I Grafos PTR

Algoritmo

El Algoritmo de Bellman-Ford es, en su estructura básica, muy parecido al algoritmo de Dijkstra, pero en vez de seleccionar vorazmente el nodo de peso mínimo aun sin procesar para relajarlo, simplemente relaja todas las aristas, y lo hace |V|-1 veces, siendo |V| el número de vértices en el grafo. Las repeticiones permiten a las distancias mínimas recorrer el árbol, ya que en la ausencia de ciclos negativos, el camino más corto solo visita cada vértice una vez. A diferencia de la solución voraz, la cual depende de la suposición de que los pesos sean positivos, esta solución se aproxima más al caso general.

Existen dos versiones:

Versión no optimizada para grafos con ciclos negativos, cuyo coste de tiempo es O(VE) Versión optimizada para grafos con aristas de peso negativo, pero en el grafo no existen ciclos

de coste negativo, cuyo coste de tiempo, es también O(VE).

BellmanFord(Grafo G, nodo_origen s) // inicializamos el grafo. Ponemos distancias a INFINITO menos el nodo origen que // tiene distancia 0 for v ∈ V[G] do distancia[v]=INFINITO predecesor[v]=NIL distancia[s]=0 // relajamos cada arista del grafo tantas veces como número de nodos -1 haya en el grafo for i=1 to |V[G]-1| do for (u, v) ∈ E[G] do if distancia[v]>distancia[u] + peso(u, v) then distancia[v] = distancia[u] + peso (u, v) predecesor[v] = u // comprobamos si hay ciclos negativo for (u, v) ∈ E[G] do if distancia[v] > distancia[u] + peso(u, v) then print ("Hay ciclo negativo") return FALSE return TRUE BellmanFord_Optimizado(Grafo G, nodo_origen s) // inicializamos el grafo. Ponemos distancias a INFINITO menos el nodo origen que // tiene distancia 0. Para ello lo hacemos recorriéndonos todos los vértices del grafo for v ∈ V[G] do distancia[v]=INFINITO padre[v]=NIL distancia[s]=0

Page 47: Apuntes Unidad I Grafos PTR

encolar(s, Q) en_cola[s]=TRUE mientras Q!=0 then u = extraer(Q) en_cola[u]=FALSE // relajamos las aristas for v ∈ ady[u] do if distancia[v]>distancia[u] + peso(u, v) then distancia[v] = distancia[u] + peso (u, v) padre[v] = u if en_cola[v]==FALSE then encolar(v, Q) en_cola[v]=TRUE

Demostración de la corrección del algoritmo

La corrección del algoritmo se puede demostrar por inducción. La demostración es la siguiente:

Lema. Tras i repeticiones del bucle for:

Si distancia(u) no es infinita, entonces es igual a la longitud de algún camino de s hasta u. Si hay un camino desde s hasta u con al menos i aristas, entonces distancia(u) es a lo sumo la

longitud del camino más corto desde s hasta u con i aristas como máximo.

Demostración. Para el caso base de la inducción, consideramos i=0 y justo antes el bucle for es ejecutado por primera vez. Luego, para el vértice origen distancia(origen) = 0, lo que es cierto. Para cualquier otro vértice u distancia(u) = INFINITO, lo cual es también correcto porque no hay camino desde origen hasta u con 0 aristas.

Para el caso inductivo, primero demostramos la primera parte. Considerando un momento en el que la distancia hasta el vértice es actualizada por distancia(u)= distancia(u)+peso(uv). Por la hipótesis de inducción, distancia(u) es la longitud de algún camino desde origen hasta u. Entonces distancia(u) + peso(uv) es la longitud del camino desde la origen hasta v que sigue el camino desde origen hasta u y luego va a v.

Para la segunda parte, tomamos el camino más corto desde origen hasta u con al menos i aristas. Dejamos a v ser el último vértice antes de u en su camino. Luego, la parte del camino desde origen hasta v es el camino más corto desde origen hasta v con al menos i-1 aristas. Por la hipótesis de inducción, distancia(v) después de i-1 iteraciones es a lo sumo la longitud de su camino. Entonces, peso(uv) + distancia(v) es a lo sumo la longitud del camino desde s hasta u. En el primer ciclo, distancia(u) es comparada con peso(uv) + distancia(v), y es igual a él si peso(uv) + distancia(v)era menor. Entonces, tras i iteraciones, distancia(u) es a lo sumo la longitud del camino más corto desde origen hasta u que usa al menos i aristas.

Page 48: Apuntes Unidad I Grafos PTR

Si no hay ciclos de peso negativo, entonces cada camino más corto visita cada vértice al menos una vez, así que al paso 3 no se le pueden añadir mejoras. Por el contrario, supongamos que no se puede hacer ningún cambio. Entonces para cualquier vértice v[0],..,v[k-1], Distancia(v[i]) <= Distancia(v[i-1 (mod k)]) + peso(v[i-1 (mod k)]v[i])

Simplificando, distancia(v[i]) y distancia(v[i-1 (mod k)]) se cancelan, dejando:

Es decir, cada ciclo tiene pesos no negativos

Aplicaciones de encaminamiento

Una variante distribuida del Algoritmo del Bellman-Ford se usa en protocolos de encaminamiento basados en vector de distancias, por ejemplo el Protocolo de encaminamiento de información (RIP). El algoritmo es distribuido porque envuelve una serie de nodos (routers) dentro de un Sistema autónomo(AS), un conjunto de redes y dispositivos router IP administrados típicamente por un Proveedor de Servicio de Internet (ISP). Se compone de los siguientes pasos:

1. Cada nodo calcula la distancia entre él mismo y todos los demás dentro de un AS y almacena esta información en una tabla.

2. Cada nodo envía su tabla a todos los nodos vecinos.3. Cuando un nodo recibe las tablas de distancias de sus vecinos, éste calcula la ruta más corta a

los demás nodos y actualiza su tabla para reflejar los cambios.

Las desventajas principales del algoritmo de Bellman-Ford en este ajuste son:

No escala bien Los cambios en la topología de red no se reflejan rápidamente ya que las actualizaciones se

distribuyen nodo por nodo. Contando hasta el infinito(si un fallo de enlace o nodo hace que un nodo sea inalcanzable

desde un conjunto de otros nodos, éstos pueden estar siempre aumentando gradualmente sus cálculos de distancia a él, y mientras tanto puede haber bucles de enrutamiento)

Mejoras

En 1970 Yen describió una mejora del algoritmo Bellman-Ford para un grafo sin ciclos con peso negativo. Esta mejora primero asigna un orden arbitrario lineal a todos los vértices y luego divide el conjunto de todas las aristas en uno o dos subconjuntos. El primer subconjunto, Ef, contiene todas las aristas (vi,vj) tales que i < j; mientras que el segundo, Eb, contiene aristas (vi,vj) tales que i > j. Cada vértice se visita en orden v1,v2,…,v|v|, relajando cada arista saliente de ese vértice en Ef. Cada vértice es, después, visitado en orden v|v|,v|v|,…,v1, relajando cada arista saliente de ese vértice en Eb. La mejora de Yen reduce a la mitad, de manera efectiva, el número de “pases” requeridos para la solución del camino más corto desde una única fuente.

Page 49: Apuntes Unidad I Grafos PTR

Búsqueda en profundidad

Una Búsqueda en profundidad (en inglés DFS o Depth First Search) es un algoritmo que permite recorrer todos los nodos de un grafo o árbol (teoría de grafos) de manera ordenada, pero no uniforme. Su funcionamiento consiste en ir expandiendo todos y cada uno de los nodos que va localizando, de forma recurrente, en un camino concreto. Cuando ya no quedan más nodos que visitar en dicho camino, regresa (Backtracking), de modo que repite el mismo proceso con cada uno de los hermanos del nodo ya procesado.

Análogamente existe el algoritmo de búsqueda en anchura (BFS o Breadth First Search).

Pseudocódigo

Pseudocódigo para grafos

DFS(grafo G) PARA CADA vertice u ∈ V[G] HACER estado[u] ← NO_VISITADO padre[u] ← NULO tiempo ← 0 PARA CADA vertice u ∈ V[G] HACER SI estado[u] = NO_VISITADO ENTONCES DFS_Visitar(u) DFS-Visitar(nodo u) estado[u] ← VISITADO tiempo ← tiempo + 1 d[u] ← tiempo PARA CADA v ∈ Vecinos[u] HACER SI estado[v] = NO_VISITADO ENTONCES padre[v] ← u DFS_Visitar(v) estado[u] ← TERMINADO tiempo ← tiempo + 1 f[u] ← tiempo

Arcos DF

Si en tiempo de descubrimiento de u tenemos el arco (u,v):

i. Si el estado de v es NO_VISITADO, entonces (u,v) ∈ DF,

Algoritmo de búsqueda A*

Page 50: Apuntes Unidad I Grafos PTR

Ejemplo de aplicación del algoritmo A*.

El algoritmo de búsqueda A* (A Asterisco) se clasifica dentro de los algoritmos de búsqueda en grafos. Presentado por primera vez en 1968 por Peter E. Hart, Nils J. Nilsson y Bertram Raphael, el algoritmo encuentra, siempre y cuando se cumplan unas determinadas condiciones, el camino de menor coste entre un nodo origen y uno objetivo.

Motivación y Descripción

El problema de algunos algoritmos de búsqueda en grafos informados, como puede ser el algoritmo voraz, es que se guían en exclusiva por la función heurística, la cual puede no indicar el camino de coste más bajo, o por el coste real de desplazarse de un nodo a otro (como los algoritmos de escalada), pudiéndose dar el caso de que sea necesario realizar un movimiento de coste mayor para alcanzar la solución. Es por ello bastante intuitivo el hecho de que un buen algoritmo de búsqueda informada debería tener en cuenta ambos factores, el valor heurístico de los nodos y el coste real del recorrido.

Así, el algoritmo A* utiliza una función de evaluación f(n) = g(n) + h'(n), donde h'(n) representa el valor heurístico del nodo a evaluar desde el actual, n, hasta el final, y g(n), el coste real del camino recorrido para llegar a dicho nodo, n. A* mantiene dos estructuras de datos auxiliares, que podemos denominar abiertos, implementado como una cola de prioridad (ordenada por el valor f(n) de cada nodo), y cerrados, donde se guarda la información de los nodos que ya han sido visitados. En cada paso del algoritmo, se expande el nodo que esté primero en abiertos, y en caso de que no sea un nodo objetivo, calcula la f(n) de todos sus hijos, los inserta en abiertos, y pasa el nodo evaluado a cerrados.

El algoritmo es una combinación entre búsquedas del tipo primero en anchura con primero en profundidad: mientras que h'(n) tiende a primero en profundidad, g(n) tiende a primero en anchura. De este modo, se cambia de camino de búsqueda cada vez que existen nodos más prometedores.

Propiedades

Como todo algoritmo de búsqueda en anchura, A* es un algoritmo completo: en caso de existir una solución, siempre dará con ella.

Page 51: Apuntes Unidad I Grafos PTR

Si para todo nodo n del grafo se cumple g(n) = 0, nos encontramos ante una búsqueda voraz. Si para todo nodo n del grafo se cumple h(n) = 0, A* pasa a ser una búsqueda de coste uniforme no informada.

Para garantizar la optimalidad del algoritmo, la función h(n) debe ser admisible, esto es, que no sobrestime el coste real de alcanzar el nodo objetivo.

De no cumplirse dicha condición, el algoritmo pasa a denominarse simplemente A, y a pesar de seguir siendo completo, no se asegura que el resultado obtenido sea el camino de coste mínimo. Asimismo, si garantizamos que h(n) es consistente (o monótona), es decir, que para cualquier nodo n y cualquiera de sus sucesores, el coste estimado de alcanzar el objetivo desde n no es mayor que el de alcanzar el sucesor más el coste de alcanzar el objetivo desde el sucesor.

Complejidad computacional

La complejidad computacional del algoritmo está íntimamente relacionada con la calidad de la heurística que se utilice en el problema. En el caso peor, con una heurística de pésima calidad, la complejidad será exponencial, mientras que en el caso mejor, con una buena h'(n), el algoritmo se ejecutará en tiempo lineal. Para que esto último suceda, se debe cumplir que

donde h* es una heurística óptima para el problema, como por ejemplo, el coste real de alcanzar el objetivo.

Complejidad en memoria

El espacio requerido por A* para ser ejecutado es su mayor problema. Dado que tiene que almacenar todos los posibles siguientes nodos de cada estado, la cantidad de memoria que requerirá será exponencial con respecto al tamaño del problema. Para solucionar este problema, se han propuesto diversas variaciones de este algoritmo, como pueden ser RTA*, IDA* o SMA*.

Implementación en pseudocódigo

Tratar punto

.:= . // coste del camino hasta .

caso . = . perteneciente a () si g(.) < g(.) entonces // (-----) // nos quedamos con el camino de menor coste .:= MEJORNODO actualizar g(.) y f'(.)

Page 52: Apuntes Unidad I Grafos PTR

propagar g a . de . eliminar . añadir . a ._MEJORNODOcaso . = . perteneciente a )-----( si g(.) < g(.) entonces // nos quedamos con el camino de menor coste .:= MEJORNODO actualizar g(.) y f'(.) eliminar . añadir . a ._MEJORNODOcaso . no estaba en ).( ni (.) añadir . a ).( añadir . a ._MEJORNODO f'(.) := g(.) + h'(.)

Observaciones

Como se mencionó anteriormente h'(x) es un estimador de h(x) que informa la distancia al nodo objetivo, entonces

Si h'(x) hace un estimación perfecta de h(x), A* converge inmediatamente al objetivo.

Si h'(x) = 0, la función g(x) controla la búsqueda.

Si h'(x) = 0 y g(x) =0 la búsqueda será aleatoria.

Si h'(x) = 0 y g(x) =1 o constante la búsqueda será Primero en Anchura.

Si h'(x) nunca sobrestima a h(x) (o subestima), se garantiza encontrar el camino optimo, pero se desperdicia esfuerzo explorando otras rutas que parecieron buenas.

Si h'(x) sobrestima a h(x), no puede garantizarse la consecución del camino del menor coste

Algoritmo de Prim

El algoritmo de Prim es un algoritmo perteneciente a la teoría de los grafos para encontrar un árbol recubridor mínimo en un grafo conexo, no dirigido y cuyas aristas están etiquetadas.

En otras palabras, el algoritmo encuentra un subconjunto de aristas que forman un árbol con todos los vértices, donde el peso total de todas las aristas en el árbol es el mínimo posible. Si el grafo no es conexo, entonces el algoritmo encontrará el árbol recubridor mínimo para uno de los componentes conexos que forman dicho grafo no conexo.

Page 53: Apuntes Unidad I Grafos PTR

El algoritmo fue diseñado en 1930 por el matemático Vojtech Jarnik y luego de manera independiente por el científico computacional Robert C. Prim en 1957 y redescubierto por Dijkstra en 1959. Por esta razón, el algoritmo es también conocido como algoritmo DJP o algoritmo de Jarnik.

Descripción conceptual

El algoritmo incrementa continuamente el tamaño de un árbol, comenzando por un vértice inicial al que se le van agregando sucesivamente vértices cuya distancia a los anteriores es mínima. Esto significa que en cada paso, las aristas a considerar son aquellas que inciden en vértices que ya pertenecen al árbol.

El árbol recubridor mínimo está completamente construido cuando no quedan más vértices por agregar.

Pseudocódigo del algoritmo

Estructura de datos auxiliar: Q = Estructura de datos Cola de prioridad (se puede implementar con un heap)

JARNIK (Grafo G, nodo_fuente s) // Inicializamos todos los nodos del grafo. La distancia la ponemos a infinito y el padre de cada nodo a NULL // Encolamos, en una cola de prioridad donde la prioridad es la distancia, todas las parejas <nodo,distancia> del grafo por cada u en V[G] hacer distancia[u] = INFINITO padre[u] = NULL Añadir(cola,<u,distancia[u]>) distancia[s]=0 mientras cola != 0 do // OJO: Se entiende por mayor prioridad aquel nodo cuya distancia[u] es menor. u = extraer_minimo(cola) //devuelve el minimo y lo elimina de la cola. por cada v adyacente a 'u' hacer si ((v ∈ cola) && (distancia[v] > peso(u, v)) entonces padre[v] = u distancia[v] = peso(u, v) Actualizar(cola,<v,distancia[v]>)

Código en JAVA

public class Algorithms{ public static Graph PrimsAlgorithm (Graph g, int s) {

Page 54: Apuntes Unidad I Grafos PTR

int n = g.getNumberOfVertices ();Entry[] table = new Entry [n];for (int v = 0; v < n; ++v) table [v] = new Entry ();table [s].distance = 0;PriorityQueue queue = new BinaryHeap (g.getNumberOfEdges());queue.enqueue ( new Association (new Int (0), g.getVertex (s)));while (!queue.isEmpty ()){ Association assoc = (Association) queue.dequeueMin(); Vertex v0 = (Vertex) assoc.getValue (); int n0 = v0.getNumber (); if (!table [n0].known) {

table [n0].known = true;Enumeration p = v0.getEmanatingEdges ();while (p.hasMoreElements ()){ Edge edge = (Edge) p.nextElement (); Vertex v1 = edge.getMate (v0); int n1 = v1.getNumber (); Int wt = (Int) edge.getWeight (); int d = wt.intValue (); if (!table[n1].known && table[n1].distance>d) { table [n1].distance = d;

table [n1].predecessor = n0;queue.enqueue ( new Association (new Int (d), v1));

}}

}}Graph result = new GraphAsLists (n);for (int v = 0; v < n; ++v) result.addVertex (v);for (int v = 0; v < n; ++v) if (v != s)

result.addEdge (v, table [v].predecessor);return result;

}}

Page 55: Apuntes Unidad I Grafos PTR

Demostración

Sea G un grafo conexo y ponderado.

En toda iteración del algoritmo de Prim, se debe encontrar una arista que conecte un nodo del subgrafo a otro nodo fuera del subgrafo.

Ya que G es conexo, siempre habrá un camino para todo nodo.

La salida Y del algoritmo de Prim es un árbol porque las aristas y los nodos agregados a Y están conectados.

Sea Y el árbol recubridor mínimo de G.

Si es el árbol recubridor mínimo.

Si no, sea e la primera arista agregada durante la construcción de Y, que no está en Y1 y sea V el conjunto de nodos conectados por las aristas agregadas antes que e. Entonces un extremo de e está en V y el otro no. Ya que Y1 es el árbol recubridor mínimo de G hay un camino en Y1 que une los dos extremos. Mientras que uno se mueve por el camino, se debe encontrar una arista f uniendo un nodo en V a uno que no está en V. En la iteración que e se agrega a Y, f también se podría haber agregado y se hubiese agregado en vez de e si su peso fuera menor que el de e. Ya que f no se agregó se concluye:

Sea Y2 el grafo obtenido al remover f y agregando . Es fácil mostrar que Y2 conexo tiene la misma cantidad de aristas que Y1, y el peso total de sus aristas no es mayor que el de Y1, entonces también es un árbol recubridor mínimo de G y contiene a e y todas las aristas agregadas anteriormente durante la construcción de V. Si se repiten los pasos mencionados anteriormente, eventualmente se obtendrá el árbol recubridor mínimo de G que es igual a Y.

Esto demuestra que Y es el árbol recubridor mínimo de G.

Ejemplo de ejecución del algoritmo

Image Descripción No visto

En el grafo

En el árbol

Page 56: Apuntes Unidad I Grafos PTR

Este es el grafo ponderado de partida. No es un árbol ya que requiere que no haya ciclos y en este grafo los hay. Los números cerca de las aristas indican el peso. Ninguna de las aristas está marcada, y el vértice D ha sido elegido arbitrariamente como el punto de partida.

C, G A, B, E, F D

El segundo vértice es el más cercano a D: A está a 5 de distancia, B a 9, E a 15 y F a 6. De estos, 5 es el valor más pequeño, así que marcamos la arista DA.

C, G B, E, F A, D

El próximo vértice a elegir es el más cercano a D o A. B está a 9 de distancia de D y a 7 de A, E está a 15, y F está a 6. 6 es el valor más pequeño, así que marcamos el vértice F y a la arista DF.

C B, E, G A, D, F

El algoritmo continua. El vértice B, que está a una distancia de 7 de A, es el siguiente marcado. En este punto la arista DB es marcada en rojo porque sus dos extremos ya están en el árbol y por lo tanto no podrá ser utilizado.

null C, E, G

A, D, F, B

Page 57: Apuntes Unidad I Grafos PTR

Aquí hay que elegir entre C, E y G. C está a 8 de distancia de B, E está a 7 de distancia de B, y G está a 11 de distancia de F. E está más cerca, entonces marcamos el vértice E y la arista EB. Otras dos aristas fueron marcadas en rojo porque ambos vértices que unen fueron agregados al árbol.

null C, G A, D, F, B, E

Sólo quedan disponibles C y G. C está a 5 de distancia de E, y G a 9 de distancia de E. Se elige C, y se marca con el arco EC. El arco BC también se marca con rojo.

null GA, D, F, B, E, C

G es el único vértice pendiente, y está más cerca de E que de F, así que se agrega EG al árbol. Todos los vértices están ya marcados, el árbol de expansión mínimo se muestra en verde. En este caso con un peso de 39.

null nullA, D, F, B, E, C, G

Árbol recubridor mínimo

Page 58: Apuntes Unidad I Grafos PTR

Un ejemplo de árbol recubridor mínimo. Cada punto representa un vértice, cada arista está etiquetada con su peso, que en este caso equivale a su longitud.

Dado un grafo conexo, un árbol recubridor mínimo de ese grafo es un subgrafo que tiene que ser un árbol y contener todos los vértices del grafo inicial. Cada arista tiene asignado un peso proporcional entre ellos, que es un número representativo de algún objeto, distancia, etc.. , y se usa para asignar un peso total al árbol recubridor mínimo computando la suma de todos los pesos de las aristas del árbol en cuestión. Un árbol recubridor mínimo o un árbol expandido mínimo es un árbol recubridor que pesa menos o igual que otros árboles recubridores. Todo grafo tiene un bósque recubridor mínimo.

Un ejemplo sería una compañía de cable trazando cable a una nueva vecindad. Si está limitada a trazar el cable por ciertos caminos, entonces se hará un grafo que represente los puntos conectados por esos caminos. Algunos de estos caminos podrán ser más caros que otros, por ser más largos. Estos caminos serían representados por las aristas con mayores pesos. Un árbol recubridor para este grafo sería un subconjunto de estos caminos que no tenga ciclos pero que mantenga conectadas todas las casas. Puede haber más de un árbol recubridor posible. El árbol recubridor mínimo será el de menos coste.

En el caso de un empate, porque podría haber más de un árbol recubridor mínimo; en particular, si todos los pesos son iguales, todo árbol recubridor será mínimo. De todas formas, si cada arista tiene un peso distinto existirá sólo un árbol recubridor mínimo. La demostración de esto es trivial y se puede hacer por inducción. Esto ocurre en muchas situaciones de la realidad, como con la compañía de cable en el ejemplo anterior, donde es extraño que dos caminos tengan exactamente el mismo coste. Esto también se generaliza para los bósques recubridores.

Si los pesos son positivos, el árbol recubridor mínimo es el subgrafo de menor costo posible conectando todos los vértices, ya que los subgrafos que contienen ciclos necesariamente tienen más peso total.

Algoritmo de Ford-Fulkerson

El algoritmo de Ford-Fulkerson propone buscar caminos en los que se pueda aumentar el flujo, hasta que se alcance el flujo máximo. Es aplicable a los Flujos maximales. La idea es encontrar una ruta de penetración con un flujo positivo neto que una los nodos origen y destino. Su nombre viene dado por sus creadores, L. R. Ford, Jr. y D. R. Fulkerson.

Introducción

Sea (V,A,w) con V vértices, A aristas y w peso de las aristas, una red con una única fuente s y un único sumidero t; w(α) es la capacidad de α perteneciente a la arista A. Un flujo f es viable si f(α) <= w(α) para todo α perteneciente a la arista A. Se trata de hallar un flujo viable con el valor máximo posible.

Page 59: Apuntes Unidad I Grafos PTR

En un red con fuente s y sumidero t único el valor máximo que puede tomar un flujo variable es igual a la capacidad mínima que puede tomar un corte.

Pseudocódigo

Ford-Fulkerson(G,s,t) { for (cada arco (u,v) de E) { f[u,v]= 0; f[v,u]= 0; } while (exista un camino p desde s a t en la red residual Gf) { cf(p) = min{cf(u,v): (u,v) está sobre p}; for (cada arco (u,v) en p) { f[u,v]= f[u,v] + cf(p); f[v,u]= - f[u,v]; } } }

Algoritmo de Kruskal

El algoritmo de Kruskal es un algoritmo de la teoría de grafos para encontrar un árbol recubridor mínimo en un grafo conexo y ponderado. Es decir, busca un subconjunto de aristas que, formando un árbol, incluyen todos los vértices y donde el valor total de todas las aristas del árbol es el mínimo. Si el grafo no es conexo, entonces busca un bosque expandido mínimo (un árbol expandido mínimo para cada componente conexa). El algoritmo de Kruskal es un ejemplo de algoritmo voraz.

Un ejemplo de árbol expandido mínimo. Cada punto representa un vértice, el cual puede ser un árbol por sí mismo. Se usa el Algoritmo para buscar las distancias más cortas (árbol expandido) que conectan todos los puntos o vértices.

Page 60: Apuntes Unidad I Grafos PTR

Funciona de la siguiente manera:

se crea un bosque B (un conjunto de árboles), donde cada vértice del grafo es un árbol separado

se crea un conjunto C que contenga a todas las aristas del grafo mientras C es no vacío

o eliminar una arista de peso mínimo de Co si esa arista conecta dos árboles diferentes se añade al bosque, combinando los dos

árboles en un solo árbolo en caso contrario, se desecha la arista

Al acabar el algoritmo, el bosque tiene un solo componente, el cual forma un árbol de expansión mínimo del grafo.

Este algoritmo fue publicado por primera vez en Proceedings of the American Mathematical Society, pp. 48–50 en 1956, y fue escrito por Joseph Kruskal.

Pseudocódigo

1 function Kruskal(G) 2 for each vertex v in G do 3 Define an elementary cluster C(v) ← {v}. 4 Initialize a priority queue Q to contain all edges in G, using the weights as keys. 5 Define a tree T ← Ø //T will ultimately contain the edges of the MST 6 // n es el número total de vértices 7 while T has fewer than n-1 edges do 8 // edge u, v is the minimum weighted route from/to v 9 (u,v) ← Q.removeMin()10 // previene ciclos en T. suma u, v solo si T no contiene una arista que una u y v. 11 // Nótese que el cluster contiene más de un vértice si una arista une un par de12 // vértices que han sido añadidos al árbol.13 Let C(v) be the cluster containing v, and let C(u) be the cluster containing u.14 if C(v) ≠ C(u) then15 Add edge (v,u) to T.16 Merge C(v) and C(u) into one cluster, that is, union C(v) and C(u).17 return tree T

Complejidad del algoritmo

m el número de aristas del grafo y n el número de vértices, el algoritmo de Kruskal muestra una complejidad O(m log m) o, equivalentemente, O(m log n), cuando se ejecuta sobre estructuras de datos simples. Los tiempos de ejecución son equivalentes porque:

m es a lo sumo n2 y log n2 = 2logn es O(log n).

Page 61: Apuntes Unidad I Grafos PTR

ignorando los vértices aislados, los cuales forman su propia componente del árbol de expansión mínimo, n ≤ 2m, así que log n es O(log m).

Se puede conseguir esta complejidad de la siguiente manera: primero se ordenan las aristas por su peso usando una ordenación por comparación (comparison sort) con una complejidad del orden de O(m log m); esto permite que el paso "eliminar una arista de peso mínimo de C" se ejecute en tiempo constante. Lo siguiente es usar una estructura de datos sobre conjuntos disjuntos (disjoint-set data structure) para controlar qué vértices están en qué componentes. Es necesario hacer orden de O(m) operaciones ya que por cada arista hay dos operaciones de búsqueda y posiblemente una unión de conjuntos. Incluso una estructura de datos sobre conjuntos disjuntos simple con uniones por rangos puede ejecutar las operaciones mencionadas en O(m log n). Por tanto, la complejidad total es del orden de O(m log m) = O(m log n).

Con la condición de que las aristas estén ordenadas o puedan ser ordenadas en un tiempo lineal (por ejemplo, mediante el ordenamiento por cuentas o con el ordenamiento Radix), el algoritmo puede usar estructuras de datos de conjuntos disjuntos más complejas para ejecutarse en tiempos del orden de O(m α(n)), donde α es la inversa (tiene un crecimiento extremadamente lento) de la función de Ackermann.

Demostración de la corrección

Sea P un grafo conexo y valuado y sea Y el subgrafo de P producido por el algoritmo. Y no puede tener ciclos porque cada vez que se añade una arista, ésta debe conectar vértices de dos árboles diferentes y no vértices dentro de un subárbol. Y no puede ser disconexa ya que la primera arista que une dos componentes de Y debería haber sido añadida por el algoritmo. Por tanto, Y es un árbol expandido de P.

Sea Y1 el árbol expandido de peso mínimo de P, el cual tiene el mayor número de aristas en común con Y. Si Y1=Y entonces Y es un árbol de expansión mínimo. Por otro lado, sea e la primera arista considerada por el algoritmo que está en Y y que no está en Y1. Sean C1 y C2 las componentes de P que conecta la arista e. Ya que Y1 es un árbol, Y1+e tiene un ciclo y existe una arista diferente f en ese ciclo que también conecta C1 y C2. Entonces Y2=Y1+e-f es también un árbol expandido. Ya que e fue considerada por el algoritmo antes que f, el peso de e es al menos igual que que el peso de f y ya que Y1 es un árbol expandido mínimo, los pesos de esas dos aristas deben ser de hecho iguales. Por tanto, Y2 es un árbol expandido mínimo con más aristas en común con Y que las que tiene Y1, contradiciendo las hipótesis que se habían establecido antes para Y1. Esto prueba que Y debe ser un árbol expandido de peso mínimo.

Otros algoritmos para este problema son el algoritmo de Prim y el algoritmo de Boruvka.

Ejemplo

Page 62: Apuntes Unidad I Grafos PTR

Este es el grafo original. Los números de las aristas indican su peso. Ninguna de las aristas está resaltada.

AD y CE son las aristas más cortas, con peso 5, y AD se ha elegido arbitrariamente, por tanto se resalta.

Sin embargo, ahora es CE la arista más pequeña que no forma ciclos, con peso 5, por lo que se resalta como segunda arista.

La siguiente arista, DF con peso 6, ha sido resaltada utilizando el mismo método.

Page 63: Apuntes Unidad I Grafos PTR

La siguientes aristas más pequeñas son AB y BE, ambas con peso 7. AB se elige arbitrariamente, y se resalta. La arista BD se resalta en rojo, porque formaría un ciclo ABD si se hubiera elegido.

El proceso continúa marcando las aristas, BE con peso 7. Muchas otras aristas se marcan en rojo en este paso: BC (formaría el ciclo BCE), DE (formaría el ciclo DEBA), y FE (formaría el ciclo FEBAD).

Finalmente, el proceso termina con la arista EG de peso 9, y se ha encontrado el árbol expandido mínimo.

Algoritmo de Floyd-Warshall

En informática, el algoritmo de Floyd-Warshall, descrito en 1959 por Bernard Roy, es un algoritmo de análisis sobre grafos para encontrar el camino mínimo en grafos dirigidos ponderados. El algoritmo encuentra el camino entre todos los pares de vértices en una única ejecución. El algoritmo de Floyd-Warshall es un ejemplo de programación dinámica.

Algoritmo

El algoritmo de Floyd-Warshall compara todos los posibles caminos a través del grafo entre cada par de vértices. El algoritmo es capaz de hacer esto con sólo V3 comparaciones (esto es notable

Page 64: Apuntes Unidad I Grafos PTR

considerando que puede haber hasta V2 aristas en el grafo, y que cada combinación de aristas se prueba). Lo hace mejorando paulatinamente una estimación del camino más corto entre dos vértices, hasta que se sabe que la estimación es óptima.

Sea un grafo G con conjunto de vértices V, numerados de 1 a N. Sea además una función caminoMinimo(i,j,k) que devuelve el camino mínimo de i a j usando únicamente los vértices de 1 a k como puntos intermedios en el camino. Ahora, dada esta función, nuestro objetivo es encontrar el camino mínimo desde cada i a cada j usando únicamente los vértices de 1 hasta k + 1.

Hay dos candidatos para este camino: un camino mínimo, que utiliza únicamente los vértices del conjunto (1...k); o bien existe un camino que va desde i hasta k + 1, después de k + 1 hasta j que es mejor. Sabemos que el camino óptimo de i a j que únicamente utiliza los vértices de 1 hasta k está definido por caminoMinimo(i,j,k), y está claro que si hubiera un camino mejor de i a k + 1 a j, la longitud de este camino sería la concatenación del camino mínimo de i a k + 1 (utilizando vértices de (1...k)) y el camino mínimo de k + 1 a j (que también utiliza los vértices en (1...k)).

Por lo tanto, podemos definir caminoMinimo(i,j,k) de forma recursiva:

Esta fórmula es la base del algortimo Floyd-Warshall. Funciona ejecutando primero caminoMinimo(i,j,1) para todos los pares (i,j), usándolos para después hallar caminoMinimo(i,j,2) para todos los pares (i,j)... Este proceso continúa hasta que k = n, y habremos encontrado el camino más corto para todos los pares de vértices (i,j) usando algún vértice intermedio.

Pseudocodigo

Convenientemente, cuando calculamos el k-esimo caso, se puede sobreescribir la información salvada en la computación k -1. Esto significa que el algoritmo usa memoria cuadrática. Hay que cuidar la inicialización de las condiciones:

1 /* Suponemos que la función pesoArista devuelve el coste del camino que va de i a j2 (infinito si no existe).3 También suponemos que n es el número de vértices y pesoArista(i,i) = 04 */56 int camino[][];7 /* Una matriz bidimensional. En cada paso del algoritmo, camino[i][j] es el camino mínimo 8 de i hasta j usando valores intermedios de (1..k-1). Cada camino[i][j] es inicializado a 9 pesoArista(i,j)10 */

Page 65: Apuntes Unidad I Grafos PTR

1112 procedimiento FloydWarshall ()13 para k: = 0 hasta n − 114 para todo (i,j) en (0..n − 1)15 camino[i][j] = mín ( camino[i][j], camino[i][k]+camino[k][j]);

Comportamiento con ciclos negativos

Para que haya coherencia numérica, Floyd-Warshall supone que no hay ciclos negativos (de hecho, entre cualquier pareja de vértices que forme parte de un ciclo negativo, el camino mínimo no está bien definido porque el camino puede ser infinitamente pequeño). No obstante, si hay ciclos negativos, Floyd-Warshall puede ser usado para detectarlos. Si ejecutamos el algoritmo una vez más, algunos caminos pueden decrementarse pero no garantiza que, entre todos los vértices, caminos entre los cuales puedan ser infinitamente pequeños, el camino se reduzca. Si los números de la diagonal de la matriz de caminos son negativos, es condición necesaria y suficiente para que este vértice pertenezca a un ciclo negativo.

Ejemplo

Hallar el camino mínimo desde el vértice 3 hasta 4 en el grafo con la siguiente matriz de distancias:

Aplicamos el algoritmo de Floyd-Warshall, y para ello en cada iteración fijamos un vértice intermedio.

1ª Iteración: nodo intermedio = 1

La matriz es simétrica, por lo que solamente hará falta calcular el triángulo superior de las distancias.

d23 = min(d23, d21 + d13) = 8

d24 = min(d24, d21 + d14) = 4

d25 = min(d25, d21 + d15) = 9

d26 = min(d26, d21 + d16) =

Page 66: Apuntes Unidad I Grafos PTR

d34 = min(d34, d31 + d14) = 6

d35 = min(d35, d31 + d15) = 7

d36 = min(d36, d31 + d16) = 1

d45 = min(d45, d41 + d15) =

d46 = min(d46, d41 + d16) = 4

d56 = min(d56, d51 + d16) =

La matriz de distancia después de esta iteración es:

2ª Iteración: nodo intermedio = 2

d13 = min(d13, d12 + d23) = 5

d14 = min(d14, d12 + d24) = 1

d15 = min(d15, d12 + d25) = 12

d16 = min(d16, d12 + d26) =

d34 = min(d34, d32 + d24) = 6

d35 = min(d35, d32 + d25) = 7

d36 = min(d36, d32 + d26) = 1

d45 = min(d45, d42 + d25) = 13

d46 = min(d46, d42 + d26) = 4

d56 = min(d56, d52 + d26) =

Page 67: Apuntes Unidad I Grafos PTR

La matriz de distancia después de esta iteración es:

3ª Iteración: nodo intermedio = 3

d12 = min(d12, d13 + d32) = 3

d14 = min(d14, d13 + d34) = 1

d15 = min(d15, d13 + d35) = 12

d16 = min(d16, d13 + d36) = 6

d24 = min(d24, d23 + d34) = 4

d25 = min(d25, d23 + d35) = 9

d26 = min(d26, d23 + d36) = 9

d45 = min(d45, d43 + d35) = 13

d46 = min(d46, d43 + d36) = 4

d56 = min(d56, d53 + d36) = 8

La matriz de distancia después de esta iteración es:

4ª Iteración: nodo intermedio = 4

Page 68: Apuntes Unidad I Grafos PTR

d12 = min(d12, d14 + d42) = 3

d13 = min(d13, d14 + d43) = 5

d15 = min(d15, d14 + d45) = 12

d16 = min(d16, d14 + d46) = 5

d23 = min(d23, d24 + d43) = 8

d25 = min(d25, d24 + d45) = 9

d26 = min(d26, d24 + d46) = 8

d35 = min(d35, d34 + d45) = 7

d36 = min(d36, d34 + d46) = 1

d56 = min(d56, d54 + d46) = 8

La matriz de distancia después de esta iteración es:

5ª Iteración: nodo intermedio = 5

d12 = min(d12, d15 + d52) = 3

d13 = min(d13, d15 + d53) = 5

d14 = min(d14, d15 + d54) = 1

d16 = min(d16, d15 + d56) = 5

d23 = min(d23, d25 + d53) = 8

d24 = min(d24, d25 + d54) = 4

Page 69: Apuntes Unidad I Grafos PTR

d26 = min(d26, d25 + d56) = 8

d34 = min(d34, d35 + d54) = 6

d36 = min(d36, d35 + d56) = 1

d46 = min(d46, d45 + d56) = 4

La matriz de distancia después de esta iteración es:

6ª Iteración: nodo intermedio = 6

d12 = min(d12, d16 + d62) = 3

d13 = min(d13, d16 + d63) = 5

d14 = min(d14, d16 + d64) = 1

d15 = min(d15, d16 + d65) = 12

d23 = min(d23, d26 + d63) = 8

d24 = min(d24, d26 + d64) = 4

d25 = min(d25, d26 + d65) = 9

d34 = min(d34, d36 + d64) = 5

d35 = min(d35, d36 + d65) = 7

d45 = min(d45, d46 + d65) = 12

La matriz de distancia después de esta iteración es:

Page 70: Apuntes Unidad I Grafos PTR

Ya se han hecho todas las iteraciones posibles. Por tanto, el camino mínimo entre 2 vértices cualesquiera del grafo será el obtenido en la matriz final. En este caso, el camino mínimo entre 3 y 4 vale 5.

Análisis

Si utilizamos matrices booleanas, para encontrar todos los n2 de desde se necesita hacer 2n2 operaciones binarias. Debido a que empezamos con y computamos la secuencia de n matrices booleanas , , ..., , el número total de operaciones binarias es de

. Por lo tanto, la complejidad del algoritmo es Θ( n 3 ) y puede ser resuelto por una máquina determinista de Turing en tiempo polinómico.

Aplicaciones y generalizaciones

El algoritmo de Floyd-Warshall puede ser utilizado para resolver los siguientes problemas, entre otros:

Camino mínimo en grafos dirigidos(algoritmo de Floyd). Cierre transitivo en grafos dirigidos (algoritmo de Warshall). Es la formulación original del

algoritmo de Warshall. El grafo es un grafo no ponderado y representado por una matriz booleana de adyacencia. Entonces la operación de adición es reemplazada por la conjunción lógica(AND) y la operación menor por la disyunción lógica (OR).

Encontrar una expresión regular dada por un lenguaje regular aceptado por un autómata finito (algoritmo de Kleene).

Inversión de matrices de números reales(algoritmo de Gauss-Jordan). Ruta optima. En esta aplicación es interesante encontrar el camino del flujo máximo entre 2

vértices. Esto significa que en lugar de tomar los mínimos con el pseudocodigo anterior, se coge el máximo. Los pesos de las aristas representan las limitaciones del flujo. Los pesos de los caminos representan cuellos de botella; por ello, la operación de adición anterior es reemplazada por la operación mínimo.

Comprobar si un grafo no dirigido es bipartito.

Implementación del algoritmo de Floyd en Java

Page 71: Apuntes Unidad I Grafos PTR

El algoritmo de Floyd intenta resolver el problema de encontrar el camino más corto entre todos los pares de nodos o vértices de un grafo. Esto es similar a construir una tabla con todas las distancias mínimas entre pares de ciudades de un mapa, indicando la ruta a seguir para ir de la primera ciudad a la segunda. Esto puede verse de la siguiente manera:

Sea G= (V, A) un digrafo en el cual cada arco tiene asociado un costo no negativo. El problema es hallar para cualquier par de vértices (v, w) el camino más corto de v a w.

G= (V, A), V= {1,...,n} y C[i, j] es el costo del arco que va de i a j. El algoritmo calcula la serie de matrices Ak[i, j] significa el costo del camino más corto que va de i a j y que no pasa por algún vértice

mayor que k. El objetivo es calcular An[i, j]

Además de eso, se busca hallar también el camino más corto entre cada par de nodos, imprimiendo así en el programa no solo la matriz final sino también dichos caminos.

Solución al problema

La solución al problema se puede dar aplicando el algoritmo de Floyd para encontrar las distancias y caminos mínimos entre nodos. Una forma de implementar el algoritmo de Floyd en java es como se muestra en el siguiente fragmento de código. Se utilizó un enfoque dinámico, ya que se guardan valores en una matriz para luego reutilizarlos y no repetir operaciones.

public String floyd(long[][] adyacencia) { int n=adyacencia.length; long D[][]=adyacencia; String enlaces[][]=new String [n][n]; String[][] aux_enlaces=new String[n][n]; }

El método recibe una matriz, en este caso la matriz de adyacencia del grafo. Para calcular las distancias mínimas se crea una matriz D[][], la cual irá guardando los resultados que arroja el algoritmo de Floyd, y otra matriz que guardará los caminos mínimos llamada enlaces[][]. Los caminos mínimos se calculan, utilizando los diferentes k, que son guardados en la matriz enl_aux[][], y utilizada en el método enlaces(). El método enlaces, utiliza una llamada recursiva para recorrer la matriz de enlaces y arrojar el camino mínimo entre los nodos. Este resultado será guardado en la matriz enlaces[i][j].

Limitaciones

Page 72: Apuntes Unidad I Grafos PTR

Las limitaciones que se encontraron con el algoritmo no son muchas, mejor dicho, no se encontraron, a excepción que cuando se va a resolver un grafo muy grande, éste no halla la respuesta porque se presenta un desborde de memoria, ya que debe iterar y almacenar demasiados valores.

Complejidad temporal

Este es el método principal, es decir, el que halla la solución del problema que se le plantee (la matriz de adyacencia):

TABLA 1. Calculo del T (n)

Instrucción Tiempo

long[][] adyacencia 1

int n=adyacencia.length 1

long D[][]=adyacencia 1

String enlaces[][]=new String [n][n] 1

String[][] aux_enlaces=new String[adyacencia.length][adyacencia.length] 1

(1)for concatenados4n2 + 4n + 2

String enl_rec="" 1

(2)for concatenados bn4 + an3 + 12n3 + 4n2

Page 73: Apuntes Unidad I Grafos PTR

+ 4n + 2

enlaces (int i, int k, String[][] aux_enlaces,String enl_rec) 1

if(aux_enlaces[i][k].equals("")==true) 1

return " 1

enl_rec+=enlaces (i, Integer.parseInt(aux_enlaces[i][k].toString()),aux_enlaces,enl_rec)+(Integer.parseInt(aux_enlaces[i][k].toString())+1)+" , " an+b

T (n)=bn4 + an3 + 12n3 + 8n2 + 8n + 13 y O(n4)

Código fuente

Forma de compilar y ejecutar

Para poder compilar y ejecutar el programa debe tener instalado en su computador el jdk de java 1.5 o una actualización más reciente y además debe tener el path de su sistema configurado con el JDK.

Este programa está hecho en lenguaje Java, por lo que se puede compilar y ejecutar en cualquier IDE que tenga soporte JAVA; si usa Netbeans o Eclipse, debe primero crear un proyecto con el nombre que quiera, y crea tres clases con los correspondientes nombres usados en el código fuente y lógicamente pega dicho código en la clase. Si usa JCreator, puede bien sea, crear una única clase con el nombre de Aplicación y pega ahí todo el código fuente, o crea las tres clases con los nombres que son y pega el código fuente correspondiente en cada clase.

Tiempo empleado

El tiempo que nos tardo desarrollar el programa fue aproximadamente 3 días, el primer día desarrollando el algoritmo de solución, el segundo día desarrollando la interfaz grafica, y el tercero corrigiendo las excepciones que se pudieran presentar.

Pruebas

Page 74: Apuntes Unidad I Grafos PTR

Esta es la matriz de adyacencia de un grafo que puede ser utilizada para probar la efectividad del algoritmo: La i representa infinito.

Matriz de adyacencia

1 2 3 4

1 0 5 i i

2 50 0 15 5

3 30 i 0 15

4 15 i 5 0

La respuesta es:

Los caminos mínimos entre nodos son:

De ( 1 a 2 ) = ( 1 , 2 )

De ( 1 a 3 ) = ( 1 , 2 , 4 , 3 )

De ( 1 a 4 ) = ( 1 , 2 , 4 )

De ( 2 a 1 ) = ( 2 , 4 , 1 )

De ( 2 a 3 ) = ( 2 , 4 , 3 )

De ( 2 a 4 ) = ( 2 , 4 )

De ( 3 a 1 ) = ( 3 , 1 )

De ( 3 a 2 ) = ( 3 , 1 , 2 )

De ( 3 a 4 ) = ( 3 , 4 )

Page 75: Apuntes Unidad I Grafos PTR

De ( 4 a 1 ) = ( 4 , 1 )

De ( 4 a 2 ) = ( 4 , 1 , 2 )

De ( 4 a 3 ) = ( 4 , 3 )

Conclusiones

Con las herramientas adecuadas y unos buenos fundamentos en programación, además de tener un conocimiento en grafos, se podría concluir que el algoritmo es fácil de implementar.

Para hallar la solución óptima es mucho mejor usar el enfoque dinámico, ya que éste no repite operaciones y acelera un poco el proceso de solución.

El algoritmo tiene una desventaja, debido a su enfoque dinámico, realiza un uso masivo de memoria, lo cual puede ser inconveniente.

Floyd-Warshall Algorithm in C

The following is a C code implementation of the Floyd-Warshall algorithm for solving the all-pairs shortest path problem. The code is not guaranteed to run correctly in all cases as I have only tested it on my system, but if it does not, please let me know!

// Floyd-Warshall algorithm// // solves the all-pairs shortest path problem using Floyd-Warshall algorithm// inputs: nn, number of nodes// connectivity matrix cmat, where 0 means disconnected// and distances are all positive. array of doubles of// length (nn*nn).// outputs: // dist_mat - shortest path distances(the answer)// pred_mat - predicate matrix, useful in reconstructing shortest routes// Note that the caller should provide empty pointers as this // function will handle the malloc() calls.void fwarsh(int nn, double *cmat, double **dist_mat, int **pred_mat){ double *dist; int *pred; int i,j,k; //loop counters

//initialize data structures dist = (double *)malloc(sizeof(double) * nn * nn);

Page 76: Apuntes Unidad I Grafos PTR

pred = (int *)malloc(sizeof(int) * nn * nn); memset(dist, 0, sizeof(double)*nn*nn); memset(pred, 0, sizeof(int)*nn*nn);

//algorithm initialization for (i=0; i < nn; i++) { for (j=0; j < nn; j++) { if (cmat[i*nn+j] != 0.0)

dist[i*nn+j] = cmat[i*nn + j]; else

dist[i*nn+j] = HUGE_VAL; //disconnected

if (i==j) //diagonal casedist[i*nn+j] = 0;

if ((dist[i*nn + j] > 0.0) && (dist[i*nn+j] < HUGE_VAL))pred[i*nn+j] = i;

} } //Main loop of the algorithm for (k=0; k < nn; k++) { for (i=0; i < nn; i++) { for (j=0; j < nn; j++) {

if (dist[i*nn+j] > (dist[i*nn+k] + dist[k*nn+j])) { dist[i*nn+j] = dist[i*nn+k] + dist[k*nn+j]; pred[i*nn+j] = k; //printf("updated entry %d,%d with %d\n", i,j, dist[i*nn+j]);}

} } }

/* //Print out the results table of shortest distances for (i=0; i < nn; i++) { for (j=0; j < nn; j++) printf("%g ", dist[i*nn+j]); printf("\n"); } */ //now set the dist and pred matrices for the calling function //but do some checks because we allow NULL to be passed if the user

Page 77: Apuntes Unidad I Grafos PTR

//doesn't care about one of the results. if (dist_mat) *dist_mat = dist; else free(dist);

if (pred_mat) *pred_mat = pred; else free(pred);

return;} //end of fwarsh()

1.9 LOCURA INSTANTÁNEA

Es un juego formado por 4 cubos, cada una de cuyas caras está pintada de uno de 4 colores: rojo, blanco, azul, verde (R, B, A, V).

El problema consiste en apilar los cubos, uno sobre otro, de modo que uno vea los 4 colores desde el frente, por detrás, por la izquierda o por la derecha.

Ejemplo:

Determine una solución del juego de locura instantánea, cuya información está dada por el siguiente grafo:

Page 78: Apuntes Unidad I Grafos PTR

RB BV VA AR

AB AV VB RR

Cada arista debe tener grado 2.

Cada cubo debe representarse mediante una arista exactamente una vez en cada gráfica.

Las dos gráficas no deben tener aristas en común.

tPara obtener una solución primero trazamos una gráfica G que representa todas las caras de todos los cubos.

Los vértices de G representan los cuatro colores y una arista con etiqueta i conecta dos vértices (colores) si las caras opuestas i tienen dichos colores.

No es conveniente el método prueba error.

Mejor busque dos subgráficas que cumplan las condiciones pedidas.