tópicos i unidad i algoritmos sobre grafos elementales, conectividad y grafos ponderados semana 4...

Post on 02-Feb-2016

316 Views

Category:

Documents

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Tópicos I

Unidad I

Algoritmos sobre grafos elementales, Conectividad y Grafos ponderados

Semana 4

Árboles, montículos y grafos

Objetivos Generales

Entender el manejo, uso de algoritmos y estructuras de datos avanzados, haciendo énfasis en los algoritmos de internet, seguridad y redes.

Objetivo Específico

Implementar algoritmos utilizando estructura de datos avanzadas.

Objetivo Instruccional

Implementar algoritmos fundamentales para la solución de problemas basados en la teoría de grafos.

GlosarioGrafo: Un grafo es una colección de vértices y aristas. Los vértices son objetos simples que pueden tener un nombre y otras propiedades; una arista es una conexión entre dos vértices.

Camino: Un camino entre los vértices x e y de un grafo es una lista de vértices en la que dos elementos sucesivos están conectados por aristas del grafo.

A

B C G

D E

F

A

B G

E

F

GlosarioGrafo conexo: Un grafo es conexo si hay un camino desde cada nodo hacia otro nodo del grafo.

Grafo no conexo: Esta constituido por componentes conexos.

Camino simple: Es un camino en el que no se repite ningún vértice.

Ciclo: es un camino simple con la característica de que el primero y el ultimo vértices son el mismo.

A

B C GD E

F

A

B

G

D EF

A

B G

EF

A G

EF

GlosarioGrafo completo: Son los grafos con todas las aristas posibles.

Grafo disperso: Tienen relativamente pocas aristas.

Grafo denso: Les falta muy pocas aristas de todas las posibles.

A

G

EF

A

G

EF

A

G

EF

RepresentaciónMatriz de adyacencia:

A

B C G

D E

F

A B C D E F G

A 1 1 1 0 0 1 1

B 1 1 0 0 0 0 0

C 1 0 1 0 0 0 0

D 0 0 0 1 1 1 0

E 0 0 0 1 1 1 1

F 1 0 0 1 1 1 0

G 1 0 0 0 1 0 1

Se construye un array de V*V valores booleanos en el que a[x][y] es igual a 1 si existe una arista desde el vértice x al y, y a 0 en el caso contrario.

Es de destacar que en realidad cada arista se representa con dos bits: una arista que enlace x e y se representa con valores verdaderos tanto en a[x][y] como en a[y][x].

La matriz de adyacencia solo es satisfactoria si los grafos a procesar son densos.

RepresentaciónLista de adyacencia:

A

B C G

D E

F

La lista de adyacencia se adapta mejor a lo casos en los que los grafos a procesar no son densos.

A B C D E F G

F A A AA

B

C

G

E

F

D

DF

G E

E

Recorridos en un grafo

En una gran cantidad de problemas con grafos, es necesario visitar sistemáticamente los vértices y aristas del grafo.

La búsqueda en PROFUNDIDAD y en AMPLITUD, son dos técnicas importantes de recorrido del grafo.

Búsqueda en Profundidad (BEP)

Se comienza en el vértice inicial (vértice con

índice 1) que se marca como vértice activo. Hasta que todos los vértices hayan sido visitados, en cada paso se avanza al vecino con el menor índice siempre que se pueda, pasando este a ser el vértice activo. Cuando todos los vecinos al vértice activo hayan sido visitados, se retrocede al vértice X desde el que se alcanzó el vértice activo y se prosigue siendo ahora X el vértice activo.

Búsqueda en Profundidada b

d e

c

a b

d e

c

a b

d e

c

a b

d e

c

a b

d e

c

a b

d e

c

La estructura utilizada para guardar los nodos a visitar en este tipo de búsqueda es una pila o stack (de procedimiento recursivo).

Búsqueda en Amplitud (BEA)

Se comienza en el vértice inicial (vértice con índice 1) y se marca como vértice activo, a diferencia con la BEP ahora se visitan en orden creciente de índice todos los vecinos del vértice activo antes de pasar al siguiente. Hasta que todos los vértices hayan sido visitados, en cada paso se van visitando en orden creciente de índice todos los vecinos del vértice activo. Cuando se han visitado todos los vecinos del vértice activo, se toma como nuevo vértice activo el primer vértice X visitado después del actual vértice activo.

Búsqueda en Amplitud

a b

d e

c

0a b

d e

c

0 1

1

1

a b

d e

c

0 1

1

1 2En la búsqueda en amplitud se utiliza una cola para guardar tales nodos a visitar.

