Está en la página 1de 47

Definicin

Un grafo es un objeto matemtico que se utiliza para representar circuitos, redes, etc. Los grafos
son muy utilizados en computacin, ya que permiten resolver problemas muy complejos.

Imaginemos que disponemos de una serie de ciudades y de carreteras que las unen. De cada
ciudad saldrn varias carreteras, por lo que para ir de una ciudad a otra se podrn tomar diversos
caminos. Cada carretera tendr un coste asociado (por ejemplo, la longitud de la misma). Gracias a
la representacin por grafos podremos elegir el camino ms corto que conecta dos ciudades,
determinar si es posible llegar de una ciudad a otra, si desde cualquier ciudad existe un camino
que llegue a cualquier otra, etc.

El estudio de grafos es una rama de la algoritmia muy importante. Estudiaremos primero sus
rasgos generales y sus recorridos fundamentales, para tener una buena base que permita
comprender los algoritmos que se pueden aplicar.

Glosario

Un grafo consta de vrtices (o nodos) y aristas. Los vrtices son objetos que contienen informacin
y las aristas son conexiones entre vrtices. Para representarlos, se suelen utilizar puntos para los
vrtices y lneas para las conexiones, aunque hay que recordar siempre que la definicin de un
grafo no depende de su representacin.

Un camino entre dos vrtices es una lista de vrtices en la que dos elementos sucesivos estn
conectados por una arista del grafo. As, el camino AJLOE es un camino que comienza en el vrtice
A y pasa por los vrtices J,L y O (en ese orden) y al final va del O al E. El grafo ser conexo si existe
un camino desde cualquier nodo del grafo hasta cualquier otro. Si no es conexo constar de
varias componentes conexas.

Un camino simple es un camino desde un nodo a otro en el que ningn nodo se repite (no se pasa
dos veces). Si el camino simple tiene como primer y ltimo elemento al mismo nodo se
denomina ciclo. Cuando el grafo no tiene ciclos tenemos un rbol (ver rboles). Varios rboles
independientes forman un bosque. Un rbol de expansin de un grafo es una reduccin del grafo
en el que solo entran a formar parte el nmero mnimo de aristas que forman un rbol y conectan
a todos los nodos.

Segn el nmero de aristas que contiene, un grafo es completo si cuenta con todas las aristas
posibles (es decir, todos los nodos estn conectados con todos), disperso si tiene relativamente
pocas aristas y denso si le faltan pocas para ser completo.

Las aristas son la mayor parte de las veces bidireccionales, es decir, si una arista conecta dos nodos
A y B se puede recorrer tanto en sentido hacia B como en sentido hacia A: estos son
llamados grafos no dirigidos. Sin embargo, en ocasiones tenemos que las uniones son
unidireccionales. Estas uniones se suelen dibujar con una flecha y definen un grafo dirigido.
Cuando las aristas llevan un coste asociado (un entero al que se denomina peso) el grafo
es ponderado. Una red es un grafo dirigido y ponderado.
Representacin de grafos

Una caracterstica especial en los grafos es que podemos representarlos utilizando dos estructuras
de datos distintas. En los algoritmos que se aplican sobre ellos veremos que adoptarn tiempos
distintos dependiendo de la forma de representacin elegida. En particular, los tiempos de
ejecucin variarn en funcin del nmero de vrtices y el de aristas, por lo que la utilizacin de
una representacin u otra depender en gran medida de si el grafo es denso o disperso.

Para nombrar los nodos utilizaremos letras maysculas, aunque en el cdigo deberemos hacer
corresponder cada nodo con un entero entre 1 y V (nmero de vrtices) de cara a la manipulacin
de los mismos.

Representacin por matriz de adyacencia

Es la forma ms comn de representacin y la ms directa. Consiste en una tabla de tamao V x V,


en que la que a[i][j] tendr como valor 1 si existe una arista del nodo i al nodo j. En caso contrario,
el valor ser 0. Cuando se trata de grafos ponderados en lugar de 1 el valor que tomar ser el
peso de la arista. Si el grafo es no dirigido hay que asegurarse de que se marca con un 1 (o con el
peso) tanto la entrada a[i][j] como la entrada a[j][i], puesto que se puede recorrer en ambos
sentidos.

int V,A;

int a[maxV][maxV];

void inicializar()

int i,x,y,p;

char v1,v2;

// Leer V y A

memset(a,0,sizeof(a));

for (i=1; i<=A; i++)

scanf("%c %c %d\n",&v1,&v2,&p);

x=v1-'A'; y=v2-'A';

a[x][y]=p; a[y][x]=p;

}
}

En esta implementacin se ha supuesto que los vrtices se nombran con una letra mayscula y no
hay errores en la entrada. Evidentemente, cada problema tendr una forma de entrada distinta y
la inicializacin ser conveniente adaptarla a cada situacin. En todo caso, esta operacin es
sencilla si el nmero de nodos es pequeo. Si, por el contrario, la entrada fuese muy grande se
pueden almacenar los nombres de nodos en un rbol binario de bsqueda o utilizar una tabla de
dispersin, asignando un entero a cada nodo, que ser el utilizado en la matriz de adyacencia.

Como se puede apreciar, la matriz de adyacencia siempre ocupa un espacio de V*V, es decir,
depende solamente del nmero de nodos y no del de aristas, por lo que ser til para representar
grafos densos.

Representacin por lista de adyacencia

Otra forma de representar un grafo es por medio de listas que definen las aristas que conectan los
nodos. Lo que se hace es definir una lista enlazada para cada nodo, que contendr los nodos a los
cuales es posible acceder. Es decir, un nodo A tendr una lista enlazada asociada en la que
aparecer un elemento con una referencia al nodo B si A y B tienen una arista que los une.
Obviamente, si el grafo es no dirigido, en la lista enlazada de B aparecer la correspondiente
referencia al nodo A.

Las listas de adyacencia sern estructuras que contendrn un valor entero (el nmero que
identifica al nodo destino), as como otro entero que indica el coste en el caso de que el grafo sea
ponderado. En el ejemplo se ha utilizado un nodo z ficticio en la cola (ver listas, apartado
cabeceras y centinelas).

struct nodo

int v;

int p;

nodo *sig;

};

int V,A; // vrtices y aristas del grafo

struct nodo *a[maxV], *z;

void inicializar()

