Beruflich Dokumente
Kultur Dokumente
Antes de explicar directamente el algoritmo de Kruskal, comenzaré dando conceptos sobre que es un árbol de expansión
mínima para entender mejor el problema.
Árbol de Expansión
Dado un grafo conexo, no dirigido G. Un árbol de expansión es un árbol compuesto por todos los vértices y algunas
(posiblemente todas) de las aristas de G. Al ser creado un árbol no existirán ciclos, además debe existir una ruta entre cada
par de vértices.
Un grafo puede tener muchos arboles de expansión, veamos un ejemplo con el siguiente grafo:
En la imagen anterior se puede observar que el grafo dado posee 3 arboles de expansión, dichos arboles cumplen con las
propiedades antes mencionadas como son unir todos los vértices usando algunas aristas.
Árbol de Expansión Mínima
Dado un grafo conexo, no dirigido y con pesos en las aristas, un árbol de expansión mínima es un árbol compuesto por todos
los vértices 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:
https://jariasf.wordpress.com/category/algorithms/ 1/45
21/10/2016 Algorithms | Algorithms and More
De la imagen anterior el árbol de expansión mínima seria el primer árbol de expansión cuyo peso total es 6.
El problema de hallar el Árbol de Expansión Mínima (MST) puede ser resuelto con varios algoritmos, los mas conocidos con
Prim y Kruskal ambos usan técnicas 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 técnica 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
vértices que contienen dicha arista no están en la misma componente
conexa entonces los unimos para formar una sola componente
mediante Union(x , y), para revisar si están o no en la misma
componente conexa usamos la función SameComponent(x , y) al hacer
esto estamos evitando que se creen ciclos y que la arista que une dos
vértices siempre sea la mínima posible.
Algoritmo en Pseudocódigo
1 método Kruskal(Grafo):
2 inicializamos MST como vacío
3 inicializamos estructura unión-find
4 ordenamos las aristas del grafo por peso de menor a mayor.
5 para cada arista e que une los vértices u y v
6 si u y v no están en la misma componente
7 agregamos la arista e al MST
8 realizamos la unión de las componentes de u y v
https://jariasf.wordpress.com/category/algorithms/ 2/45
21/10/2016 Algorithms | Algorithms and More
Ejemplo y código paso a paso
Como podemos ver en la imagen anterior la definición de nuestro grafo en código sería:
1 struct Edge{
2 int origen; //Vértice origen
3 int destino; //Vértice destino
4 int peso; //Peso entre el vértice origen y destino
5 Edge(){}
6 …
7 }arista[ MAX ]; //Arreglo de aristas para el uso en kruskal
Primeramente usaremos el método MakeSet de unión-find para inicializar cada componente, obteniendo las siguientes
componentes conexas iniciales:
Ahora el siguiente paso es ordenar las aristas del grafo en orden ascendente:
https://jariasf.wordpress.com/category/algorithms/ 3/45
21/10/2016 Algorithms | Algorithms and More
Para el ordenamiento podemos usar las librerías 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{
2 …
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
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 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):
https://jariasf.wordpress.com/category/algorithms/ 4/45
21/10/2016 Algorithms | Algorithms and More
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 MST así que unimos los vértices por el método de Union( 8 , 7 ).
Observamos la tabla de Union-Find y vemos que Find(3) != Find(9). Entonces es posible realizar la unión de ambas
componentes:
https://jariasf.wordpress.com/category/algorithms/ 5/45
21/10/2016 Algorithms | Algorithms and More
En la imagen podemos observar que ambos vértices no están en la misma componente, por tanto realizamos la Union( 6 , 7 ):
Continuamos con la siguiente arista, los vértices 1 y 2 no están en la misma componente conexa:
Realizamos la Union(1,2):
https://jariasf.wordpress.com/category/algorithms/ 6/45
21/10/2016 Algorithms | Algorithms and More
Continuamos con la siguiente arista:
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.
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.
https://jariasf.wordpress.com/category/algorithms/ 7/45
21/10/2016 Algorithms | Algorithms and More
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:
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:
https://jariasf.wordpress.com/category/algorithms/ 8/45
21/10/2016 Algorithms | Algorithms and More
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:
https://jariasf.wordpress.com/category/algorithms/ 9/45
21/10/2016 Algorithms | Algorithms and More
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.
En código simplemente es iterar sobre el arreglo de aristas ingresado y ordenado obteniendo sus respectivos datos. Para
verificar si están o no en la misma componente usamos el método 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; //Vértice origen de la arista actual
3 destino = arista[ i ].destino; //Vértice 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 }
Verificación de 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
https://jariasf.wordpress.com/category/algorithms/ 10/45
21/10/2016 Algorithms | Algorithms and More
Cumple con lo dicho -> 9 – 1 = 8 por tanto tenemos un MST válido. 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 válido porque no tiene 1
componente conexa que posea todos los vértices, comprobemos:
Número de Aristas = 7
Número de Vértices = 9
No cumple lo dicho inicialmente 9 – 1 != 7 por lo tanto tenemos un MST invalido. En código basta con un if:
1 //Si el MST encontrado no posee todos los vértices mostramos mensaje de error
2 //Para saber si contiene o no todos los vértices 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 }
Problemas de diferentes Jueces
UVA
1208 – Oreon
10034 – Freckles
https://jariasf.wordpress.com/category/algorithms/ 11/45
21/10/2016 Algorithms | Algorithms and More
10462 – Is There A Second Way Left?
11733 – Airports
COJ
1016 – Freckles
TOPCODER
HDU
POJ
TJU
2531 – Oreon
Códigos:
https://jariasf.wordpress.com/category/algorithms/ 12/45
21/10/2016 Algorithms | Algorithms and More
Por Jhosimar George Arias Figueroa
Para el problema de la ruta corta tenemos varios algoritmos, en esta oportunidad se explicará el algoritmo de dijkstra el cual
usa una técnica voraz (greedy). Al final del articulo se encuentran adjuntas las implementaciones en C++ y JAVA.
Descripción
El algoritmo de dijkstra determina la ruta más corta desde un nodo origen hacia los demás nodos para ello es requerido como
entrada un grafo cuyas aristas posean pesos. Algunas consideraciones:
Si los pesos de mis aristas son de valor 1, entonces bastará con usar el algoritmo de BFS.
Si los pesos de mis aristas son negativos no puedo usar el algoritmo de dijsktra, para pesos negativos tenemos otro
algoritmo llamado Algoritmo de Bellmand-Ford.
Como trabaja
Primero marcamos todos los vértices como no utilizados. El algoritmo parte de un vértice origen que será ingresado, a partir
de ese vértices evaluaremos sus adyacentes, como dijkstra usa una técnica greedy – La técnica greedy utiliza el principio de
que para que un camino sea óptimo, todos los caminos que contiene también
deben ser óptimos entre todos los vértices adyacentes, buscamos el que esté
más cerca de nuestro punto origen, lo tomamos como punto intermedio y
vemos si podemos llegar más rápido a través de este vértice a los demás.
Después escogemos al siguiente más cercano (con las distancias ya
actualizadas) y repetimos el proceso. Esto lo hacemos hasta que el vértice no
utilizado más cercano sea nuestro destino. Al proceso de actualizar las
distancias tomando como punto intermedio al nuevo vértice se le conoce
como relajación (relaxation).
Dijkstra es muy similar a BFS, si recordamos BFS usaba una Cola para el
recorrido para el caso de Dijkstra usaremos una Cola de Prioridad o Heap, este Heap debe tener la propiedad de Min-Heap es
decir cada vez que extraiga un elemento del Heap me debe devolver el de menor valor, en nuestro caso dicho valor será el
peso acumulado en los nodos.
Tanto java como C++ cuentan con una cola de prioridad ambos implementan un Binary Heap aunque con un Fibonacci Heap
la complejidad de dijkstra se reduce haciéndolo mas eficiente, pero en un concurso mas vale usar la librería que intentar
programar una nueva estructura como un Fibonacci Heap, claro que particularmente uno puede investigar y programarlo
para saber como funciona internamente.
Algoritmo en pseudocódigo
https://jariasf.wordpress.com/category/algorithms/ 13/45
21/10/2016 Algorithms | Algorithms and More
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 Q
3 agregamos origen a la cola de prioridad Q
4 mientras Q no este vacío:
5 sacamos un elemento de la cola Q llamado u
6 si u ya fue visitado continuo sacando elementos de Q
7 marcamos como visitado u
8 para cada vértice v adyacente a u en el Grafo:
9 sea w el peso entre vértices ( u , v )
10 si v no ah sido visitado:
11 Relajacion( u , v , w )
Ejemplo y Código paso a paso
Tengamos el siguiente grafo, cuyos ID están en color negro encima de cada vértice, los pesos esta en color azul y la distancia
inicial en cada vértice es infinito
Algunas consideraciones para entender el código que se explicara junto con el funcionamiento del algoritmo.
1 #define MAX 10005 //maximo numero de vértices
2 #define Node pair< int , int > //definimos el nodo como un par( first , second ) donde first es el verti
3 #define INF 1<<30 //definimos un valor grande que represente la distancia infinita inicial, basta conque
https://jariasf.wordpress.com/category/algorithms/ 14/45
21/10/2016 Algorithms | Algorithms and More
Donde:
1 vector< Node > ady[ MAX ]; //lista de adyacencia
2 int distancia[ MAX ]; //distancia[ u ] distancia de vértice inicial a vértice con ID = u
3 bool visitado[ MAX ]; //para vértices visitados
4 int previo[ MAX ]; //para la impresion de caminos
5 priority_queue< Node , vector<Node> , cmp > Q; //priority queue propia del c++, usamos el comparador def
6 int V; //numero de vertices
1 //función de inicialización
2 void init(){
3 for( int i = 0 ; i <= V ; ++i ){
4 distancia[ i ] = INF; //inicializamos todas las distancias con valor infinito
5 visitado[ i ] = false; //inicializamos todos los vértices como no visitado
6 previo[ i ] = ‐1; //inicializamos el previo del vértice i con ‐1
7 }
8 }
De acuerdo al vértice inicial que elijamos cambiara la distancia inicial, por ejemplo la ruta más corta partiendo del vértice 1 a
todos los demás vértices:
El vértice 1 es visitado, la distancia de vértice 1 -> vértice 1 es 0 por estar en el mismo lugar.
1 Q.push( Node( inicial , 0 ) ); //Insertamos el vértice inicial en la Cola de Prioridad
2 distancia[ inicial ] = 0; //Este paso es importante, inicializamos la distancia del inicial como 0
1 while( !Q.empty() ){ //Mientras cola no este vacia
2 actual = Q.top().first; //Obtengo de la cola el nodo con menor peso, en un comienzo s
3 Q.pop(); //Sacamos el elemento de la cola
https://jariasf.wordpress.com/category/algorithms/ 15/45
21/10/2016 Algorithms | Algorithms and More
Si el tope ya fue visitado entonces no tengo necesidad de evaluarlo, por ello continuaría extrayendo elementos dela cola:
1 if( visitado[ actual ] ) continue; //Si el vértice actual ya fue visitado entonces sigo sacando elemento
En este caso al ser el tope el inicial no esta visitado por lo tanto marcamos como visitado.
1 visitado[ actual ] = true; //Marco como visitado el vértice actual
1 for( int i = 0 ; i < ady[ actual ].size() ; ++i ){ //reviso sus adyacentes del vertice actual
2 adyacente = ady[ actual ][ i ].first; //id del vertice adyacente
3 peso = ady[ actual ][ i ].second; //peso de la arista que une actual con adyacente ( actual ,
4 if( !visitado[ adyacente ] ){ //si el vertice adyacente no fue visitado
Vemos que la distancia actual desde el vértice inicial a 2 es ∞, verifiquemos el paso de relajación:
distancia[ 1 ] + 7 < distancia[ 2 ] > 0 + 7 < ∞ > 7 < ∞
El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 2 y agregando el vértice en la cola
de prioridad con nueva distancia quedando:
https://jariasf.wordpress.com/category/algorithms/ 16/45
21/10/2016 Algorithms | Algorithms and More
1 for( int i = 0 ; i < ady[ actual ].size() ; ++i ){ //reviso sus adyacentes del vertice actual
2 adyacente = ady[ actual ][ i ].first; //id del vertice adyacente
3 peso = ady[ actual ][ i ].second; //peso de la arista que une actual con adyacente ( actual ,
4 if( !visitado[ adyacente ] ){ //si el vertice adyacente no fue visitado
5 relajacion( actual , adyacente , peso ); //realizamos el paso de relajacion
6 }
7 }
1 //Paso de relajacion
2 void relajacion( int actual , int adyacente , int peso ){
3 //Si la distancia del origen al vertice actual + peso de su arista es menor a la distancia del orige
4 if( distancia[ actual ] + peso < distancia[ adyacente ] ){
5 distancia[ adyacente ] = distancia[ actual ] + peso; //relajamos el vertice actualizando la dis
6 previo[ adyacente ] = actual; //a su vez actualizamos el vertice previo
7 Q.push( Node( adyacente , distancia[ adyacente ] ) ); //agregamos adyacente a la cola de priorid
8 }
9 }
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 < ∞
https://jariasf.wordpress.com/category/algorithms/ 17/45
21/10/2016 Algorithms | Algorithms and More
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:
Ahora vemos que la distancia actual del vértice inicial al vértice 2 es 7, verifiquemos el paso de relajación:
distancia[ 4 ] + 3 < distancia[ 2 ] > 2 + 3 < 7 > 5 < 7
En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al vértice 2, actualizamos la
distancia en el vértice 2 y actualizamos el vértice previo al actual quedando:
https://jariasf.wordpress.com/category/algorithms/ 18/45
21/10/2016 Algorithms | Algorithms and More
En cuanto a la cola de prioridad como tenemos un vértice con menor peso este nuevo vértice ira en el tope de la cola,
podemos ver que tenemos 2 veces el mismo vértice pero como usamos una técnica greedy siempre usaremos el valor óptimo:
Continuamos con los Vértices 3 y 5 como tienen valor ∞ si será posible relajarlos por lo que sería similar a los pasos iniciales
solo que en los pasos iniciales distancia[ 1 ] era 0 en este caso distancia[ 4 ] es 2, quedando:
Hemos terminado de evaluar al vértice 4, continuamos con el tope de la cola que es vértice 2, el cual marcamos como
visitado.
https://jariasf.wordpress.com/category/algorithms/ 19/45
21/10/2016 Algorithms | Algorithms and More
Los adyacentes no visitados del vértice 2 son los vértices 3 y 5. Comencemos con el vértice 3
Ahora vemos que la distancia actual del vértice inicial al vértice 3 es 10, verifiquemos el paso de relajación:
distancia[ 2 ] + 1 < distancia[ 3 ] > 5 + 1 < 10 > 6 < 10
En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al vértice 3, dicha ruta sería 1 ->
4 -> 2 -> 3 cuyo peso es 6 que es mucho menor que la ruta 1 – > 4 -> 3 cuyo peso es 10, actualizamos la distancia en el vértice
3 quedando:
https://jariasf.wordpress.com/category/algorithms/ 20/45
21/10/2016 Algorithms | Algorithms and More
Vemos que la distancia actual del vértice inicial al vértice 5 es 7, verifiquemos el paso de relajación:
distancia[ 3 ] + 5 < distancia[ 5 ] > 6 + 5 < 7 > 11 < 7
En esta oportunidad se no cumple por lo que no relajamos el vértice 5, por lo que la tabla en cuanto a distancias no sufre
modificaciones y no agregamos vértices a la cola:
Ahora tocaría el vértice 2 pero como ya fue visitado seguimos extrayendo elementos de la cola, el siguiente vértice será el 5.
Al ser el ultimo vértice a evaluar no posee adyacentes sin visitar por lo tanto hemos terminado el algoritmo. En el grafico
anterior observamos que 2 aristas no fueron usadas para la relajación, las demás si fueron usadas. La tabla final quedaría de
https://jariasf.wordpress.com/category/algorithms/ 21/45
21/10/2016 Algorithms | Algorithms and More
la siguiente manera:
De la tabla si deseo saber la distancia mas corta del vértice 1 al vértice 5, solo tengo que acceder al valor del arreglo en su
índice respectivo (distancia[ 5 ]).
Shortest Path Tree
En el proceso anterior usábamos el arreglo previo[ u ] para almacenar el ID del vértice previo al vértice con ID = u, ello me
sirve para formar el árbol de la ruta mas corta y además me sirve para imprimir caminos de la ruta mas corta.
Impresión del camino encontrado.
Para imprimir el camino mas corto deseado usamos el arreglo previo[ u ], donde u tendrá el ID del vértice destino, o sea si
quiero imprimir el camino mas corto desde vértice 1 -> vértice 3 partiré desde previo[ 3 ] hasta el previo[ 1 ]. De manera
similar a lo que se explico en el algoritmo BFS, en este caso se realizara de manera recursiva:
1 //Impresion del camino mas corto desde el vertice inicial y final ingresados
2 void print( int destino ){
3 if( previo[ destino ] != ‐1 ) //si aun poseo un vertice previo
4 print( previo[ destino ] ); //recursivamente sigo explorando
5 printf("%d " , destino ); //terminada la recursion imprimo los vertices recorridos
6 }
https://jariasf.wordpress.com/category/algorithms/ 22/45
21/10/2016 Algorithms | Algorithms and More
https://jariasf.wordpress.com/category/algorithms/ 23/45
21/10/2016 Algorithms | Algorithms and More
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
Problemas de diferentes Jueces
UVA
https://jariasf.wordpress.com/category/algorithms/ 24/45
21/10/2016 Algorithms | Algorithms and More
10278 – Fire Station
TJU
SPOJ
POJ
HDU
ICPC
COJ
Códigos:
Implementación del algoritmo en C++: Algoritmo de Dijkstra
Implementación del algoritmo en JAVA: Algoritmo de Dijkstra
Por Jhosimar George Arias Figueroa
https://jariasf.wordpress.com/category/algorithms/ 25/45
21/10/2016 Algorithms | Algorithms and More
El algoritmo de búsqueda que se explicará a continuación es Depth First Search ( DFS ) se explicará el algoritmo de manera
similar a como se hizo BFS, proponiendo problemas y otorgando códigos del algoritmo en si.
Descripción
El algoritmo DFS posee varias aplicaciones la mas importante es para problemas de conectividad, si un grafo es conexo,
detectar ciclos en un grafo, numero de componentes conexas, etc y es bastante útil en otro algoritmos como para hallar las
componentes fuertemente conexas en un grafo ( Algoritmo de Kosaraju, Algoritmo de Tarjan), para hallar puntos de
articulación o componentes biconexas ( puentes ), para recorrido en un circuito o camino euleriano, topological sort, flood
fill y otras aplicaciones.
Como trabaja
Usando Stack
Algoritmo en pseudocodigo:
1 método DFS( origen):
2 creamos una pila S
3 agregamos origen a la pila S
4 marcamos origen como visitado
5 mientras S no este vacío:
6 sacamos un elemento de la pila S llamado v
7 para cada vertice w adyacente a v en el Grafo:
8 si w no ah sido visitado:
9 marcamos como visitado w
10 insertamos w dentro de la pila S
Código en C++: Algoritmo DFS usando Stack
Usando Recursión
https://jariasf.wordpress.com/category/algorithms/ 26/45
21/10/2016 Algorithms | Algorithms and More
Usar la recursión es mucho mas fácil y ademas muy útil, es la forma mas usada en la solución de problemas con este
algoritmo.
Algoritmo en pseudocódigo:
1 método DFS( origen):
2 marcamos origen como visitado
3 para cada vertice v adyacente a origen en el Grafo:
4 si v no ah sido visitado:
5 marcamos como visitado v
6 llamamos recursivamente DFS( v )
Al igual que con la pila requerimos un nodo inicial, de manera recursiva llamamos a los adyacentes del nodo inicial, de esta
forma se puede ver si llamamos inicialmente a “1”:
Inicial “1”: marcamos “1” como visitado, sus adyacentes son “2”, “3” y “5”.
Visitados : 1.
Adyacentes de 1: 2 , 3 , 5
https://jariasf.wordpress.com/category/algorithms/ 27/45
21/10/2016 Algorithms | Algorithms and More
En la llamada recursiva ira el primero insertado en la lista de adyacencia, en este caso “2”, marcamos como visitado. Ahora el
inicial es “2”, sus adyacentes son “1” , “4” y “5”.
Visitados: 1 , 2
Adyacentes de 2: 1, 4 , 5
Evaluamos el 1ero de la lista que es “1” pero ya fue visitado por lo tanto sigo con el siguiente, en este caso “4” , marcamos
como visitado. Ahora inicial es “4”, sus adyacentes son “2”.
https://jariasf.wordpress.com/category/algorithms/ 28/45
21/10/2016 Algorithms | Algorithms and More
Visitados: 1 , 2 , 4
Adyacentes de 4: 2
Tocaria el nodo 2 pero ya fue visitado termina la recursion por ese lado. El siguiente adyacente de “2” es “5”. Ahora inicial es
“5”, marcamos como visitado, sus adyacentes son “1” y “2”.
Visitados: 1 , 2 , 4 , 5
Adyacentes de 5: 1 , 2
Igual que con nodo “4” sus adyacentes han sido visitados por lo tanto terminamos la recursion por el nodo “2”.
https://jariasf.wordpress.com/category/algorithms/ 29/45
21/10/2016 Algorithms | Algorithms and More
El nodo actual es “1”, sus adyacentes eran “2”, “5” y “3”, estabamos evaluando por “2” pero ya terminamos el siguiente es “5”
el cual ya fue visitado, continuamos con “3” este no fue visitado, marcamos como visitado, vemos sus adyacentes “1”.
Visitados: 1 , 2 , 4 , 5 , 3
Adyacentes de 3: 1
Como nodo “1” ya fue visitado entonces termina la recursión y termina el recorrido DFS. Como se puede observar el orden en
que fueron visitados los nodos es el recorrido DFS del grafo.
Posibles Paths en un grafo
https://jariasf.wordpress.com/category/algorithms/ 30/45
21/10/2016 Algorithms | Algorithms and More
Otra ayuda importantisima del algoritmo recursivo es que nos permite hallar todos los posibles paths( rutas) de un nodo
inicial a otro final, ello lo conseguiremos usando backtracking dentro del algoritmo:
Algoritmo en pseudocódigo:
1 método DFS( origen,final):
2 marcamos origen como visitado
3 recuperar el path si se llego a final
4 para cada vertice v adyacente a origen en el Grafo:
5 si v no ah sido visitado:
6 marcamos como visitado v
7 llamamos recursivamente DFS( v )
8 marcamos origen como no visitado
Codigo C++: Algoritmo DFS usando Recursion
Componentes Conexas
Algun problema puede ser que nos pida hallar la cantidad de componentes conexas, supongamos el siguiente grafo no
dirigido:
Evaluamos todos los vertices posibles y los tomamos como origen aquellos no visitados:
1 ...
2 for( int i = 1 ; i <= V ; ++i ){ //recorremos todos los posibles vertices
3 if( !visitado[ i ] ){ //si alguno no fue visitado tenemos una componente a partir de ese no
4 dfs( i ); //recorremos a partir de nodo i todo el grafo que se forma
5 total++; //incrementamos cantidad de componentes
6 }
7 }
8 ...
i=1
Visitados: Ninguno
Como no fue visitado entonces hacemos recorrido DFS partiendo de ese vértice ( dfs( i = 1 ) ). Entonces veamos el recorrido:
Vértice Actual: 1
Vértices Adyacentes: 2
Vértices Visitados: 1
https://jariasf.wordpress.com/category/algorithms/ 32/45
21/10/2016 Algorithms | Algorithms and More
Vértice Actual: 2
Vértices Adyacentes: 3
Vértices Visitados: 1, 2
Vértice Actual: 3
Vértices Adyacentes: 2
Vértices Visitados: 1, 2, 3
https://jariasf.wordpress.com/category/algorithms/ 33/45
21/10/2016 Algorithms | Algorithms and More
Terminamos la función de DFS partiendo del vértice 1. Ahora volvemos al for inicial e incrementamos las componentes.
1 ...
2 if( !visitado[ i ] ){
3 dfs( i );
4 total++; //incrementamos numero de componentes
5 }
6 ...
Ahora tenemos i = 2 pero 2 ya fue visitado por lo que no entraría al if, igual para i=3 que ya fue visitado; sin embargo para
i=4 este no fue visitado por lo tanto hacemos recorrido dfs partiendo del vértice 4:
i=4
Visitados: 1, 2, 3
Vértice Actual: 4
Vértices Adyacentes: 5
https://jariasf.wordpress.com/category/algorithms/ 34/45
21/10/2016 Algorithms | Algorithms and More
Vértices Visitados: 1, 2, 3, 4
Vértice Actual: 5
Vértices Adyacentes: 4
Vértices Visitados: 1, 2, 3, 4, 5
https://jariasf.wordpress.com/category/algorithms/ 35/45
21/10/2016 Algorithms | Algorithms and More
Terminamos DFS partiendo del vértice 4, entonces volvemos al if e incrementamos el numero de componentes, de esta
manera el resultado será de 2 componentes.
Codigo C++: DFS Ejemplo – Conectividad
Ejemplo Aplicativo:
Tengamos un juego de dominos donde si hago caer uno domino, todos los demas dominos que siguen a este caerán. Dado el
numero de dominos “n”, el estado del juego en la forma “x y” ( si domino “x” cae entonces domino “y” tambien caerá) , la
cantidad de consultas a realizar, cada consulta sera el numero del domino el cual yo impulsaré. El problema me pide hallar
cuantos dominos caeran a partir del domino que yo impulsé.
Supongamos la entrada:
12
25
53
43
67
78
https://jariasf.wordpress.com/category/algorithms/ 36/45
21/10/2016 Algorithms | Algorithms and More
6
Solución
1 vector<int> ady[ MAX ]; //lista de adyacencia
2 int total; //la cantidad total de dominos que caerán
3 bool visitado[ MAX ]; //arreglo de domino caido
Ahora vemos cada consulta, la primera nos indica que impulsaré el domino numero 1, entonces al hacer ello los dominos que
caerán serán 1 -> 2 -> 5 ->3, debo retornar 4 la cantidad de dominos caidos. Para la segunda consulta caéran solamente 4->3,
y finalmente para 6 caerán 6->7->8
1 void dfs( int u ){ //domino origen
2 total++; //aumento en mi respuesta la caida de un domino
3 visitado[ u ] = true; //domino "u" cayo
4 for( int v = 0 ; v < ady[ u ].size(); ++v ){ //verifico los demas posibles domino que caeran si impu
5 if( !visitado[ ady[ u ][ v ] ] ){ //si el domino adyacente no cayó entonces es el sigui
6 dfs( ady[ u ][ v ] ); //recursivamente veo que dominos caeran a partir del ad
7 }
8 }
9 }
Codigo C++: DFS Ejemplo – Domino
A continuación algunos problemas que pueden ser resueltos con este algoritmo:
JUEZ UVA:
459 – Graph Connectivity
JUEZ PKU:
2245 – Lotto
https://jariasf.wordpress.com/category/algorithms/ 37/45
21/10/2016 Algorithms | Algorithms and More
TOPCODER:
SRM 371: DIV 2 – 1000 FloodRelief
SRM 407: DIV 2 – 500 CorporationSalary
SRM 435 : DIV 1 – 250 CellRemoval
Se irán agregando mas adelante mas problemas de otros jueces asi como más aplicaciones de DFS.
Resumen de códigos:
Código en C++: Algoritmo DFS usando Stack
Código en C++: Algoritmo DFS usando Recursion
Código en C++: DFS Ejemplo – Domino
Código en C++: DFS Ejemplo – Conectividad
Por: Jhosimar George Arias Figueroa
En esta oportunidad mostraré el algoritmo de búsqueda por anchura en un grafo (BFS) , se explicará el algoritmo y
propondremos algunos problemas que se solucionan con este algoritmo para que puedan resolverlos.
Descripción
Este algoritmo de grafos es muy útil en diversos problemas de programación. Por ejemplo halla la ruta más corta cuando el
peso entre todos los nodos es 1, cuando se requiere llegar con un movimiento de caballo de un punto a otro con el menor
numero de pasos, cuando se desea tranformar algo un numero o cadena en otro realizando ciertas operaciones como suma
producto, pero teniendo en cuenta que no sea muy grande el proceso de conversion, o para salir de un laberinto con el menor
numero de pasos , etc. Podrán aprender a identificarlos con la práctica.
Como trabaja
BFS va formando un árbol a medida que va recorriendo un grafo, veamos el ejemplo de la figura:
Si observan bien todo parte de un nodo inicial que será la raiz del árbol que se forma, luego ve los adyacentes a ese nodo y los
agrega en un cola, como la prioridad de una cola es FIFO (primero en entrar es el primero en salir), los siguientes nodos a
https://jariasf.wordpress.com/category/algorithms/ 38/45
21/10/2016 Algorithms | Algorithms and More
evaluar serán los adyacentes previamente insertados. una cosa bastante importante es el
hecho de que no se pueden visitar 2 veces el mismo nodo o Estado. ya que sino podriamos
terminar en un ciclo interminable o simplemente no hallar el punto deseado en el menor
número de pasos.
Dos ejemplos mas detallados lo puede ver desde aqui ->EJEMPLO1 BFS , EJEMPLO2 BFS
Algoritmo en pseudocódigo
1 método BFS(Grafo,origen):
2 creamos una cola Q
3 agregamos origen a la cola Q
4 marcamos origen como visitado
5 mientras Q no este vacío:
6 sacamos un elemento de la cola Q llamado v
7 para cada vertice w adyacente a v en el Grafo:
8 si w no ah sido visitado:
9 marcamos como visitado w
10 insertamos w dentro de la cola Q
Ejemplo Aplicativo:
Tenemos una matriz de caracteres que representa un laberinto 2D un ‘#’ implica un muro, un ‘.’ implica
un espacio libre, un ‘I’ indica la entrada del laberinto y una ‘S’ indica una salida.
¿Cuánto mide la ruta más corta para escapar?
Solución:
El problema nos da un Estado inicial y final, en este caso “I” y “S”, nos pide el menor numero de pasos para ir de “I” a “S”
tomando en consideración que solo podremos avanzar por los “.”
Dicha la descripción anteriro, el problema puede ser resulto por BFS al tener un estado inicial y final eh ir avanzando por
algun adyacente que no sea “#” . Los posibles adyacentes para movernos es hacia arriba, abajo, izquierda y derecha,
suponiendo que estamos en la posición (x,y) tendriamos:
https://jariasf.wordpress.com/category/algorithms/ 39/45
21/10/2016 Algorithms | Algorithms and More
Asi por ejemplo si estoy en la posición siguiente (circulo) y suponiendo que sea la coordenada 3,4 :
Los posibles Estados adyacentes serian: izquierda (3,2 -> “.”), derecha (3,5 -> “.”), arriba (2,4 -> “#”) y abajo (4,4 -> “#”) por
lo tanto para ese caso solo podriamos avanzar por la derecha o izquierda.
La solución la explicaremos paso a paso en el lenguaje C++ , para otros lenguajes como Java es bastante similar.
Primeramente declaramos nuestro laberinto como una matriz, tambien declaramos un arreglo de Visitado que nos indicará si
se visito o no un estado determinado:
1 ...
2 #define MAX 100 //máximo número de filas y columnas del laberinto
3 char ady[MAX][MAX]; //laberinto
4 bool visitado[MAX][MAX]; //arreglo de estados visitados
5 ...
Para la función BFS, la entrada que se recibe es el punto inicial de donde deseamos partir y el punto de llegada, en este
ejemplo el punto de llegada esta representado por “S” por lo que no es necesario pasarlo como parametro. El valor inicial
cuando trabajamos con dos dimensiones es necesario la fila y columna inicial, ademas tenemos que pasar la altura y el ancho
del laberinto para controlar los limites de recorrido estos podemos declararlos globales si se desea:
1 ...
2 int BFS(int x , int y, int h, int w) //coordenadas de inicial "I" y dimensiones de laberinto
3
4 ...
Cada vez que trabajamos con BFS trabajamos con estados, desde un estado inicial podemos llegar al estado final por medio
de varios estados, por ello una forma de manejar los diferentes estados y los valores que se tiene hasta ese estado podemos
realizar una estructura “Estado” que contendrá la posición del estado determinado asi como otros atributos, uno necesario es
la distancia en dicho estado:
1 struct Estado{
2 int x; // Fila del estado
3 int y; // Columna del estado
4 int d; // Distancia del estado
https://jariasf.wordpress.com/category/algorithms/ 40/45
21/10/2016 Algorithms | Algorithms and More
5 Estado(int x1 , int y1 , int d1) : x(x1), y(y1), d(d1){} // Constructor
6 };
Respecto a lo anterior otra forma para hallar las distancias sería ir almacenando en un arreglo de distancias. Declaramos
nuestro estado inicial:
1 ...
2 Estado inicial(x,y,0) ; //Estado inicial, distancia = 0
3 ...
Ahora para realizar un BFS es necesario la estructura de datos Cola, la cual pondemos implementar por medio de arreglos o
simplemente usar la estructura ya implementada de las librerias de C++ (queue) o Java(Queue):
1 ...
2 queue Q; //Cola de todos los posibles Estados por los que se pase para llegar al destino
3 Q.push(inicial); //Insertamos el estado inicial en la Cola.
4 ...
1 ...
2 memset(visitado, false, sizeof(visitado)); //marcamos como no visitado
3 ...
Ahora a partir del inicial comprobamos si se llego al destino, en caso sea verdadero retornamos la distancia recorrida hasta
ese momento, de otro modo empezamos a evaluar los adyacentes y agregarlos a la cola. Para evaluar los adyacentes se explico
anteriormente que son dados en 4 direcciones ello se puede expresar de la siguiente manera:
1 ...
2 int dx[4] = {0, 0, 1, ‐1 }; //incremento en coordenada x
3 int dy[4] = {1, ‐1, 0, 0 }; //incremento en coordanada y
4 ...
Estos valores seran usados para aumentar al estado actual y ver su adyacente, esto se explico anteriormente en la grafica
ejemplo de recorrido.
Haremos todo este proceso descrito anteriormente mientras la cola no este vacia:
1 while( !Q.empty() ){ //Mientras cola no este vacia
2 Estado actual = Q.front(); //Obtengo de la cola el estado actual, en un comienzo será el inic
3 Q.pop(); //Saco el elemento de la cola
4 if( ady[actual.x][actual.y]== 'S'){//Si se llego al destino (punto final)
5 return actual.d; //Retornamos distancia recorrida hasta ese momento
6 }
7 visitado[actual.x][actual.y]=true; //Marco como visitado dicho estado para no volver a recorrerlo
8 for( int i = 0; i < 4; i++){ //Recorremos hasta 4 porque tenemos 4 posibles adyacentes
9 int nx = dx[i] + actual.x; //nx y ny tendran la coordenada adyacente
10 int ny = dy[i] + actual.y; //ejemplo en i=0 y actual (3,4) ‐> 3+dx[0]=3+0=3, 4+dy[0]=4+1=5, n
11 //aqui comprobamos que la coordenada adyacente no sobrepase las dimensiones del laberinto
12 //ademas comprobamos que no sea pared "#" y no este visitado
13 if(nx>=0 && nx<h && ny>=0 && ny<w && ady[nx][ny]!='#' && !visitado[nx][ny]){
14 Estado adyacente(nx, ny, actual.d+1); //Creamos estado adyacente aumento en 1 la distancia rec
15 Q.push(adyacente); //Agregamos adyacente a la cola
16 }
17 }
18 }
Finalmente si no se pudo llegar al destino retornamos un valor que el problema comunmente nos da en este caso podemos
retornar -1.
Impresión del Camino Encontrado
https://jariasf.wordpress.com/category/algorithms/ 41/45
21/10/2016 Algorithms | Algorithms and More
Adicionalmente a lo anterior habrá problemas que nos pedirán imprimir la ruta que me llevo a la solución, por ello
tendremos un nuevo arreglo:
1 ...
2 Estado prev[ MAX ][ MAX ]; //Arreglo para mostrar la ruta que se siguio
3 ..
Este arreglo es de 2 dimensiones por que estamos trabajando con una malla de 2 dimensiones, la priera dimensión será para
las coordenadas X y la segunda será para coordenadas Y.
El arreglo me servirá para almacenar las coordenadas del Estado en el que estuve anteriormente, por ello se le puso como un
arreglo de Estado debido a que Estado posee las coordenadas X e Y que necesito.
Para el Estado inicial, al ser el primero no tengo un Estado anterior por lo que le pongo valores de -1:
1 ...
2 prev[ x ][ y ] = Estado( ‐1 , ‐1 , ‐1 ); //el inicial no tiene una ruta anterior puesto que es el prime
3 ..
Ahora veamos un ejemplo de por que me sirve un arreglo con las coordenadas anteriores:
Asumiendo que la salida del laberinto se llevo a cabo por medio de la ruta mostrada.
Una vez que llegue a S osea la salida, estoy en la coordenada 3,3. Para mostrar la ruta recorrida tengo almacenado en mi
arreglo prev el anterior de 3,3 que vendría a ser 3,2.
Osea accediendo al arreglo sería Estado ant = prev[ 3 ][ 3 ]. Si revisamos las coordenadas del Estado ant, tendríamos ant.x =
3, ant.y = 2. Y de esa manera iremos iterando viendo los anteriores hasta llegar al inicial donde ya no tengo anterior.
1 ...
2 if( nx >= 0 && nx < h && ny >= 0 && ny < w && ady[nx][ny] != '#' && !visitado[nx][ny] ){
3 ...
4 prev[ nx ][ ny ] = actual; //El previo del nuevo nodo es el actual.
5 }
6 ..
1 ...
2 if( ady[actual.x][actual.y]== 'S'){ //Si se llego al destino (punto final
3 return actual.d; //Retornamos distancia recorrida hasta ese momento
https://jariasf.wordpress.com/category/algorithms/ 42/45
21/10/2016 Algorithms | Algorithms and More
4 }
5 ..
1 ...
2 if( ady[actual.x][actual.y]== 'S'){//Si se llego al destino (punto final
3 print( actual.x , actual.y ); //imprimo la ruta del camino mas corto
4 return actual.d; //Retornamos distancia recorrida hasta ese momento
5 }
6 ..
La función de impresión tendrá como parámetros las coordenadas del estado final, lo que tendremos que hacer es iterar hasta
encontrar el estado con valores -1, como se declaro en un inicio.
1 void print( int x , int y ){
2 //El arreglo prev posee las coordenadas del nodo anterior, por ello empezamos desde el final
3 //El proceso termina al momento de preguntar por el anterior del nodo inicial, como pusimos ‐1
4 //Preguntamos hasta que nuestro anterior sea diferente de ‐1
5 for( int i = x , j = y ; prev[ i ][ j ].d != ‐1 ; i = prev[ x ][ y ].x , j = prev[ x ][ y ].y ){
6 ady[ i ][ j ] = '*'; x = i; y = j;
7 }
8
9 printf("Camino con menor numero de pasos\n" );
10 for( int i = 0 ; i < h ; ++i ){
11 for( int j = 0 ; j < w ; ++j ){
12 printf("%c" , ady[ i ][ j ] );
13 }
14 printf("\n");
15 }
16 }
De esta manera se realiza la impresión de la ruta encontrada por el BFS, el arreglo prev se puede declarar de diferentes
formas acorde al programador.
A continuación dejamos una lista de problemas que pueden ser resueltos con este algoritmo:
Juez UVA
Para mas problemos en el UVA pueden encontrarlos en el UvaToolkit escribiendo en el buscador BFS.
https://jariasf.wordpress.com/category/algorithms/ 43/45
21/10/2016 Algorithms | Algorithms and More
Juez TJU
– 1056 – Labyrinth
Juez HDU
– 1242 – Rescue
– 1072 – Nightmare
– 1240 – Asteroids
Topcoder
ICPC LIVE
– 2040 – Multiple
Códigos:
Implementación del algoritmo en Java: Algoritmo BFS
Implementación del algoritmo en C++: Algoritmo BFS
https://jariasf.wordpress.com/category/algorithms/ 44/45
21/10/2016 Algorithms | Algorithms and More
Ejemplo BFS en Java: Saliendo del Laberinto sin impresión de Ruta
Ejemplo BFS en C++: Saliendo del Laberinto con impresión de Ruta
Por Jhosimar George Arias Figueroa
https://jariasf.wordpress.com/category/algorithms/ 45/45