Usos de los Recorridos Ambos recorridos se pueden usar para resolver

los siguientes problemas:– Probar que G es conectado.– Obtener un árbol de expansión de G.– Obtener los componentes conectados de G.– Obtener un camino entre dos vértices dados de G, o

indicar que no existe tal camino. El recorrido BEP se usa para:

– Obtener un ciclo en G, o indicar que G no tiene ciclos. El recorrido BEA se usa para:

– Obtener para cada vértice v de G, el número mínimo de

aristas de cualquier camino entre s y v.

LaberintosLa búsqueda en profundidad fue expuesta formalmente como un método para recorrer laberintos.

El grafo se construye colocando un vértice en cada punto en el que existe mas de un camino a tomar y a conectar a continuación los vértices de acuerdo con esos caminos.

“La búsqueda en profundidad es apropiada para la búsqueda de un elemento en el laberinto por una sola persona, dado que el “siguiente lugar a visitar” esta siempre próximo; la búsqueda es amplitud es mas apropiada par un grupo de personas que buscan el mismo elemento desplazándose en todas las direcciones a la vez”

ProblemáticaSe examinara una generalización de la conectividad denominada biconectividad, cuyo interés reside en conocer si hay mas de un medio de pasar de un vértice de un grafo a otro.

Grafo biconexo: Un grafo es biconexo, si solo si, existen al menos dos caminos diferentes que conecten cada par de vértices. De esta forma si se suprime un vértice y todas las aristas que inciden en el, el grafo permanece conexo.

A

G

EF

“Si para algunas aplicaciones es importante que un grafo sea conexo, es también importante que permanezca conexo”

ProblemáticaUna versión particular del problema de la conectividad, que con frecuencia concierne a la situación dinámica en las que las aristas se añaden al grafo una a una, intercalando preguntas sobre si dos vértices determinados pertenecen (o no) a la misma componente conexa.

El problema se denomina a veces como “unión-pertenencia”.

Componentes conexas

• De un grafo no dirigido– Se pude saber si es conexo

• Si no lo es se pueden conocer sus – Componentes Conexas

• Conjunto W, de vértices del grafo

• En el cual hay camino desde cualquier V1 a cualquier V2

• Donde V1 y V2 pertenecen a W

A

C

B

D

EA

C

B

D

E

CONEXO

No CONEXO

Componentes conexas: entre ellas son conexas

BiconectividadA veces es útil diseñar mas de una ruta entre puntos de un grafo, aunque solo sea para identificar posibles fallos en los puntos de conexión (vértices). Esto nos permitiría tener recorridos alternativos por ejemplo para llegar de una ciudad a otra.

A

G

EF

Puntos de articulación

En un grafo no dirigido conexo:•Existen vértices que si se eliminan

– “Desconectan” al Grafo

– Lo dividen en componentes conexas

•Estos vértices “clave” son puntos de articulación

•Si un grafo no tiene Punto de Articulación– Es biconexo

•La conectividad de un grafo– Es el numero de nodos que se necesitan eliminar para dejar a

un grafo “desconectado”

Un punto de articulación en un grafo conexo es un vértice que si se suprime romperá el grafo en dos o mas piezas.

Ejemplo

A

D

C

B

E

F

Puntos de Articulación

Puntos de articulación

Árbol de expansiónDefinición: Un árbol T es un árbol de expansión de un grafo G si T es un subgrafo que contiene a todos los vértices de G.

Árbol de expansión• Para poder calcular los Puntos de Articulación de un grafo

– Hay que obtener el árbol de expansión

• Este se obtiene– A partir del Recorrido en Profundidad

Ejemplo:

A

D

C

B

E

F

A

C

F

E

B

D

Arco en Retroceso: Cuando se quiere visitar un nodo que ya ha sido visitado y no es el padre

Como representar el árbol de expansión

• Un árbol en expansión– No es un árbol binario– Cada Vértice puede tener 0, 1 o n hijos

• Si no tomamos en cuenta los arcos en retroceso, la representación depende del Grafo– Matriz de Adyacencia -> Usar un arreglo – Lista de Adyacencia -> Una lista

• La representación mostrará quien es el padre de cada nodo

Árbol de expansión con matriz de adyacencia

• Los vértices del grafo– Se encuentran en un arreglo

– Cada índice del arreglo• Representa un vértice

• Ej: A – 0, B – 1, etc.

• Al representar el árbol de expansión– El padre(índice) de cada nodo puede

almacenarse en un arreglo• Que también represente a los vértices

A

D

C

B

E

F

0400520

A

1

B

2

C

3

D

4