{
int i,x,y,peso;

char v1,v2;

struct nodo *t;

z=(struct nodo *)malloc(sizeof(struct nodo));

z->sig=z;

for (i=0; i<V; i++)

a[i]=z;

for (i=0; i<A; i++)

scanf("%c %c %d\n",&v1,&v2,&peso);

x=v1-'A'; y=v2-'A';

t=(struct nodo *)malloc(sizeof(struct nodo));

t->v=y; t->p=peso; t->sig=a[x]; a[x]=t;

t=(struct nodo *)malloc(sizeof(struct nodo));

t->v=x; t->p=peso; t->sig=a[y]; a[y]=t;

En este caso el espacio ocupado es O(V + A), muy distinto del necesario en la matriz de adyacencia,
que era de O(V2). La representacin por listas de adyacencia, por tanto, ser ms adecuada para
grafos dispersos.

Hay que tener en cuenta un aspecto importante y es que la implementacin con listas enlazadas
determina fuertemente el tratamiento del grafo posterior. Como se puede ver en el cdigo, los
nodos se van aadiendo a las listas segn se leen las aristas, por lo que nos encontramos que un
mismo grafo con un orden distinto de las aristas en la entrada producir listas de adyacencia
diferentes y por ello el orden en que los nodos se procesen variar. Una consecuencia de esto es
que si un problema tiene varias soluciones la primera que se encuentre depender de la entrada
dada. Podra presentarse el caso de tener varias soluciones y tener que mostrarlas siguiendo un
determinado orden. Ante una situacin as podra ser muy conveniente modificar la forma de
meter los nodos en la lista (por ejemplo, hacerlo al final y no al principio, o incluso insertarlo en
una posicin adecuada), de manera que el algoritmo mismo diera las soluciones ya ordenadas.
Exploracin de grafos

A la hora de explorar un grafo, nos encontramos con dos mtodos distintos. Ambos conducen al
mismo destino (la exploracin de todos los vrtices o hasta que se encuentra uno determinado), si
bien el orden en que stos son "visitados" decide radicalmente el tiempo de ejecucin de un
algoritmo, como se ver posteriormente.

En primer lugar, una forma sencilla de recorrer los vrtices es mediante una funcin recursiva, lo
que se denomina bsqueda en profundidad. La sustitucin de la recursin (cuya base es la
estructura de datos pila) por una cola nos proporciona el segundo mtodo de bsqueda o
recorrido, la bsqueda en amplitud o anchura.

Suponiendo que el orden en que estn almacenados los nodos en la estructura de datos
correspondiente es A-B-C-D-E-F... (el orden alfabtico), tenemos que el orden que seguira el
recorrido en profundidad sera el siguiente:

A-B-E-I-F-C-G-J-K-H-D

En un recorrido en anchura el orden sera, por contra:

A-B-C-D-E-G-H-I-J-K-F

Es decir, en el primer caso se exploran primero los verdes y luego los marrones, pasando primero
por los de mayor intensidad de color. En el segundo caso se exploran primero los verdes, despus
los rojos, los naranjas y, por ltimo, el rosa.

Es destacable que el nodo D es el ltimo en explorarse en la bsqueda en profundidad pese a ser


adyacente al nodo de origen (el A). Esto es debido a que primero se explora la rama del nodo C,
que tambin conduce al nodo D.

En estos ejemplos hay que tener en cuenta que es fundamental el orden en que los nodos estn
almacenados en las estructuras de datos. Si, por ejemplo, el nodo D estuviera antes que el C, en la
bsqueda en profundidad se tomara primero la rama del D (con lo que el ltimo en visitarse sera
el C), y en la bsqueda en anchura se explorara antes el H que el G.
Bsqueda en profundidad

Se implementa de forma recursiva, aunque tambin puede realizarse con una pila. Se utiliza un
array val para almacenar el orden en que fueron explorados los vrtices. Para ello se incrementa
una variable global id (inicializada a 0) cada vez que se visita un nuevo vrtice y se almacena id en
la entrada del array val correspondiente al vrtice que se est explorando.

La siguiente funcin realiza un mximo de V (el nmero total de vrtices) llamadas a la


funcin visitar, que implementamos aqu en sus dos variantes: representacin por matriz de
adyacencia y por listas de adyacencia.

int id=0;

int val[V];

void buscar()

int k;

for (k=1; k<=V; k++)

val[k]=0;

for (k=1; k<=V; k++)

if (val[k]==0) visitar(k);

void visitar(int k) // matriz de adyacencia

int t;

val[k]=++id;

for (t=1; t<=V; t++)

if (a[k][t] && val[t]==0) visitar(t);

void visitar(int k) // listas de adyacencia

struct nodo *t;


val[k]=++id;

for (t=a[k]; t!=z; t=t->sig)

if (val[t->v]==0) visitar(t->v);

El resultado es que el array val contendr en su i-sima entrada el orden en el que el vrtice i-
simo fue explorado. Es decir, si tenemos un grafo con cuatro nodos y fueron explorados en el
orden 3-1-2-4, el array val quedar como sigue:

val[1]=2; // el primer nodo fue visto en segundo lugar

val[2]=3; // el segundo nodo fue visto en tercer lugar

val[3]=1; // etc.

val[4]=4;

Una modificacin que puede resultar especialmente til es la creacin de un array "inverso" al
array val que contenga los datos anteriores "al revs". Esto es, un array en el que la entrada i-
sima contiene el vrtice que se explor en i-simo lugar. Basta modificar la lnea

val[k]=++id;

sustituyndola por

val[++id]=k;

Para el orden de exploracin de ejemplo anterior los valores seran los siguientes:

val[1]=3;

val[2]=1;

val[3]=2;

val[4]=4;

Bsqueda en amplitud o anchura

La diferencia fundamental respecto a la bsqueda en profundidad es el cambio de estructura de


datos: una cola en lugar de una pila. En esta implementacin, la funcin del array val y la
variable id es la misma que en el mtodo anterior.

struct tcola *cola;

void visitar(int k) // listas de adyacencia


{

struct nodo *t;

encolar(&cola,k);

while (!vacia(cola))

desencolar(&cola,&k);

val[k]=++id;

for (t=a[k]; t!=z; t=t->sig)

if (val[t->v]==0)

encolar(&cola,t->v);

val[t->v]=-1;

Definiciones Bsicas

Un grafo consiste de un conjunto V de vrtices (o nodos) y un conjunto E de arcos que conectan a


esos vrtices.

Ejemplos:
V = {v1,v2,v3,v4,v5} V = {v1,v2,v3,v4}
E = { {v1,v2}, {v1,v3}, {v1,v5}, {v2,v3}, {v3,v4}, E = { (v1,v2), (v2,v2), (v2,v3), (v3,v1), (v3,v4),
{v4,v5} } (v4,v3) }

Adems de esto, los grafos pueden ser extendidos mediante la adicin de rtulos (labels) a los
arcos. Estos rtulos pueden representar costos, longitudes, distancias, pesos, etc.

Representaciones de grafos en memoria

Matriz de adyacencia

Un grafo se puede representar mediante una matriz A tal que A[i,j]=1 si hay un arco que
conecta vi con vj, y 0 si no. La matriz de adyacencia de un grafo no dirigido es simtrica.

Una matriz de adyacencia permite determinar si dos vrtices estn conectados o no en tiempo
constante, pero requieren O(n2) bits de memoria. Esto puede ser demasiado para muchos grafos
que aparecen en aplicaciones reales, en donde |E|<<n2. Otro problema es que se requiere
tiempo O(n) para encontrar la lista de vecinos de un vrtice dado.

Listas de adyacencia

Esta representacin consiste en almacenar, para cada nodo, la lista de los nodos adyacentes a l.
Para el segundo ejemplo anterior,

v1: v2

v2: v2, v3

v3: v1, v4

v4: v3

Esto utiliza espacio O(|E|) y permite acceso eficiente a los vecinos, pero no hay acceso aleatorio a
los arcos.

Caminos, ciclos y rboles


Un camino es una secuencia de arcos en que el extremo final de cada arco coincide con el extremo
inicial del siguiente en la secuencia.

Un camino es simple si no se repiten vrtices, excepto posiblemente el primero y el ltimo.

Un ciclo es un camino simple y cerrado.

Un grafo es conexo si desde cualquier vrtice existe un camino hasta cualquier otro vrtice del
grafo.

Se dice que un grafo no dirigido es un rbol si es conexo y acclico.


Recorridos de Grafos

En muchas aplicaciones es necesario visitar todos los vrtices del grafo a partir de un nodo dado.
Algunas aplicaciones son:

Encontrar ciclos

Encontrar componentes conexas

Encontrar rboles cobertores

Hay dos enfoque bsicos:

Recorrido (o bsqueda) en profundidad (depth-first search):


La idea es alejarse lo ms posible del nodo inicial (sin repetir nodos), luego devolverse un paso e
intentar lo mismo por otro camino.

Recorrido (o bsqueda) en amplitud (breadth-first search):


Se visita a todos los vecinos directos del nodo inicial, luego a los vecinos de los vecinos, etc.

Recorrido en Profundidad (DFS)

A medida que recorremos el grafo, iremos numerando correlativamente los nodos encontrados
(1,2,...). Suponemos que todos estos nmeros son cero inicialmente, y utilizamos un contador
global n, tambin inicializado en cero.

El siguiente algoritmo en seudo-cdigo muestra cmo se puede hacer este tipo de recorrido
recursivamente:

DFS(v) // recorre en profundidad a partir del vrtice v

++n;

DFN[v]=n;

for(todo w tal que {v,w} est en E y DFN[w]==0)


DFS(w);

Para hacer un recorrido en profundidad a partir del nodo v, utilizamos el siguiente programa
principal:

n=0;

for(todo w)

DFN[w]=0;

DFS(v);

Si hubiera ms de una componente conexa, esto no llegara a todos los nodos. Para esto podemos
hacer:

n=0;

ncc=0; // nmero de componentes conexas

for(todo w)

DFN[w]=0;

while(existe v en V con DFN[v]==0)

++ncc;

DFS(v);

Ejercicio: Cmo utilizar este algoritmo para detectar si un grafo es acclico?

Tambin es posible implementar un recorrido no recursivo utilizando una pila:

Pila pila=new Pila();

n=0;

for(todo w)

DFN[w]=0;

pila.apilar(v); // para recorrer a partir de v

while(!pila.estaVacia())

v=pila.desapilar();

if(DFN[v]==0) // primera vez que se visita este nodo


{

++n;

DFN[v]=n;

for(todo w tal que {v,w} esta en E)

pila.apilar(w);

Recorrido en amplitud (BFS)

La implementacin es similar a la de DFS, pero se utiliza una cola en lugar de una pila.

El resultado es que los nodos se visitan en orden creciente en relacin a su distancia al nodo
origen.

rboles cobertores

Dado un grafo G no dirigido, conexo, se dice que un subgrafo T de G es un rbol cobertor si es un


rbol y contiene el mismo conjunto de nodos que G.

Todo recorrido de un grafo conexo genera un rbol cobertor, consistente del conjunto de los arcos
utilizados para llegar por primera vez a cada nodo.

Para un grafo dado pueden existir muchos rboles cobertores. Si introducimos un concepto de
"peso" (o "costo") sobre los arcos, es interesante tratar de encontrar un rbol cobertor que tenga
costo mnimo.

rbol Cobertor Mnimo

En esta seccin veremos dos algoritmos para encontrar un rbol cobertor mnimo para un grafo no
dirigido dado, conexo y con costos asociados a los arcos. El costo de un rbol es la suma de los
costos de sus arcos.
Algoritmo de Kruskal

Este es un algoritmo del tipo "avaro" ("greedy"). Comienza inicialmente con un grafo que contiene
slo los nodos del grafo original, sin arcos. Luego, en cada iteracin, se agrega al grafo el arco ms
barato que no introduzca un ciclo. El proceso termina cuando el grafo est completamente
conectado.

En general, la estrategia "avara" no garantiza que se encuentre un ptimo global, porque es un


mtodo "miope", que slo optimiza las decisiones de corto plazo. Por otra parte, a menudo este
tipo de mtodos proveen buenas heursticas, que se acercan al ptimo global.

En este caso, afortunadamente, se puede demostrar que el mtodo "avaro" logra siempre
encontrar el ptimo global, por lo cual un rbol cobertor encontrado por esta va est garantizado
que es un rbol cobertor mnimo.

Una forma de ver este algoritmo es diciendo que al principio cada nodo constituye su propia
componente conexa, aislado de todos los dems nodos. Durante el proceso de construccin del
rbol, se agrega un arco slo si sus dos extremos se encuentran en componentes conexas
distintas, y luego de agregarlo esas dos componentes conexas se fusionan en una sola.

Para la operatoria con componentes conexas supondremos que cada componente conexa se
identifica mediante un representante cannico (el "lider" del conjunto), y que se dispone de las
siguientes operaciones:

Union(a,b): Se fusionan las componentes cannicas representadas por a y b, respectivamente.

Find(x): Encuentra al representante cannico de la componente conexa a la cual pertenece x.

Con estas operaciones, el algoritmo de Kruskal se puede escribir as:

Ordenar los arcos de E en orden creciente de costo;

C = { {v} | v est en V }; // El conjunto de las componentes conexas

while( |C| > 1 )

Sea e={v,w} el siguiente arco en orden de costo creciente;

if(Find(v)!=Find(w))

Agregar e al rbol;

Union(Find(v), Find(w));

}
El tiempo que demora este algoritmo est dominado por lo que demora la ordenacin de los
arcos. Si |V|=n y |E|=m, el tiempo es O(m log m) ms lo que demora
realizar moperaciones Find ms n operaciones Union.

Es posible implementar Union-Find de modo que las operaciones Union demoran tiempo
constante, y las operaciones Find un tiempo casi constante. Ms precisamente, el costo
amortizado de un Find est acotado por log* n, donde log* n es una funcin definida como el
nmero de veces que es necesario tomar el logaritmo de un nmero para que el resultado sea
menor que 1.

Por lo tanto, el costo total es O(m log m) o, lo que es lo mismo, O(m log n) (por qu?).

Ejemplo:

La correctitud del algoritmo de Kruskal viene del siguiente lema:

Lema

Sea V' subconjunto propio de V, y sea e={v,w} un arco de costo mnimo tal que v est
en V' y w est en V-V'. Entonces existe un rbol cobertor mnimo que incluye a e.

Este lema permite muchas estrategias distintas para escoger los arcos del rbol. Veamos por
ejemplo la siguiente:

Algoritmo de Prim

Comenzamos con el arco ms barato, y marcamos sus dos extremos como "alcanzables". Luego, a
cada paso, intentamos extender nuestro conjunto de nodos alcanzables agregando el arco ms
barato que tenga uno de sus extremos dentro del conjunto alcanzable y el otro fuera de l.

De esta manera, el conjunto alcanzable se va extendiendo como una "mancha de aceite".

Sea e={v,w} un arco de costo mnimo en E;

Agregar e al rbol;

A={v,w}; // conjunto alcanzable


while(A!=V)

Encontrar el arco e={v,w} ms barato con v en A y w en V-A;

Agregar e al rbol;

Agregar w al conjunto alcanzable A;

Para implementar este algoritmo eficientemente, podemos mantener una tabla donde, para cada
nodo de V-A, almacenamos el costo del arco ms barato que lo conecta al conjunto A. Estos costos
pueden cambiar en cada iteracin.

Si se organiza la tabla como una cola de prioridad, el tiempo total es O(m log n). Si se deja la tabla
desordenada y se busca linealmente en cada iteracin, el costo es O(n2). Esto ltimo es mejor que
lo anterior si el grafo es denso.

Distancias Mnimas en un Grafo Dirigido

En este problema los rtulos de los arcos se interpretan como distancias. La distancia (o largo) de
un camino es la suma de los largos de los arcos que lo componen.

El problema de encontrar los caminos ms cortos tiene dos variantes:

Caminos ms cortos desde un nodo "origen"


Encontrar los n caminos ms cortos desde un nodo dado s hasta todos los nodos del grafo.

Todos los caminos ms cortos


Encontrar los n2 caminos ms cortos entre todos los pares de nodos (origen, destino). Esto se
puede resolver resolviendo n veces el problema anterior, cambiando cada vez de nodo origen,
pero puede ser ms eficiente encontrar todos los caminos de una sola vez.

Algoritmo de Dijkstra para los caminos ms cortos desde un nodo "origen"

La idea del algoritmo es mantener un conjunto A de nodos "alcanzables" desde el nodo origen e ir
extendiendo este conjunto en cada iteracin.

Los nodos alcanzables son aquellos para los cuales ya se ha encontrado su camino ptimo desde el
nodo origen. Para esos nodos su distancia ptima al origen es conocida. Inicialmente A={s}.

Para los nodos que no estn en A se puede conocer el camino ptimo desde s que pasa slo por
nodos de A. Esto es, caminos en que todos los nodos intermedios son nodos de A. Llamemos a
esto su camino ptimo tentativo.

En cada iteracin, el algoritmo encuentra el nodo que no est en A y cuyo camino ptimo
tentativo tiene largo mnimo. Este nodo se agrega a A y su camino ptimo tentativo se convierte
en su camino ptimo. Luego se actualizan los caminos ptimos tentativos para los dems nodos.

El algoritmo es el siguiente:
A={s};

D[s]=0;

D[v]=cost(s,v) para todo v en V-A; // infinito si el arco no existe

while(A!=V)

Encontrar v en V-A tal que D[v] es mnimo;

Agregar v a A;

for(todo w tal que (v,w) est en E)

D[w]=min(D[w],D[v]+cost(v,w));

Implementaciones:

Usando una cola de prioridad para la tabla D el tiempo es O(m log n).

Usando un arreglo con bsqueda secuencial del mnimo el tiempo es O(n2).

Ejemplo:

Algoritmo de Floyd para todas las distancias ms cortas

Para aplicar este algoritmo, los nodos se numeran arbitrariamente 1,2,...n. Al comenzar la
iteracin k-sima se supone que una matriz D[i,j] contiene la distancia mnima entre i y jmedida a
travs de caminos que pasen slo por nodos intermedios de numeracin <k.

Estas distancias se comparan con las que se obtendran si se pasara una vez por el nodo k, y si de
esta manera se obtendra un camino ms corto entonces se prefiere este nuevo camino, de lo
contrario nos quedamos con el nodo antiguo.
Al terminar esta iteracin, las distancias calculadas ahora incluyen la posibilidad de pasar por
nodos intermedios de numeracin <=k, con lo cual estamos listos para ir a la iteracin siguiente.

Para inicializar la matriz de distancias, se utilizan las distancias obtenidas a travs de un arco
directo entre los pares de nodos (o infinito si no existe tal arco). La distancia inicial entre un nodo y
s mismo es cero.

for(1<=i,j<=n)

D[i,j]=cost(i,j); // infinito si no hay arco entre i y j

for(1<=i<=n)

D[i,i]=0;

for(k=1,...,n)

for(1<=i,j<=n)

D[i,j]=min(D[i,j], D[i,k]+D[k,j]);

El tiempo total que demora este algoritmo es O(n3).

Algoritmo de Warshall para cerradura transitiva

Dada la matriz de adyacencia de un grafo (con D[i,j]=1 si hay un arco entre i y j, y 0 si no), resulta
til calcular la matriz de conectividad, en que el casillero respectivo contiene un 1 si y slo si existe
un camino (de largo >=0) entre i y j. Esta matriz se llama tambin la cerradura transitiva de la
matriz original.

La matriz de conectividad se puede construir de la siguiente manera:

Primero calcular la matriz de distancias mnimas usando Floyd.

Luego sustituir cada valor infinito por 0 y cada valor no infinito por 1.

Mejor todava, podramos modificar el algoritmo de Floyd para que vaya calculando con ceros y
unos directamente, usando las correspondencias siguientes:

min infinito <infinito or 0 1


infinito infinito <infinito 0 0 1

<infinito <infinito <infinito 1 1 1

+ infinito <infinito and 0 1

infinito infinito infinito 0 0 0

<infinito infinito <infinito 1 0 1

El resultado se denomina Algoritmo de Warshall:

for(1<=i,j<=n)

D[i,j]=adyacente(i,j); // 1 si existe, 0 si no

for(1<=i<=n)

D[i,i]=1;

for(k=1,...,n)

for(1<=i,j<=n)

D[i,j]=D[i,j] or (D[i,k] and D[k,j]);

Si lo que queremos es obtener el camino ms corto entre todas las pareja posibles, lo primero que
se nos puede ocurrir es utilizar el algoritmo de Dijkstra para cada vrtice. Sin embargo, podemos
encontrar algoritmos ms eficientes para este caso.

El algoritmo de Floyd-Warshall (tambin conocido como algoritmo de Roy-Floyd) aprovecha que


los sub-problemas son ptimos, pero desde una perspectiva de programacin dinmica.

Este algoritmo se basa en lo siguiente: supongamos que tenemos la forma de llegar del
vrtice i al j de forma ptima contando con k-1 puntos intermedios. Ahora queremos encontrar la
distancia ms corta agregando un k-simo punto intermedio. Existen dos casos: podemos ir
de i a k y despus a j, o mantener la distancia de i a j que ya tenamos. En ambos estamos
considerando los k -1 puntos como intermedios. Al hacer que k tome el valor de todos los vrtices,
obtenemos la distancia ms corta entre todas las parejas.

Visto como formula, donde dk(i,j) es la distancia mnima para ir de i a j con k puntos, y p(i, j) es el
peso de la arista de i a j, tenemos que:

Si adems de la distancia, queremos encontrar la trayectoria, podemos hacer algo similar a lo que
hicimos con Dijkstra en el problema anterior. La funcin para imprimir esta trayectoria sera la
misma. Tambin podemos encontrar las aristas maxi-min y mini-max utilizando un enfoque
parecido al de Dijkstra.

Problema ejemplo

Risk

Problema

Risk es un juego de mesa en el cual varios jugadores intentan conquistar el mundo. El tablero
consiste en un mapa del mundo dividido en pases hipotticos. Durante el turno de un jugador, el
ejrcito de una nacin puede atacar a un pas con el que comparta frontera. Despus de
conquistar el pas, el ejrcito puede ocuparlo.

Es comn que un jugador empiece una serie de conquistas con el objetivo de transferir parte de su
ejrcito de un pas a otro. Normalmente, es conveniente escoger una ruta en la cual la cantidad de
pases que se tienen que conquistar sea mnima. Dada la descripcin de un tablero con 20 pases,
cada uno con 1 a 19 conexiones a otros pases, tu trabajo es escribir un programa que tome un
pas inicial y uno final, y calcule la cantidad mnima de pases que se tienen que conquistar para
llegar al destino. Por ejemplo, si el pas inicial y el final son vecinos, el programa deber regresar
uno.

El siguiente diagrama ilustra la entrada del ejemplo:


Entrada

La entrada de tu programa consistir en una serie de configuraciones para los pases. Cada caso
consistir en la descripcin de un tablero en las primeras 19 lneas. La representacin evita repetir
fronteras, por lo que slo incluye la frontera de un pas i con uno j cuando i < j. Por lo tanto, la i-
sima lnea, donde i es menor a 20, contiene un entero x que indica cuantos de los pases con
numeracin ms alta comparten frontera con el pas i, seguido con x enteros distintos mayores
a i pero menores a 20, que describen las fronteras entre el pas i y el j. La lnea 20 de cada caso
contiene un entero (1 n 100) ndicando la cantidad de pases que siguen a continuacin. Las
siguientes n lneas contienen exactamente dos enteros (1 a, b 20; a b) indicando los pases
iniciales y finales en una conquista.

Puede haber varios casos en la entrada; tu programa deber de ser capaz de leerlos y procesarlos
hasta llegar al fin de archivo. Existir por lo menos una ruta de un pas a otro en cualquier
configuracin dada.

Salida

Para cada caso en la entrada, tu programa deber imprimir el siguiente mensaje Test Set #t,
donde t es el nmero del caso actual, empezando en 1. Las siguientes nt lneas tendrn el
resultado para el caso correspondiente esto es, la cantidad mnima de pases a conquistar. Para
cada prueba en el caso, se deber empezar escribiendo el nmero del pas a seguido por la cadena
to , el nmero del pas b, la cadena : y terminando un entero que indique la cantidad mnima
de movimientos requeridos para ir del pas a al b en la configuracin dada. Despus de todas
pruebas en un caso, tu programa deber imprimir una lnea en blanco.

Zhejiang: 1221

Solucin: Como el peso de cada arista es unitario, podemos utilizar una bsqueda en amplitud por
cada distancia que nos pidan. Sin embargo, resulta ms fcil implementar Floyd, y como la
cantidad de vrtices son muy pocos la diferencia en tiempo no es significativa.

Cdigo
En las primeras tres lneas declaramos las variables que vamos a utilizar. Para los ciclos
usamos i y j, para contar en que prueba vamos c, t para leer con que vrtice es adyacente el
vrtice actual, n, x, a, y b las utilizamos de acuerdo a la descripcin del problema. La matriz de
adyacencia la guardamos en m.

En el procedimiento Floyd (lneas 5 a 13), calculamos la distancia ms corta entre todos los pares
de vrtices. En cada iteracin del ciclo k, utilizamos al vrtice k como punto intermedio para ir
de i a j. Si utilizndolo como punto intermedio logramos llegar ms rpido, actualizamos la
distancia (lnea 12). Una vez que utilizamos a todos los vrtices como intermedio, obtenemos las
distancias ms cortas para cada par de vrtices.

En seguida tenemos la parte principal del cdigo. En la lnea 16 inicializamos el nmero de casos a
cero. Mientras no encontremos el fin de archivo, procesamos las entradas. Inicializamos la matriz
a infinito, excepto por la diagonales, que van a cero (lneas 19 y 21). Algo importante es que el
valor de infinito debe ser ms grande que cualquier distancia que podamos obtener pero no
demasiado grande, ya que podemos causar un desbordamiento en la lnea 11 y 12. Tambin
debemos recordar que la funcin fillchar asigna el valor a cada byte, no a cada casilla; por lo que
en cada casilla tenemos el valor de 0x01010101 = 16843009, y no 1 como pudiera parecer.

Entre las lneas 22 y 31 leemos la conexin entre pases. Como los caminos pueden ser transitados
en ambas direcciones, asignamos dos valores a la matriz de adyacencia (lnea 28). Despus
utilizamos Floyd para calcular las distancias mnimas (lnea 32). Por ltimo, escribimos cual caso
estamos procesando (lnea 34), leemos las parejas de vrtices, y para cada una de ellas,
imprimimos la distancia mnima (lneas 38 y 39).

Caso ejemplo

3234
13
14
15
5
Entrada:
14
25
32
43
51

m[]
0 1 1 1
1 0 1
1 1 0 1
1 1 0 1
Desarrollo: 1 0

k= 1 k= 2 k= 3 k= 4 k= 5
0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 2 0 1 1 1 2
1 0 1 2 1 0 1 2 1 0 1 2 1 0 1 2 3 1 0 1 2 3
1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 2 1 1 0 1 2
1 2 1 0 1 1 2 1 0 1 1 2 1 0 1 1 2 1 0 1 1 2 1 0 1
1 0 1 0 1 0 2 3 2 1 0 2 3 2 1 0

Test Set #1
1 to 4: 1
2 to 5: 3
Salida:
3 to 2: 1
4 to 3: 1
5 to 1: 2

Nota: Slo utilizamos 5 vrtices para poder mostrar todo el proceso. El signo de infinito tiene un
valor de 16843009 durante la ejecucin.

Informacin Adicional

Es claro que el tiempo de ejecucin de Floyd es de O(V3). Para grficas dispersas existe un
algoritmo conocido como el algoritmo de Johnson, el cual tiene un tiempo de ejecucin de
O(V2lgV + VE).

El algoritmo de Floyd tambin puede ser utilizado para encontrar la cerradura transitiva, en cuyo
caso se le conoce como el algoritmo de Warshall.. Lo que busca la cerradura transitiva es saber si
se puede alcanzar un vrtice j a partir del vrtice i. Utilizando Floyd, si el camino ms corto
de i a j es menor a infinito es porque si existe un camino por el que podemos llegar. Sin embargo,
podemos hacer algunos cambios para eficientar los clculos.

Aqu m sigue siendo la matriz de adyacencia pero en lugar de enteros utilizamos booleanos
(aunque tambin funciona con enteros). El vrtice j lo podemos alcanzar desde i si ya podamos
llegar desde i hasta j (parte izquierda de la operacin), o si podemos llegar desde i a j pasando a
travs de k (parte derecha).

Cdigo en C
2.2 Problema Camino Ms Corto Abril 9, 2010

Los problemas conocidos como problemas del camino mnimo o camino ms corto, tratan como su
nombre indica de hallar la ruta mnima o ms corta entre dos puntos. Este mnimo puede ser la
distancia entre los puntos origen y destino o bien el tiempo transcurrido para trasladarse desde un
punto a otro. Se aplica mucho para problemas de redes de comunicaciones.

Este tipo de problemas pueden ser resueltos por el mtodo del Simplex, sin embargo existen otros
mtodos ms eficientes como por ejemplo el algoritmo de Dijkstra o el de Bellman-Ford.

Este algoritmo calcula el camino mnimo de un nodo a a otro nodo z en particular, a la vez que
calcula los caminos mnimos desde el nodo inicial a dado hasta cada uno de los otros nodos del
grafo.

Consiste en encontrar la ruta ms corta entre dos nodos dados de un grafo dirigido y valuado (con
capacidades). Veremos dos algoritmos, por un lado el algoritmo de Dijkstra, que encuentra el
camino ms corto entre el nodo origen y cada uno de los otros nodos de la red, y por otro lado el
algoritmo de Floyd, que encuentra el camino ms corto entre cualquier par de nodos de la red.

Algoritmo de DIJKSTRA

Tendremos a lo largo de todo el proceso dos conjuntos y dos vectores:

Conjunto C : Conjunto de vrtices candidatos. Inicialmente contiene todos los nodos menos el
nodo origen.

Conjunto S : Conjunto de vrtices seleccionados, es decir, aquellos para los que ya conocemos su
camino mnimo desde el nodo origen. Inicialmente contiene el nodo origen.

Vector D : Almacenar la longitud del camino ms corto desde el origen a cada nodo. Tendr
tantas posiciones como nodos tenga el grafo. El coste de ir del nodo origen a s mismo lo
estimaremos como cero.

Vector P : Almacenar el nodo predecesor a cada nodo en el camino ms corto desde el origen
hasta l. Tendr tantas posiciones como nodos tenga el grafo. La posicin del nodo predecesor al
origen estableceremos que sea cero para indicar que es el nodo origen.

Llamaremos al nodo origen o, y el coste de ir del nodo i al nodo j lo denotaremos como COSTEij .

Hay que seguir los siguientes pasos:

Seleccionamos el nodo que sea destino de la arista con menor valor que salga del nodo o,
llammoslo u. Introducimos el nodo u en S y lo sacamos de C. Almacenamos en la posicin u del
vector D el valor COSTEou y en la posicin u del vector P el valor del nodo predecesor, es decir, o.

Seleccionamos el siguiente nodo al que podamos llegar con menor coste, bien directamente
desde o, bien a travs del otro nodo seleccionado u. Llamamos al nuevo nodo seleccionado v.
Introducimos el nodo v en S y lo sacamos de C. Introducimos en la posicin v del vector D el coste
de llegar al nodo v, si es directamente desde o ser COSTEov, si es a travs
de u ser D[u]+COSTEuv. Por ltimo, en la posicin v del vector P introducimos el valor del nodo
predecesor, ya sea o o u.

Repetiremos este proceso hasta que todos los nodos hayan sido seleccionados, es decir, hasta que
el conjunto C est vaco, o lo que es lo mismo, hasta que en el conjunto S se encuentren todos los
nodos del grafo. En ese momento en el vector D tendremos almacenado el coste mnimo para
llegar desde el nodo origen a cualquier nodo del grafo, y podremos obtener el camino ms corto
mediante el vector P.

Ejemplo: Aplicar el algoritmo de Dijkstra sobre el siguiente grafo siendo el nodo origen el 1:
Algoritmo DE FLOYD

El algoritmo de Floyd es ms general que el de Dijkstra, ya que determina la ruta ms corta entre
dos nodos cualquiera de la red.

El algoritmo representa una red de n nodos como una matriz cuadrada de orden n, la llamaremos
matriz C. De esta forma, el valor Cijrepresenta el coste de ir desde el nodo i al nodo j, inicialmente
en caso de no existir un arco entre ambos, el valor Cij ser infinito.

Definiremos otra matriz D, tambin cuadrada de orden n, cuyos elementos van a ser los nodos
predecesores en el camino hacia el nodo origen, es decir, el valor Dij representar el nodo
predecesor a j en el camino mnimo desde i hasta j. Inicialmente se comienza con caminos de
longitud 1, por lo que Dij = i.

Las diagonales de ambas matrices representan el coste y el nodo predecesor para ir de un nodo a
si mismo, por lo que no sirven para nada, estarn bloqueadas.

Los pasos a dar en la aplicacin del algoritmo de Floyd son los siguientes:

Formar las matrices iniciales C y D.


Se toma k=1.

Se selecciona la fila y la columna k de la matriz C y entonces, para i y j, con ik, jk e ij, hacemos:

Si (Cik + Ckj) < Cij Dij = Dkj y Cij = Cik + Ckj

En caso contrario, dejamos las matrices como estn.

Si k n, aumentamos k en una unidad y repetimos el paso anterior, en caso contrario paramos las
iteraciones.

La matriz final C contiene los costes ptimos para ir de un vrtice a otro, mientras que la
matriz D contiene los penltimos vrtices de los caminos ptimos que unen dos vrtices, lo cual
permite reconstruir cualquier camino ptimo para ir de un vrtice a otro.

Ejemplo: Aplicar el algoritmo de Floyd sobre el siguiente grafo para obtener las rutas ms cortas
entre cada dos nodos. El arco (3, 5) es direccional, de manera que no est permitido ningn trfico
del nodo 5 al nodo 3 . Todos los dems arcos permiten el trfico en ambas direcciones.

Inicializamos las matrices :


Tomamos k=1:

Tomamos i=2 (i k):

j=3 (jk, ji): C[2,1]+C[1,3] = 3+10 = 13 < C[2,3] = , por tanto hacemos:

C[2,3] = 13 y D[2,3] = 1.

j=4 (jk, ji): C[2,1]+C[1,4] = 3+ = , no hay que cambiar nada, no podemos llegar de 2 a 4 a
travs de 1.

j=5 (jk, ji): C[2,1]+C[1,5] = 3+ = , no hay que cambiar nada, no podemos llegar de 2 a 5 a
travs de 1.

Tomamos i=3 (i k):

j=2 (jk, ji): C[3,1]+C[1,2] = 10+3 = 13 < C[3,2] = , por tanto hacemos:

C[3,2] = 13 y D[3,2] = 1.

j=4 (jk, ji): C[3,1]+C[1,4] = 10+ = , no hay que cambiar nada, no podemos llegar de 3 a 4 a
travs de 1.

j=5 (jk, ji): C[3,1]+C[1,5] = 10+ = , no hay que cambiar nada, no podemos llegar de 3 a 5 a
travs de 1.

Tomamos i=4 (i k):

j=2 (jk, ji): C[4,1]+C[1,2] = +3 = , no hay que cambiar nada, no podemos llegar de 4 a 2 a
travs de 1.

j=3 (jk, ji): C[4,1]+C[1,3] = +10 = , no hay que cambiar nada, no podemos llegar de 4 a 3 a
travs de 1.

j=5 (jk, ji): C[4,1]+C[1,5] = + = , no hay que cambiar nada, no podemos llegar de 4 a 5 a
travs de 1.

Tomamos i=5 (i k), en este caso ocurre como en el paso anterior, como C[5,1]=, entonces no
habr ningn cambio, no puede haber ningn camino desde 5 a travs de 1.
Tomamos k=2:

Tomamos i=1:

j=3: C[1,2]+C[2,3] = 3+13 = 16 > C[1,3] = 10 , por tanto no hay que cambiar nada, el camino es
mayor que el existente.

j=4: C[1,2]+C[2,4] = 3+5 = 8 < C[1,4] = , por tanto hacemos:

C[1,4] = 8 y D[1,4] = 2 .

j=5: C[1,2]+C[2,5] = 3+ = , no hay que cambiar nada.

Tomamos i=3:

j=1: C[3,2]+C[2,1] = 13+3 = 16 > C[3,1] = 10, no hay que cambiar nada.

j=4: C[3,2]+C[2,4] = 13+5 = 18 > C[3,4] = 6, no hay que cambiar nada.

j=5: C[3,2]+C[2,5] = 13+ = , no hay que cambiar nada.

Tomamos i=4:

j=1: C[4,2]+C[2,1] = 5+3 = 8 < C[4,1] = , por tanto hacemos:

C[4,1] = 8 y D[4,1] = 2.

j=3: C[4,2]+C[2,3] = 5+13 = 18 > C[4,3] = 6, no hay que cambiar nada.

j=5: C[4,2]+C[2,5] = 5+ = , no hay que cambiar nada.

Tomamos i=5, como C[5,2]=, entonces no habr ningn cambio.


Tomamos k=3:

Tomamos i=1:

j=2: C[1,3]+C[3,2] = 10+13 = 23 > C[1,2] = 3, no hay que cambiar nada.

j=4: C[1,3]+C[3,4] = 10+6 = 16 > C[1,4] = 8, no hay que cambiar nada.

j=5: C[1,3]+C[3,5] = 10+15 = 25 < C[1,5] = , por tanto hacemos:

C[1,5] = 25 y D[1,5] = 3 .

Tomamos i=2:

j=1: C[2,3]+C[3,1] = 13+10 = 23 > C[2,1] = 3, no hay que cambiar nada.

j=4: C[2,3]+C[3,4] = 13+6 = 19 > C[2,4] = 5, no hay que cambiar nada.

j=5: C[2,3]+C[3,5] = 13+15 = 28 < C[2,5] = , por tanto hacemos:

C[2,5] = 28 y D[2,5] = 3.

Tomamos i=4:

j=1: C[4,3]+C[3,1] = 6+10 = 16 > C[4,1] = 8, no hay que cambiar nada.

j=2: C[4,3]+C[3,2] = 6+13 = 19 > C[4,2] = 5, no hay que cambiar nada.

j=5: C[4,3]+C[3,5] = 6+15 = 21 > C[4,5] = 4, no hay que cambiar nada.

Tomamos i=5, como C[5,3]=, entonces no habr ningn cambio.


Tomamos k=4:

Tomamos i=1:

j=2: C[1,4]+C[4,2] = 8+5 = 13 > C[1,2] = 3, no hay que cambiar nada.

j=3: C[1,4]+C[4,3] = 8+6 = 14 > C[1,3] = 10, no hay que cambiar nada.

j=5: C[1,4]+C[4,5] = 8+4 = 12 < C[1,5] = 25, por tanto hacemos:

C[1,5] = 12 y D[1,5] = 4.

Tomamos i=2:

j=1: C[2,4]+C[4,1] = 5+8 = 13 > C[2,1] = 3, no hay que cambiar nada.

j=3: C[2,4]+C[4,3] = 5+6 = 11 < C[2,3] = 13, por tanto hacemos:

C[2,3] = 11 y D[2,3] = 4.

j=5: C[2,4]+C[4,5] = 5+4 = 9 < C[2,5] = 28, por tanto hacemos:

C[2,5] = 9 y D[2,5] = 4.

Tomamos i=3:

j=1: C[3,4]+C[4,1] = 6+8 = 14 > C[3,1] = 10, no hay que cambiar nada.

j=2: C[3,4]+C[4,2] = 6+5 = 11 < C[3,2] = 13, por tanto hacemos:

C[3,2] = 11 y D[3,2] = 4.

j=5: C[3,4]+C[4,5] = 6+4 = 10 < C[3,5] = 15, por tanto hacemos:

C[3,5] = 10 y D[3,5] = 4.

Tomamos i=5:

j=1: C[5,4]+C[4,1] = 4+8 = 12 < C[5,1] = , por tanto hacemos:

C[5,1] = 12 y D[5,1] = 2 .

j=2: C[5,4]+C[4,2] = 4+5 = 9 < C[5,2] = , por tanto hacemos:


C[5,2] = 9 y D[5,2] = 4.

j=3: C[5,4]+C[4,3] = 4+6 = 10 < C[5,3] = , por tanto hacemos:

C[5,3] = 10 y D[5,3] = 4.

Tomamos k=5:

Tomamos i=1:

j=2: C[1,5]+C[5,2] = 12+9 = 21 > C[1,2] = 3, no hay que cambiar nada.

j=3: C[1,5]+C[5,3] = 12+10 = 22 > C[1,3] = 10, no hay que cambiar nada.

j=4: C[1,5]+C[5,4] = 12+4 = 16 > C[1,4] = 8, no hay que cambiar nada.

Tomamos i=2:

j=1: C[2,5]+C[5,1] = 9+12 = 21 > C[2,1] = 3, no hay que cambiar nada.

j=3: C[2,5]+C[5,3] = 9+10 = 19 > C[2,3] = 11, no hay que cambiar nada.

j=4: C[2,5]+C[5,4] = 9+4 = 13 > C[2,4] = 5, no hay que cambiar nada.

Tomamos i=3:

j=1: C[3,5]+C[5,1] = 10+12 = 22 > C[3,1] = 10, no hay que cambiar nada.

j=2: C[3,5]+C[5,2] = 10+9 = 19 > C[3,2] = 11, no hay que cambiar nada.

j=4: C[3,5]+C[5,4] = 10+4 = 14 > C[3,4] = 6, no hay que cambiar nada.

Tomamos i=4:

j=1: C[4,5]+C[5,1] = 4+12 = 16 > C[4,1] = 8 , no hay que cambiar nada.

j=2: C[4,5]+C[5,2] = 4+9 = 13 > C[4,2] = 5 , no hay que cambiar nada.

j=3: C[4,5]+C[5,3] = 4+10 = 14 > C[4,3] = 6, no hay que cambiar nada.


FIN del proceso, las matrices quedan como sigue:

Las matrices finales C y D contienen toda la informacin necesaria para determinar la ruta ms
corta entre dos nodos cualquiera de la red. Por ejemplo, la distancia ms corta del nodo 1 al
nodo 5 es C[1,5] = 12.

Para determinar la ruta asociada del camino mnimo entre el nodo 1 y el nodo 5 haremos lo
siguiente:

Consultamos D[1,5]=4, por tanto el nodo predecesor al 5 es el 4, es decir, 4 5.

Consultamos D[1,4]=2, por tanto el nodo predecesor al 4 es el 2, es decir, 2 4 5.

RBOL DE EXPANSIN MNIMA: ALGORITMO DE KRUSKAL

Publicado el abril 19, 2012| 24 comentarios

Antes de explicar directamente el algoritmo de Kruskal, comenzar dando conceptos sobre que es
un rbol de expansin mnima para entender mejor el problema.

rbol de Expansin

Dado un grafo conexo, no dirigido G. Un rbol de expansin es un rbol compuesto por todos los
vrtices y algunas (posiblemente todas) de las aristas de G. Al ser creado un rbol no existirn
ciclos, adems debe existir una ruta entre cada par de vrtices.

Un grafo puede tener muchos arboles de expansin, veamos un ejemplo con el siguiente grafo:
En la imagen anterior se puede observar que el grafo dado posee 3 arboles de expansin, dichos
arboles cumplen con las propiedades antes mencionadas como son unir todos los vrtices usando
algunas aristas.

rbol de Expansin Mnima

Dado un grafo conexo, no dirigido y con pesos en las aristas, un rbol de expansin mnima es un
rbol compuesto por todos los vrtices y cuya suma de sus aristas es la de menor peso. Al ejemplo
anterior le agregamos pesos a sus aristas y obtenemos los arboles de expansiones siguientes:

De la imagen anterior el rbol de expansin mnima seria el primer rbol de expansin cuyo peso
total es 6.
El problema de hallar el rbol de Expansin Mnima (MST) puede ser resuelto con varios
algoritmos, los mas conocidos con Prim y Kruskal ambos usan tcnicas voraces (greedy).

Algoritmo de Kruskal

Para poder comprender el algoritmo de kruskal ser necesario revisar primer el tutorial de Union-
Find.

Como trabaja:

Primeramente ordenaremos las aristas del grafo por su peso de menor a mayor. Mediante la
tcnica greedy Kruskal intentara unir cada arista siempre y cuando no se forme un ciclo, ello se

realizar mediante Union-Find. Como hemos


ordenado las aristas por peso comenzaremos con la arista de menor peso, si los vrtices que
contienen dicha arista no estn en la misma componente conexa entonces los unimos para
formar una sola componente mediante Union(x , y), para revisar si estn o no en la misma
componente conexa usamos la funcin SameComponent(x , y) al hacer esto estamos evitando que
se creen ciclos y que la arista que une dos vrtices siempre sea la mnima posible.

Algoritmo en Pseudocdigo

1 mtodo Kruskal(Grafo):

2 inicializamos MST como vaco

3 inicializamos estructura unin-find

4 ordenamos las aristas del grafo por peso de menor a mayor.

5 para cada arista e que une los vrtices u y v

6 si u y v no estn en la misma componente

7 agregamos la arista e al MST

8 realizamos la unin de las componentes de u y v

Ejemplo y cdigo paso a paso

Tengamos el siguiente grafo no dirigido:


Como podemos ver en la imagen anterior la definicin de nuestro grafo en cdigo sera:

1 struct Edge{

2 int origen; //Vrtice origen

3 int destino; //Vrtice destino

4 int peso; //Peso entre el vrtice origen y destino

5 Edge(){}

7 }arista[ MAX ]; //Arreglo de aristas para el uso en kruskal

Primeramente usaremos el mtodo MakeSet de unin-find para inicializar cada componente,


obteniendo las siguientes componentes conexas iniciales:

Ahora el siguiente paso es ordenar las aristas del grafo en orden ascendente:
Para el ordenamiento podemos usar las libreras predefinidas de Java y C++ como estamos
ordenando estructuras necesitamos un comparador, en este caso estamos ordenando por peso
por lo tanto dentro de la estructura antes definida agregamos:

1 struct Edge{

3 //Comparador por peso, me servira al momento de ordenar lo realizara en orden ascendente

4 //Cambiar signo a > para obtener el arbol de expansion maxima

5 bool operator<( const Edge &e ) const {

6 return peso < e.peso;

7 }

8 }arista[ MAX ]; //Arreglo de aristas para el uso en kruskal

Ordenamos el arreglo de aristas mediante lo siguiente:

1 std::sort( arista , arista + E ); //Ordenamos las aristas por su comparador

Lo siguiente ser recorrer todas las aristas ya ordenadas y verificar si sus vrtices estn o no en la
misma componente.
La primera arista a verificar es la que une a los vrtices 8 y 7, verificamos si estn en la misma
componente, para ello hacemos Find(8) , Find(7):

Como podemos observar en la tabla y en la misma imagen no estn en la misma componente


conexa, por tanto esta arista es valida para el MST as que unimos los vrtices por el mtodo de
Union( 8 , 7 ).

Continuamos con la siguiente arista:

Observamos la tabla de Union-Find y vemos que Find(3) != Find(9). Entonces es posible realizar la
unin de ambas componentes:
Continuamos con la siguiente arista:

En la imagen
podemos observar que ambos vrtices no estn en la misma componente, por tanto realizamos la
Union( 6 , 7 ):

Continuamos con la siguiente arista, los vrtices 1 y 2 no estn en la misma componente conexa:
Realizamos la Union(1,2):

Continuamos con la siguiente arista:

Al observar la imagen los vrtices 3 y 6 estn en distinta componentes conexas. Entonces


realizamos la Union(3,6) y actualizamos la tabla de Union-Find.
Continuamos con la siguiente arista:

En este caso si
observamos la imagen los vrtices 7 y 9 estn en la misma componente conexa; asimismo en la
tabla de Union-Find el elemento raz del vrtice 7 es el mismo que el del vrtice 9 por ello
afirmamos que estn el a misma componente conexa, por lo tanto no habr que realizar la unin
de ambos vrtices. Con esto evitamos tener ciclos en el rbol de expansin mnima.

Continuamos con la siguiente arista:

Al observar la
imagen los vrtices 3 y 4 no estn en la misma componente conexa por lo tanto realizamos la
Union(3,4) en el grafo:
Continuamos con la siguiente arista:

Los vrtices 8
y 9 estn en la misma componente conexa por lo tanto no realizamos Unin de vrtices.
Continuemos con la siguiente arista:

Los vrtices 1
y 8 estn diferentes componentes. Realizamos la Union(1,8) en el grafo:
Continuamos con la siguiente arista:

Los vrtices 2
y 3 estn en la misma componente conexa por lo tanto no realizamos Union de componentes.
Continuamos con la siguiente arista:

Los vrtices 4 y 7 no estn en la misma componente conexa, realizamos Union(4,5) en el grafo:


Como podemos observar ya estn todos los vrtices del grafo conectados as que al momento de
continuar viendo las dems aristas ordenadas siempre tendremos e l caso de que ya estn en la
misma componente conexa por lo tanto el rbol de Expansin Mnima para el grafo es el
siguiente:

El peso total del rbol de expansin mnima para el grafo mostrado es 39.

En cdigo simplemente es iterar sobre el arreglo de aristas ingresado y ordenado obteniendo sus
respectivos datos. Para verificar si estn o no en la misma componente usamos el mtodo
sameComponent explicado en el tutorial de Union-Find:

1 for( int i = 0 ; i < E ; ++i ){ //Recorremos las aristas ya ordenadas por peso

2 origen = arista[ i ].origen; //Vrtice origen de la arista actual

3 destino = arista[ i ].destino; //Vrtice destino de la arista actual

4 peso = arista[ i ].peso; //Peso de la arista actual

5 //Verificamos si estan o no en la misma componente conexa

6 if( !sameComponent( origen , destino ) ){ //Evito ciclos

7 total += peso; //Incremento el peso total del MST


8 MST[ numAristas++ ] = arista[ i ]; //Agrego al MST la arista actual

9 Union( origen , destino ); //Union de ambas componentes en una sola

10 }

11 }

Verificacin de MST

Para que sea un MST vlido el nmero de aristas debe ser igual al nmero de vrtices 1. Esto se
cumple debido a que el MST debe poseer todos los vrtices del grafo ingresado y adems no
deben existir ciclos. Si vemos el ejemplo antes explicado tenemos en el MST:

Nmero de Aristas = 8

Nmero de Vrtices = 9

Cumple con lo dicho -> 9 1 = 8 por tanto tenemos un MST vlido. Veamos otro ejemplo teniendo
como grafo ingresado lo siguiente:

Como podemos observar el grafo ingresado posee 2 componentes conexas, al aplicar kruskal
obtendremos los siguientes MST:

En la imagen podemos observar el MST luego de aplicar kruskal, sin embargo no es un MST vlido
porque no tiene 1 componente conexa que posea todos los vrtices, comprobemos:

Nmero de Aristas = 7
Nmero de Vrtices = 9

No cumple lo dicho inicialmente 9 1 != 7 por lo tanto tenemos un MST invalido. En cdigo basta
con un if:

1 //Si el MST encontrado no posee todos los vrtices mostramos mensaje de error

2 //Para saber si contiene o no todos los vrtices basta con que el numero

3 //de aristas sea igual al numero de vertices - 1

4 if( V - 1 != numAristas ){

5 puts("No existe MST valido para el grafo ingresado, el grafo debe ser conexo.");

6 return;

7 }

También podría gustarte