Componentes Conexas de Un Grafo
Componentes Conexas de Un Grafo
Componentes Conexas de Un Grafo
Empezamos la primera entrega de tres con una breve introducción a teoría de grafos
antes de meternos en programación. Creo que la introducción es necesaria dado que este
método está muy orientado a tratamiento de grafos y pares de datos, en este caso en
forma de aristas entre dos vértices. Esta introducción es, sin embargo, extremadamente
simple, y no debe echar atrás a nadie.
Pasemos ahora a los grafos. Un grafo es un conjunto de dos elementos, esos dos
elementos son vértices y aristas. Las definiciones de cada uno de ellos son más que
evidentes como para ponerlas, pero vayamos a la parte matemática.
1
Construyamos el conjunto que define al grafo. Como siempre, tendremos dos
subconjuntos, uno de vértices y uno de aristas. En este caso tenemos cinco vértices
llamados . Las aristas se nombran con los vértices de sus extremos,
por ejemplo la arista que va de a es . Nuestro grafo es:
2
Con esto de grafos tenemos suficiente para nuestro algoritmo. Lo que haremos será
tratar una matriz como si fuese un grafo para hallar las componentes conexas de este. El
código está en Matlab, pero es un código muy simple que debería ser fácilmente
portable. Tenemos mucho pase de argumentos a funciones recursivas, así que para C
hay que usar punteros al pasar toda esta información.
Lo que haremos será pasar a la función una matriz de ceros y unos, los unos formarán
los puntos que queremos hallar. Haremos una función que a partir de esa matriz saque
los pares de vértices y la matriz de adyacencia. Haremos luego otra función que use la
matriz de adyacencia para sacar las componentes conexas.
Esto se puede hacer en Matlab con muy pocas líneas, ya que tiene funciones internas
que lo hacen por nosotros. dmperm realiza la descomposición Dulmage-Mendelsohn,
que con una matriz de adyacencia permite sacar las componentes conexas de un grafo.
Sin embargo, no creo que esto tenga gracia cuando lo que queremos es aprender, así que
programaremos la función nosotros mismos.
Este método es más eficiente cuando lo que tenemos so pares de vértices conectados, y
no mucho cuando lo que tenemos es una matriz en la que los vértices están siempre
conectados de la misma manera, pero bueno, lo aplicaremos. Dependerá de tí inventar y
aplicar métodos eficientes a cada problema.
Empecemos definiendo una matriz cualquiera en la que hallaremos las regiones, para
tener un ejemplo sobre el que ir aplicando.
3
La variable m contiene el número de filas de la matriz, la variable n contiene el número
de columnas. Creamos también la matriz B, que será la matriz numerada, y de momento
que sea todo ceros. Ahora recorreremos la matriz B, y le vamos dando números (usando
la variable contador), según encuentre casillas en la matriz de entrada:
Como vemos el código de matlab es muy simple, casi pseudocódigo, así que debería ser
fácilmente trasladable. De todos modos, si no sabes como pasar a C/C++ algo de esto,
pregúntame.
Ahora necesitamos la matriz de adyacencia. Lo que haremos será sacar una lista de
pares y usar la función accumarray para crear la matriz. No me gusta usar funciones
internas de este modo, pero es tan simple que no creo que importe. Si lo quieres
programar, sería tan fácil como crear una matriz de 12×12, ya que en este caso es el
número de vértices. Luego recorremos la matriz creada, y para cada i,j, vemos si esos
vértices están conectados, si lo están, le damos un 1, si no, le damos un 0.
Nosotros lo haremos de otro modo, sacaremos una lista con pares de vértices
conectados, el código es el siguiente: (reiniciamos el contador a 1 porque reciclamos la
variable para el siguiente bucle)
4
El código es simple. Recorremos la matriz B. Para cada vértice que encuentre, es decir,
si es diferente de 0, entonces mira sus cuatro vecinos, y si alguno es diferente de cero,
mete sus valores en una lista “pares”, que es un matriz de X filas y 2 columnas. Al
pasárselo a nuestra matriz queda la siguiente lista:
5
Vemos que los vértices han sido conectados bien. Esta lista, sin embargo, no la
necesitamos, pero la función accumarray, nos devuelve la matriz de adyacencia a partir
de ella, así que no hay más que sacarla.
Ahora vamos con nuestra función que detectará el número de componentes conexas.
Nos devolverá la información en dos variables. La primera de ellas es un vector con los
vértices permutados, la llamaremos p, la segunda será otro vector con los cortes en el
anterior, me explico. Vemos que nuestra matriz tiene dos componentes, la primera está
formada por los vértices: 1,2,3,4,5,6,7, y la segunda por los vértices: 8,9,10,11,12. El
vector p podría ser:
6
p = [1 2 3 4 5 6 7 8 9 10 11 12].
c = [1 8 13]. Esto hace que podamos ver cada componente viendo los elementos de p
desde c(x) hasta c(x+1)-1.
Para rellenar el vector c, lo que hacemos es, simplemente, cada vez que la función
principal llegue al final de una fila, le ponemos el lugar siguiente del vector de vértices
permutados. ¿Por qué? Porque cuando llega al final de una fila significa que ya ha
recorrido recursivamente todos los posibles vértices con los que podía estar conectado,
así que hemos llegado al final de una componente, lo apuntamos, y saltamos de fila.
Evidentemente los primeros valores de p y de c serán 1, así que se los vamos poniendo.
Creamos también el vector de vértices que iremos borrando conforme los encontramos
(vert). m lo usaremos como información del tamaño de la matriz. size(A,1) devuelve el
número de filas de la matriz A, puesto que la matriz de adyacencia es siempre cuadrada,
con ese ya tenemos su tamaño. “controlador” la usaremos para ir apuntando los lugares
del vector c, y cont lo mismo pero con el vector p.
7
recorremos la fila buscando conexiones. Al final de todo miramos si está en la última
columna de la fila, en ese caso metemos un nuevo valor en c.
Veamos lo que hace si encuentra una conexión ( partir de la fila 16 del código completo
de la función). Primero mete el vértice en p y borra ese vértice de vert. Luego borra la
casilla de la matriz de adyacencia, y a continuación llama a la función fila. Esta será
nuestra función recursiva. Recorrerá la fila del nuevo vértice haciendo lo mismo. (Fíjate
que, puesto que la matriz de adyacencia es simétrica con respecto a la diagonal,
borramos tanto como ). A la función fila debemos pasarle todos los
contadores, vectores y, por supuesto, la matriz, para que pueda trabajar. Y nos devolverá
todos los cambios que les haya hecho. En MatLab la salida de la función es lo que hay
antes del igual.
8
Enlace: http://pastebin.com/Hjwew3cX
9
Todo va bien. Tenemos nuestra matriz de adyacencia, pasémosla a la función clases:
Ahí tenemos nuestros resultados. En p tenemos nuestra lista de vértices permutados del
1 al 12. En c tenemos los cortes. Tenemos 3, así que tenemos 2 componentes conexas,
exactamente las que vemos en la matriz. La primera va de 1 a 8-1=7, es decir: 1, 3, 2, 5,
6, 7 y 4. Como vemos en la matriz, es correcto. La segunda va de 8 a 13-1 = 12, es
decir: 8, 10, 11, 12 y 9, que como vemos también son los vértices que forman la
segunda componente.
10
Pues esto es todo por hoy. Se que es un algoritmo no muy eficaz para este tipo de blob
finding, sin embargo, es muy útil si lo que recibimos son, como he dicho antes, listas de
pares de vértices conectados, ya que sacamos la matriz y se la pasamos a “clases”.
Como deberes dejo un par de cosas. La más chunga, pasar este código a C/C++, que me
encantaría verlo. La mayoría de cosas no son muy complicadas, pero sí un engorro
seguramente tener que pasar un montón de datos por referencia a la función recursiva, y
mucha probabilidad de error. Pero nada que no se resuelva con paciencia.
Un saludo a todos y espero que haya gustado. El próximo será mucho más programar y
menos abstracto. Se podrá visualizar mejor lo que hacemos.
11