E

5

Ftypedef struct TVertices{

Generico V[MAX];

int Padres[MAX];

int nvertices;

}Vertices;

Árbol de expansión con lista de adyacencia

• Los vértices del grafo– Se encuentran en una lista– Cada nodo representa un vértice

• Al representar el árbol de expansión– De cada nodo de esta lista solo nos interesa

conocer al padre

– Se puede añadir un dato al nodo vértice• Un enlace con el padre

A

DC

B

E

F

A

B

C

D

E

F

typedef struct Vertice{

Generico Informacion;

Vertice *padre;

Lista *LArcos;

}Vertice;

Unión - PertenenciaEn algunas aplicaciones se desea simplemente conocer si un vértice x esta o no conectado a un vértice y de un grafo, sin que sea importante el camino que los conecta.

Los grafos se corresponden de forma natural con los conjuntos (colecciones de objetos): los vértices representan a los objetos y las aristas significan “esta en el mismo conjunto que”.

A B

C

G

D E

F

{A F D E} {B C G}

Conjuntosó clases de equivalencia

Cada componente conexa corresponde a una clase de equivalencia diferente

Unión - PertenenciaEl añadir una arista se corresponde con la combinación de las clases de equivalencia representadas por los vértices a conectar.

El interés se centra en la pregunta fundamental “x es equivalente a y” ó “¿está x en el mismo conjunto que y?”. Esto se corresponde claramente con la pregunta fundamental de los grafos “¿está el vértice x conectado al vértice y?”

“Dado un conjunto de aristas, se puede construir una representación por lista de adyacencia que corresponda al grafo y utilizar la búsqueda en profundidad para asignar a cada vértice el índice de su correspondiente conexa y responder a las preguntas antes planteada con tal solo dos accesos a arrays y una comparación”

“Dado un conjunto de aristas, se puede construir una representación por lista de adyacencia que corresponda al grafo y utilizar la búsqueda en profundidad para asignar a cada vértice el índice de su correspondiente conexa y responder a las preguntas antes planteada con tal solo dos accesos a arrays y una comparación”

Unión - PertenenciaPor correspondencia con el problema de los conjuntos, la adición de una nueva arista se denomina una operación de unión, y las preguntas se denominan operaciones de pertenencia.

El objetivo es escribir una función que pueda verificar si dos vértices x e y pertenecen al mismo conjunto (o, en representación de grafos, a la misma componente conexa) y, en caso de que sea así, que pueda unirlos en el mismo conjunto (colocando una arista entre ellos y el

grafo).

En lugar de construir una lista de adyacencia directa o cualquier otra representación de los grafos, es mas eficaz utilizar una estructura interna orientada específicamente a la realización de las operaciones unión y pertenencia.

Unión - PertenenciaEsta estructura interna es un bosque de arboles, uno por cada componente conexa.

Se necesita poder encontrar si dos vértices pertenecen al mismo árbol y combinar dos arboles en uno.

Unión - PertenenciaPara ilustrar como trabaja este algoritmo, vamos a ver el bosque construido por los siguientes vértices (A-B-C-D-E-F-G-H-I-J-K-L-M) cuando se le insertan estas aristas en el siguiente orden: AG - AB - AC - LM - JM - JL - JK - ED - FD - HI - FE - AF - GE - GC - GH - JG - LG. Inicialmente todos los nodos están en árboles separados (cada vértice es un árbol).

A B C D E F G H I J K L M

Unión - PertenenciaLuego la arista AG crea un árbol de dos nodos con A como raíz (esta decisión es arbitraria, porque tranquilamente

podríamos haber puesto G como raíz).

Las aristas AB y AC agregan B y C al árbol de la misma forma.

Unión - Pertenencia

Proceso culminado

Unión - PertenenciaEs necesario recalcar que, al contrario que en los árboles de búsqueda primero en profundidad, la única relación entre estos árboles de unión y el grafo original con las aristas dadas es que dividen a los vértices en grupos de la misma forma. Por ejemplo, no hay ninguna correspondencia entre los caminos que conectan nodos en los árboles y los caminos que conectan nodos en el grafo.

Para representar estos árboles es apropiado usar la representación “enlace padre" porque siempre se viaja hacia arriba en los árboles, nunca hacia abajo. Específicamente, mantenemos un array padre que contiene, para cada vértice, el índice de su padre (0 si

es la raíz de algún árbol), como se especifica en la próxima declaración de la clase:

Unión - Pertenenciaclass EQ{private:

int *dad;public:

EQ(int size);Int find(int x, int y, int doit);

};

El constructor reserva el espacio para el array dad y setea todos sus valores inicialmente a cero (se omite el código). Se usa un solo procedimiento find para implementar ambos, la unión y la búsqueda. Si el tercer argumento es 0 se hace una búsqueda, si no es 0, se hace una unión. Para encontrar al padre del vértice j, simplemente se hace j = dad[j], y para encontrar la raíz del árbol al que pertenece j se repite esta operación hasta que se llega a 0.

Unión - PertenenciaEsto nos lleva a la siguiente implementación del procedimiento:

Int EQ::find(int x, int y, int doit){ int i=x, j=y;

while (dad[i] >0) i = dad[i];while (dad[j] > 0) j=dad[j];if (doit && (i != j)) dad[j]=i;return (i != j);

}

La función find retorna 0 si los dos vértices dados están en el mismo componente. Si no lo están y el flag doit esta seteado, son puestos en el mismo componente. El método es simple. Usar el array dad para llegar a la raíz del árbol que contiene cada vértice, luego chequear si las raíces son las mismas. Para mezclar el árbol con raíz j con el árbol de raíz i simplemente se setea dad[j] = i.

Unión - Pertenencia

Contenido de la estructura de datos union-pertenencia durante el proceso

Unión - PertenenciaEl algoritmo descrito anteriormente tiene un mal desempeño para su peor caso porque los árboles formados pueden ser degenerados.

Por ejemplo, tomando en orden las aristas AB BC CD DE EF FG GH HI IJ… YZ se produce una larga cadena con Z apuntando a Y, Y apuntando a X, etc. Este tipo de estructura toma un tiempo proporcional a V2 para construirse y tiene un tiempo proporcional a V para una prueba promedio de equivalencia.

Unión - PertenenciaVarios métodos han sido sugeridos para lidiar con esta dificultad. Un método natural es tratar de hacer lo correcto para mezclar dos árboles, en lugar de hacer arbitrariamente dad[ j ] = i. Cuando un árbol con raíz en i va a ser mezclado con otro con raíz en j, uno de los nodos debe mantenerse como raíz y el otro (y todos sus

descendientes) deben ir un nivel más abajo en el árbol.

Para minimizar la distancia a la raíz de la mayoría de los nodos tiene sentido elegir como raíz el nodo con más descendientes.

Unión - PertenenciaEsta idea, llamada equilibrado de peso, es fácilmente implementable manteniendo el tamaño de cada árbol (cantidad de descendientes de

la raíz) en el array dad para cada nodo raíz, codificado como un número no positivo de tal modo que el nodo raíz pueda ser detectado cuando se viaja hacia arriba en el árbol con find.

Unión - PertenenciaIdealmente se desearía que cada nodo apuntara directamente a la raíz de su árbol. Sin importar que estrategia se use, para lograr este objetivo se necesita examinar al menos todos los nodos en uno de los dos árboles a mezclar y esto puede ser demasiado comparado con los pocos nodos en el camino a la raíz que find generalmente examina. Sin embargo podemos aproximarnos a este ideal haciendo que todos los nodos que sí se examinan apunten a la raíz. Este método, conocido como compresión de caminos, se implementa fácilmente haciendo otra pasada por cada árbol, después de que la raíz fue encontrada, y seteando la entrada dad de cada vértice encontrado a lo largo del camino para que apunte a la raíz.

Unión - PertenenciaLa combinación de compresión de caminos y equilibrado de peso nos aseguran que los algoritmos van a correr muy rápido. La siguiente implementación muestra que el código extra necesario es un pequeño precio a pagar para protegernos de los casos degenerados.

int EQ::find(int x, int y, int doit){

int t, i=x, j=y;while (dad[i] > 0) i=dad[i];while (dad[j] > 0) j=dad[j];while (dad[x]>0) { t=x; x=dad[x]; dad[t]=i; }while (dad[y]>0) { t=y; y=dad[y]; dad[t]=j; }if (doit && (i != j)) if (dad[j] < dad[i]) { dad[j]+=dad[i] - 1; dad[i]=j; } else { dad[i]+=dad[j] - 1; dad[j]=i; }return (i !=j );

}

Unión - Pertenencia

Proceso culminado al aplicar el método, equilibrado, con compresión de caminos

Unión - Pertenencia

Contenido de la estructura de datos union-pertenencia durante el proceso con el método equilibrado, con compresión de caminos

ProblemáticaCon frecuencia se desea modelar problemas prácticos utilizando grafos en los que se asocia a las aristas unos pesos o costes.En un mapa de líneas aéreas, en el que las aristas representan rutas de vuelo, los pesos pueden representar distancias o tarifas. Estas situaciones hacen aparecer de forma natural cuestiones como el minimizar costes.

ProblemáticaSe examinara los algoritmos de dos de estos problemas:

1.Encontrar la forma de conectar todos los puntos al menor coste (problema del árbol de expansión mínima).

2.Encontrar el camino de menor coste entre dos puntos dados (problema del camino mas corto).

La forma de representar a los grafos ponderados es obvia. En la representación por matriz de adyacencia, la matriz puede contener pesos de aristas en lugar de valores booleanos y en la representación por listas de adyacencia se puede añadir un campo a cada elemento de la lista, a manera de peso.

Árbol de expansión mínimo

Un árbol de expansión mínimo de un grafo ponderado es una colección de aristas que conectan todos los vértices y en el que la suma de los pesos de las aristas es al menos inferior a la suma de los pesos de cualquier otra colección de aristas que conecten todos los vértices.

Algoritmo genérico

Se puede construir el árbol de expansión mínimo comenzando en cualquier vértice y tomando siempre el vértice mas próximo de todos los que se hayan elegido.

En otras palabras, se busca la arista de menor peso entre todas las que conectan vértices que ya están en el árbol con vértices que no lo están todavía, y después se añade al árbol la arista y el vértice a los que conduce la anterior.

Algoritmo de Kruskal

Se puede construir el árbol de expansión mínimo comenzando en cualquier vértice y tomando siempre el vértice mas próximo de todos los que se hayan elegido.

En otras palabras, se busca la arista de menor peso entre todas las que conectan vértices que ya están en el árbol con vértices que no lo están todavía, y después se añade al árbol la arista y el vértice a los que conduce la anterior.

Algoritmo de Kruskal – paso a paso

Tengamos el siguiente grafo no dirigido.

Algoritmo de Kruskal – paso a paso

En primer lugar usaremos el método MakeSet de unión-find (revisar) para inicializar cada componente, obteniendo las siguientes componentes conexas iniciales:

Algoritmo de Kruskal – paso a paso

Ahora el siguiente paso es ordenar las aristas del grafo en orden ascendente:

Algoritmo de Kruskal – paso a paso

Lo siguiente será recorrer todas las aristas ya ordenadas y verificar si sus vértices están o no en la misma componente.

La primera arista a verificar es la que une a los vértices 8 y 7, verificamos si están en la misma componente, para ello hacemos Find(8) , Find(7):

Algoritmo de Kruskal – paso a paso

Como podemos observar en la tabla y en la misma imagen no están en la misma componente conexa, por tanto esta arista es valida para el árbol de expansión mínima (MST) así que unimos los vértices por el método de Union( 8 , 7 ).

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

Observamos la tabla de Union-Find y vemos que Find(3) != Find(9). Entonces es posible realizar la unión de ambas componentes:

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

En la imagen podemos observar que ambos vértices no están en la misma componente, por tanto realizamos la Union( 6 , 7 ):

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista, los vértices 1 y 2 no están en la misma componente conexa:

Algoritmo de Kruskal – paso a paso

Realizamos la Union(1,2):

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

Al observar la imagen los vértices 3 y 6 están en distinta componentes conexas. Entonces realizamos la Union(3,6) y actualizamos la tabla de Union-Find.

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista:En este caso si observamos la imagen los vértices 7 y 9 están en la misma componente conexa; asimismo en la tabla de Union-Find el elemento raíz del vértice 7 es el mismo que el del vértice 9 por ello afirmamos que están el a misma componente conexa, por lo tanto no habrá que realizar la unión de ambos vértices. Con esto evitamos tener ciclos en el árbol de expansión mínima.

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

Al observar la imagen los vértices 3 y 4 no están en la misma componente conexa por lo tanto realizamos la Union(3,4) en el grafo:

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

Los vértices 8 y 9 están en la misma componente conexa por lo tanto no realizamos Unión de vértices. Continuemos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

Los vértices 1 y 8 están diferentes componentes. Realizamos la Union(1,8) en el grafo:

Algoritmo de Kruskal – paso a paso

Continuamos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

Los vértices 2 y 3 están en la misma componente conexa por lo tanto no realizamos Union de componentes. Continuamos con la siguiente arista:

Algoritmo de Kruskal – paso a paso

Los vértices 4 y 7 no están en la misma componente conexa, realizamos Union(4,5) en el grafo:

Algoritmo de Kruskal – paso a paso

Como podemos observar ya están todos los vértices del grafo conectados así que al momento de continuar viendo las demás aristas ordenadas siempre tendremos e l caso de que ya están en la misma componente conexa por lo tanto el Árbol de Expansión Mínima para el grafo es el siguiente:

El peso total del árbol de expansión mínima para el grafo mostrado es 39.

Verificación del árbol de expansión mínima (MST)

Para que sea un MST válido, el número de aristas debe ser igual al número de vértices – 1. Esto se cumple debido a que el MST debe poseer todos los vértices del grafo ingresado y además no deben existir ciclos. Si vemos el ejemplo antes explicado tenemos en el MST:

Número de Aristas = 8 Número de Vértices = 9

Cumple con lo dicho -> 9 – 1 = 8 por tanto tenemos un MST válido

Union - FindUnion Find es una estructura de datos que modela una colección de conjuntos disjuntos (disjoint-sets) y esta basado en 2 operaciones:

•Find( A ): Determina a cual conjunto pertenece el elemento A. Esta operación puede ser usada para determinar si 2 elementos están o no en el mismo conjunto.

•Union( A, B ): Une todo el conjunto al que pertenece A con todo el conjunto al que pertenece B, dando como resultado un nuevo conjunto basado en los elementos tanto de A como de B.

Estas operaciones servirán para la implementación del algoritmo de Kruskal o problemas que involucran particionamiento como encontrar las componentes conexas en un grafo.

Union - Find

Bosques de Conjuntos Disjuntos

En esta implementación representamos los conjuntos como un árbol, donde cada nodo mantiene la información de su nodo padre, la raíz del árbol será el elemento representativo de todo el conjunto. Por lo tanto basta con declarar un arreglo que contenga los elementos del padre de un determinado elemento.

Union - FindMétodo MakeSet

Es un método que inicializa los conjuntos.

Tengamos por ejemplo 9 vértices, inicializamos por medio de MakeSet:

Una vez inicializado podemos unir dos componentes conexas o preguntar con el método find si un determinado vértice pertenece a la misma componente conexa de otro vértice.

Union - FindMétodo Find

Este método determina a cual componente conexa pertenece un vértice X determinado, ello lo hace retornando el vértice raíz del árbol en el que se encuentra el vértice X.

Por ejemplo tengamos las siguientes componentes conexas vistas como arboles:

Deseo saber la raíz de la componente conexa a la que pertenece el vértice 3.

Union - Find

Al hacer Find( 3 ) partimos del vértice 3 y subimos hasta la raíz viendo su padre, en este caso el padre[ 3 ] = 1 por lo tanto:

Union - Find

El vértice actual ahora es el vértice 1 y hacemos lo mismo que antes, padre[ 1 ] = 0.

Union - Find

Ahora estamos en vértice 0, como el padre[ 0 ] = 0 entonces estamos en la raíz y la retornamos.

Union - FindMétodo Union

Este método permite unir 2 componentes conexas, ello se realiza por lo siguiente:

•Obtenemos la raíz del vértice x.•Obtenemos la raíz del vértice y.

Actualizamos el padre de alguna de las raíces, asignándole como nuevo padre la otra raíz.

Union - FindPor ejemplo:

Union - FindComo se pudo observar primero realizamos los pasos 1 y 2 para hallar las raíces de ambos vértices y finalmente el paso 3 para actualizar el padre de una de las componentes conexas, en este caso se actualizará el padre de la componente conexa X. Continuemos:

Union - FindAl igual que el caso anterior el nuevo padre del vértice 7 es el vértice 0.

Union - FindEn este caso hemos realizado Union( 3 , 1 ), entonces el nuevo padre del vértice 3 es el vértice 1. Hasta el momento tenemos 6 componentes conexas.

Union - FindEn este caso estamos uniendo dos componentes con más vértices y como se puede observar solo es necesario actualizar el puntero de la raíz de una de las componentes.

Union - FindEn la imagen anterior se hizo Union( 6 , 4 ) -> Union( 8 , 4 ) -> Union( 4 , 5 ) en ese orden.

En esta última imagen hemos unido todos los nodos obteniendo una componente conexa.

Camino mas corto

El problema del camino mas corto consiste en encontrar entre todos los caminos que conectan a dos vértices x e y dados de un grafo ponderado el que cumple con la propiedad de que la suma de las ponderaciones de todas las aristas sea mínima.

Si las ponderaciones son todas iguales a 1, entonces el problema sigue siendo interesante: ahora consiste en encontrar el camino que contenga al mínimo de aristas que conecten a x e y.

Algoritmo de Dijkstra

El algoritmo de Dijkstra determina la ruta más corta desde un nodo origen hacia los demás nodos para ello es requerido como entrada un grafo cuyas aristas posean pesos.

Algunas consideraciones:

•Si los pesos de las aristas son de valor 1, entonces bastará con usar el algoritmo de BFS.

•Si los pesos de las aristas son negativos no puedo usar el algoritmo de dijsktra, para pesos negativos tenemos otro algoritmo llamado Algoritmo de Bellmand-Ford.

Algoritmo de DijkstraComo trabaja:Primero marcamos todos los vértices como no utilizados. El algoritmo parte de un vértice origen que será ingresado, a partir de ese vértices evaluaremos sus adyacentes, como dijkstra usa una técnica greedy -La técnica greedy utiliza el principio de que para que un camino sea óptimo, todos los caminos que contiene también deben ser óptimos- entre todos los vértices adyacentes, buscamos el que esté más cerca de nuestro punto origen, lo tomamos como punto intermedio y vemos si podemos llegar más rápido a través de este vértice a los demás. Después escogemos al siguiente más cercano (con las distancias ya actualizadas) y repetimos el proceso. Esto lo hacemos hasta que el vértice no utilizado más cercano sea nuestro destino. Al proceso de actualizar las distancias tomando como punto intermedio al nuevo vértice se le conoce como relajación (relaxation).

Algoritmo de DijkstraPseudo-codigo:

Considerar distancia[ i ] como la distancia mas corta del vértice origen ingresado al vértice i.

1 método Dijkstra(Grafo,origen):2 creamos una cola de prioridad Q3 agregamos origen a la cola de prioridad Q4 mientras Q no este vacío:5 sacamos un elemento de la cola Q llamado u6 si u ya fue visitado continuo sacando elementos de Q 7 marcamos como visitado u8 para cada vértice v adyacente a u en el Grafo:9 sea w el peso entre vértices ( u , v ) 10 si v no ah sido visitado:11 Relajacion( u , v , w )

1 método Relajacion( actual , adyacente , peso ):2 si distancia[ actual ] + peso < distancia[ adyacente ]3 distancia[ adyacente ] = distancia[ actual ] + peso4 agregamos adyacente a la cola de prioridad Q

Algoritmo de Dijkstra – paso a paso

Tengamos el siguiente grafo, cuyos ID están en color negro encima de cada vértice, los pesos esta en color azul y la distancia inicial en cada vértice es infinito.

Algoritmo de Dijkstra – paso a paso

Inicializamos los valores de nuestros arreglos.

Donde:

•Vértices: ID de los vértices.•Distancia[ u ]: Distancia mas corta desde vértice inicial a vértice con ID = u.•Visitado[ u ]: 0 si el vértice con ID = u no fue visitado y 1 si ya fue visitado.•Previo[ u ]: Almacenara el ID del vértice anterior al vértice con ID = u, me servirá para impresión del camino mas corto.

Algoritmo de Dijkstra – paso a paso

De acuerdo al vértice inicial que elijamos cambiara la distancia inicial, por ejemplo la ruta más corta partiendo del vértice 1 a todos los demás vértices:

El vértice 1 es visitado, la distancia de vértice 1 -> vértice 1 es 0 por estar en el mismo lugar.

Algoritmo de Dijkstra – paso a paso

Hasta este momento la tabla quedaría de la siguiente manera.

Ahora vemos sus adyacentes que no hayan sido visitados. Tendríamos 2 y 4.

Algoritmo de Dijkstra – paso a paso

Evaluamos primero para vértice 2

Vemos que la distancia actual desde el vértice inicial a 2 es ∞, verifiquemos el paso de relajación:

distancia[ 1 ] + 7 < distancia[ 2 ] -> 0 + 7 < ∞ -> 7 < ∞

Algoritmo de Dijkstra – paso a paso

El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 2 y agregando el vértice en la cola de prioridad con nueva distancia quedando:

Algoritmo de Dijkstra – paso a paso

Ahora evaluamos al siguiente adyacente que es el vértice 4:

De manera similar al anterior vemos que la distancia actual desde el vértice inicial a 4 es ∞, verifiquemos el paso de relajación:

distancia[ 1 ] + 2 < distancia[ 4 ] -> 0 + 2 < ∞ -> 2 < ∞

Algoritmo de Dijkstra – paso a paso

El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 4 quedando:

En cuanto a la cola de prioridad como tenemos un vértice con menor peso este nuevo vértice ira en el tope de la cola:

Algoritmo de Dijkstra – paso a paso

Revisamos sus adyacentes no visitados que serian vértices 2, 3 y 5.

Algoritmo de Dijkstra – paso a paso

Evaluamos primero para vértice 2

Ahora vemos que la distancia actual del vértice inicial al vértice 2 es 7, verifiquemos el paso de relajación:

distancia[ 4 ] + 3 < distancia[ 2 ] -> 2 + 3 < 7 -> 5 < 7

Algoritmo de Dijkstra – paso a paso

En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al vértice 2, actualizamos la distancia en el vértice 2 y actualizamos el vértice previo al actual quedando:

En cuanto a la cola de prioridad como tenemos un vértice con menor peso este nuevo vértice ira en el tope de la cola, podemos ver que tenemos 2 veces el mismo vértice pero como usamos una técnica greedy siempre usaremos el valor óptimo:

Algoritmo de Dijkstra – paso a paso

Continuamos con los Vértices 3 y 5 como tienen valor ? si será posible relajarlos por lo que sería similar a los pasos iniciales solo que en los pasos iniciales distancia[ 1 ] era 0 en este caso distancia[ 4 ] es 2, quedando:

Algoritmo de Dijkstra – paso a paso

Hemos terminado de evaluar al vértice 4, continuamos con el tope de la cola que es vértice 2, el cual marcamos como visitado.

Algoritmo de Dijkstra – paso a paso

Los adyacentes no visitados del vértice 2 son los vértices 3 y 5. Comencemos con el vértice 3.

Ahora vemos que la distancia actual del vértice inicial al vértice 3 es 10, verifiquemos el paso de relajación:

distancia[ 2 ] + 1 < distancia[ 3 ] -> 5 + 1 < 10 -> 6 < 10

Algoritmo de Dijkstra – paso a paso

En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al vértice 3, dicha ruta sería 1 -> 4 -> 2 -> 3 cuyo peso es 6 que es mucho menor que la ruta 1 – > 4 -> 3 cuyo peso es 10, actualizamos la distancia en el vértice 3 quedando:

Algoritmo de Dijkstra – paso a paso

El siguiente vértice de la cola de prioridad es el vértice 3 y su único adyacente no visitado es el vértice 5.

Vemos que la distancia actual del vértice inicial al vértice 5 es 7, verifiquemos el paso de relajación:

distancia[ 3 ] + 5 < distancia[ 5 ] -> 6 + 5 < 7 -> 11 < 7

Algoritmo de Dijkstra – paso a paso

En esta oportunidad se no cumple por lo que no relajamos el vértice 5, por lo que la tabla en cuanto a distancias no sufre modificaciones y no agregamos vértices a la cola:

Algoritmo de Dijkstra – paso a paso

Ahora tocaría el vértice 2 pero como ya fue visitado seguimos extrayendo elementos de la cola, el siguiente vértice será el 5.

Algoritmo de Dijkstra – paso a paso

Al ser el ultimo vértice a evaluar no posee adyacentes sin visitar por lo tanto hemos terminado el algoritmo. En el grafico anterior observamos que 2 aristas no fueron usadas para la relajación, las demás si fueron usadas. La tabla final quedaría de la siguiente manera:

De la tabla si deseo saber la distancia mas corta del vértice 1 al vértice 5, solo tengo que acceder al valor del arreglo en su índice respectivo (distancia[ 5 ]).

Algoritmo de Dijkstra – Impresión camino encontradoEn el proceso anterior usábamos el arreglo previo[ u ] para almacenar el ID del vértice previo al vértice con ID = u, ello me sirve para formar el árbol de la ruta mas corta y además me sirve para imprimir caminos de la ruta mas corta.

Algoritmo de Dijkstra – Impresión camino encontradoPara imprimir el camino mas corto deseado usamos el arreglo previo[ u ], donde u tendrá el ID del vértice destino, o sea si quiero imprimir el camino mas corto desde vértice 1 -> vértice 3 partiré desde previo[ 3 ] hasta el previo[ 1 ].

Veamos gráficamente el funcionamiento, desde el grafo comenzamos en 3

Algoritmo de Dijkstra – Impresión camino encontradoEl previo de 3 es el vértice 2, por lo tanto ahora evaluó 2:

Algoritmo de Dijkstra – Impresión camino encontradoAhora el previo de 2 es el vértice 4:

Algoritmo de Dijkstra – Impresión camino encontradoEl previo de 4 es el vértice inicial 1

Como el previo de 1 es -1 terminamos el recorrido, ahora en el retorno de las llamadas recursivas imprimo el camino: 1 4 2 3

Tópicos I

Unidad I

Semana 4

Algoritmos sobre grafos elementales, Conectividad y Grafos ponderados

Árboles, montículos y grafos

top related