martes, 2 de diciembre de 2014

Redes de Petri yAplicaciones de Grafos y Arboles

Una red de Petri es un grafo orientado con dos tipos de nodos: lugares (representados mediante circunferencias) y transiciones (representadas por segmentos rectos verticales). Los lugares y las transiciones se unen mediante arcos o flechas

Un arco une siempre lugares con transiciones y nunca dos lugares o dos transiciones. Una transición puede ser destino de varios lugares y un lugar puede ser el destino de varias transiciones. Una transición puede ser origen de varios lugares y un lugar puede ser origen de varias transiciones Los lugares pueden presentar marcas (una marca se representa mediante un punto en el interior del círculo).Cada lugar tiene asociada una acción o salida. Los lugares que contienen marcas se consideran lugares activos. Cuando un lugar está activo sus salidas están a uno. A las transiciones se les asocia eventos (funciones lógicas de las variables de entrada).Una transición se dice que está sensibilizada cuando todos su lugares origen están marcados. Cuando ocurre un evento asociado a una transición (la función lógica se hace uno), se dice que la transición está validada.

6.6 Aplicaciones de grafos y arboles

¿Qué es un grafo? Recordemos que un grafo G es el par (V, A) que representa una relación entre un conjunto de Vértices y otro de Aristas. Representaremos cada elemento arista como un par de elementos de V. Gráficamente representaremos los vértices por puntos y las aristas por líneas que los unen. Un vértice puede tener 0 o más aristas, pero toda arista debe unir exactamente 2 vértices. Las aplicaciones más importantes de los grafos son las siguientes: • Rutas entre ciudades. • Determinar tiempos máximos y mínimos en un proceso. • Flujo y control en un programa 

Los grafos son la representación natural de las redes, en las que estamos cada vez más incluidos. Los grafos son artefactos matemáticos que permiten expresar de una forma visualmente muy sencilla y efectiva las relaciones que se dan entre elementos de muy diversa índole. Un grafo simple está formado por dos conjuntos: • Un conjunto V de puntos llamados vértices o nodos. • Un conjunto de pares de vértices que se llaman aristas o arcos y que indican qué nodos están relacionados. De una manera más informal podemos decir que un grafo es un conjunto de nodos con enlaces entre ellos, denominados aristas o arcos. En un grafo simple entre dos nodos sólo hay un arco. Si hay más de un arco hablamos de un multígrafo. Si los arcos se pueden recorrer en una dirección concreta pero no en la contraria lo llamamos grafo dirigido o dígrafo y los arcos son entonces aristas, si los arcos salen y llegan al mismo punto formando un bucle el grafo resultante se llama pseudografo. A pesar de que un grafo parece una estructura muy elemental, hay muchísimas propiedades de los grafos cuyo estudio ha dado lugar a una completa teoría matemática.
Fue Leonhard Euler quien ideó los grafos como una manera muy potente y elegante de resolver el problema de los puentes de Königsberg. Königsberg (hoy Kaliningrado en Rusia) era en tiempos de Euler (siglo XVIII) una ciudad prusiana cruzada por siete puentes. Durante la época se suscitó la cuestión no resuelta de si era posible recorrer toda la ciudad cruzando cada uno de los puentes una y sólo una vez. Si hacemos una representación esquemática de la ciudad vemos que los puentes unen cuatro porciones de tierra. La búsqueda por prueba y error no conduce a ningún resultado. El problema de los puentes de Königsberg. Esta ciudad esta recorrida por el río Pregel que crea dos islas. ¿Se puede recorrer toda la ciudad pasando una sola vez por todos y cada uno de los 7 puentes que unen la parte insular de la ciudad con el resto? La solución de Euler. El famoso matemático abstrajo los detalles de la forma de la ciudad y sus puentes para quedarse con la conectividad, dando lugar a una de los primeros grafos. El orden de todos los vértices es impar, lo que implica que es imposible recorrerlos pasando una sola vez por cada uno. Euler realizó una abstracción del problema representando mediante puntos las cuatro porciones de terreno y dibujando un arco entre cada dos puntos por cada puente. Llamó orden de cada vértice al numero de arcos que se reunían en el y se percató que el orden de cada vértice visitado en un recorrido sin saltos ha de ser par (sale un enlace y entra otro) excepto para dos puntos del grafo: aquellos donde se inicia y donde se acaba el recorrido, que han de tener orden impar. Si el vértice donde se inicia y se acaba son el mismo entonces todos los vértices han de ser de orden par. En el problema de Königsberg el orden de todos los nodos es 3, esto es impar, por lo que quedó claro que no existía solución para el problema. No había un camino que recorriese todos los puentes pasando una sola vez por cada uno de ellos. El interés de este ejemplo es que además de dar lugar a una teoría matemática muy potente los grafos se dibujan y resultan muy intuitivos, especialmente cuando los vértices son pocos. Ejemplos de grafos que todos conocemos son los organigramas que explicitan la estructura formal de la empresa, los árboles genealógicos o la circuitería de los chips electrónicos. Se usan regularmente para resolver problemas en la eficiencia del transporte, en sociología, electrónica y electricidad, detección de fraude y en general en aquellos campos en los que la conectividad es importante. De hecho vivimos en una sociedad interconectada en la que, por definición, las redes (que son simplemente una forma de grafos dirigidos en los que cada arco tiene un valor) forman cada vez más parte de nuestra experiencia diaria. Internet es el arquetipo de la red y su conectividad nos une a todos. Como anécdota, al parecer la captura de Saddam Hussein se realizó en parte gracias a la labor de construcción del grafo de su red de soporte, basada en las relaciones funcionales de Saddam con miembros de su partido pero sobre todo de las relaciones tribales y familiares que le unen a su ciudad natal de Tikrit. Solución de problemas por medio de gráficas. Gracias a las gráficas podemos resolver fácilmente problemas que aparentemente son muy complicados. Resolver problemas es la principal aplicación de las gráficas. A continuación mostraremos por medio de un ejemplo como resolver un problema por medio de las gráficas. Problema. ¿Es posible que en un departamento de 25 personas, clasificadas según su desacuerdo, cada persona congenie con exactamente otras 5? Para enfrentar el problema. ¿Dónde comenzar? Muchos problemas discretos se pueden resolver por medio de una grafica. Determinación de una solución. Un elemento fundamental al construir un modelo de gráfica es imaginar lo que esta debe ser: ¿Cuáles son los vértices y cuales las aristas? En este problema, no tenemos muchas opciones; tenemos personas y desacuerdos. Intentemos utilizar a las personas como vértices. En un modelo de gráfica, es común que las aristas indiquen una relación entre vértices. 

Componentes de un Arbol

Es una estructura jerárquica aplicada sobre una colección de elementos u objetos llamados nodos, de los cuales uno es conocido como raíz, además se crea una relación de parentesco entre los nodos dando lugar a términos como padre, hijo, hermano, antecesor, sucesor, ancestro, etc. Un árbol es una estructura que está compuesta por un dato y varios árboles. Dado un nodo cualquiera de la estructura, podemos considerarlo como una estructura independiente, es decir un nodo cualquiera puede ser considerado como la raíz de una árbol completo. En relación con otros nodos: 
Nodo Padre: Nodo que contiene un puntero al nodo actual. En un árbol un nodo solo puede tener un nodo padre.. X es padre de Y sí y solo sí el nodo X apunta a Y, también se dice que X es antecesor de Y. 
Nodo Hijo: Cualquiera de lo nodo apuntado por uno de lo nodo del árbol. Un nodo puede tener varios hijos. X es hijo de Y, sí y solo sí el nodo X es apuntado por Y. También se dice que X es descendiente directo de Y. 
Hermano: Dos nodos serán hermanos si son descendientes directos de un mismo nodo. En cuanto a la posición dentro del árbol: 
Nodo Raíz: Es el único nodo del árbol que no tiene padre. Este es el nodo que usaremos para referirnos al árbol. 
Nodo Hoja: Nodo que no tiene hijos. Se llama hoja o terminal a aquellos nodos que no tienen ramificaciones (hijos). 
Nodo Interior: Es un nodo que no es raíz ni hoja. 
Orden: Es el número potencial de hijos que puede tener cada elemento de árbol. De este modo, diremos que un árbol en el que cada nodo puede apuntar a otros dos es de orden dos, si puede apuntar a tres será de orden tres, etc. Podríamos decir que nuestro árbol de ejemplo es de orden tres. 
Grado: El número de hijos que tiene el elemento con más hijos dentro del árbol. En el árbol del ejemplo, el grado es tres, ya que tanto A como D tienen tres hijos, y no existen elementos con más de tres hijos 
Nivel: Se define para cada elemento del árbol como la distancia a la raíz, medida en nodos. El nivel de la raíz es cero, el de sus hijos uno y así sucesivamente. En el ejemplo, el nodo D tiene nivel 1, el nodo G tiene nivel 2 y el nodo N nivel 3. 
Rama: Es el camino desde el nodo raíz a una hoja. 
Altura: La altura de un árbol se define como el nivel del nodo de mayor nivel. Como cada nodo de un árbol puede considerarse a su vez como la raíz de un árbol, también podemos hablar de altura de ramas, el máximo número de nodos que hay que recorrer para llegar de la raíz a una de las hojas. 
Peso: Es el número de nodos del árbol sin contar la raíz. 


 Propiedades del árbol

Todo árbol es a su vez un grafo bipartito. Todo árbol con sólo un conjunto numerable de vértices es además un grafo plano. Todo grafo conexo G admite un árbol de expansión, que es un árbol que contiene cada vértice de G y cuyas aristas son aristas de G. Dado n vértices etiquetados, hay n n-2 maneras diferentes de conectarlos para construir un grafo. El resultado se llama fórmula de Cayley. El número de árboles con n vértices de grado d1,d2,...,dn es: un coeficiente multinomial.
Contar el número de árboles no etiquetados es un problema complicado. De hecho, no se conoce ninguna fórmula para el número de árboles t(n) con n vértices (debe entederse aquí el número de árboles diferentes salvo isomorfismo de grafos). Los primeros valores de t(n) son 1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159, ... (sucesión A000055 en OEIS). Otter (1948) probó que Una fórmula más exacta para el comportamiento asintótico de t(n) implica que hay dos números a y ß (a ˜ 3 y ß ˜ 0.5) tales que: 


 Clasificación de arboles

Un árbol binario es una estructura de datos en la cual cada nodo siempre tiene un hijo izquierdo y un hijo derecho. No pueden tener más de dos hijos (de ahí el nombre "binario"). Si algún hijo tiene como referencia a null, es decir que no almacena ningún dato, entonces este es llamado un nodo externo. En el caso contrario el hijo es llamado un nodo interno. Usos comunes de los árboles binarios son los árboles binarios de búsqueda, los montículos binarios y Codificación de Huffman.
Tipos de árboles Un árbol binario es un árbol con raíz en el que cada nodo tiene como máximo dos hijos.
Un árbol binario lleno es un árbol en el que cada nodo tiene cero o dos hijos.
Un árbol binario perfecto es un árbol binario lleno en el que todas las hojas (vértices con cero hijos) están a la misma profundidad (distancia desde la raíz, también llamada altura). A veces un árbol binario perfecto es denominado árbol binario completo. Otros definen un árbol binario completo como un árbol binario lleno en el que todas las hojas están a profundidad n o n-1, para alguna n. Un árbol binario es un árbol en el que ningún nodo puede tener más de dos subárboles. En un árbol binario cada nodo puede tener cero, uno o dos hijos (subárboles). Se conoce el nodo de la izquierda como hijo izquierdo y el nodo de la derecha como hijo derecho.



Dado un grafo conexo, un árbol recubierto mínimo de ese grafo es un subgrafo que tiene que ser un árbol y contener todos los vértices del grafo inicial. Cada arista tiene asignado un peso proporcional entre ellos, que es un número representativo de algún objeto, distancia, etc... , y se usa para asignar un peso total al árbol recubierto mínimo computando la suma de todos los pesos de las aristas del árbol en cuestión. Un árbol recubridor mínimo o un árbol expandido mínimo es un árbol recubridor que pesa menos o igual que otros árboles recubridores. Todo grafo tiene un bosque recubridor mínimo. En el caso de un empate, porque podría haber más de un árbol recubridor mínimo; en particular, si todos los pesos son iguales, todo árbol recubridor será mínimo. De todas formas, si cada arista tiene un peso distinto existirá sólo un árbol recubridor mínimo. La demostración de esto es trivial y se puede hacer por inducción. Esto ocurre en muchas situaciones de la realidad, como con la compañía de cable en el ejemplo anterior, donde es extraño que dos caminos tengan exactamente el mismo coste. Esto también se generaliza para los bosques recubridores. Si los pesos son positivos, el árbol recubridor mínimo es el subgrafo de menor costo posible conectando todos los vértices, ya que los subgrafos que contienen ciclos necesariamente tienen más peso total. 
 Recorrido de un árbol


Árbol binario
• Preorden: (raíz, izquierdo, derecho). Para recorrer un árbol binario no vacío en preorden, hay que realizar las siguientes operaciones recursivamente en cada nodo, comenzando con el nodo de raíz:
1. Visite la raíz
2. Atraviese el sub-árbol izquierdo
3. Atraviese el sub-árbol derecho
• Inorden: (izquierdo, raíz, derecho). Para recorrer un árbol binario no vacío en inorden (simétrico), hay que realizar las siguientes operaciones recursivamente en cada nodo:
1. Atraviese el sub-árbol izquierdo
2. Visite la raíz
3. Atraviese el sub-árbol derecho
• Postorden: (izquierdo, derecho, raíz). Para recorrer un árbol binario no vacío en postorden, hay que realizar las siguientes operaciones recursivamente en cada nodo:
1. Atraviese el sub-árbol izquierdo
2. Atraviese el sub-árbol derecho
3. Visite la raíz
En general, la diferencia entre preorden, inorden y postorden es cuándo se recorre la raíz. En los tres, se recorre primero el sub-árbol izquierdo y luego el derecho.
• En preorden, la raíz se recorre antes que los recorridos de los subárboles izquierdo y derecho
• En inorden, la raíz se recorre entre los recorridos de los árboles izquierdo y derecho, y
• En postorden, la raíz se recorre después de los recorridos por el subárbol izquierdo y el derecho 

Arboles ( Matematicas discretas)

Un grafo conectado que contiene circuitos no simples se llama árbol. En el año de 1857 Arthur Cayley, matemático inglés, los empleó para contabilizar componente químicos, no obstante, es importante señalar que no solo es una herramienta de la química sino que se han utilizado en diversas áreas, por ejemplo, conforme el propio interés de la materia encaminado hacia las ciencias de la computación, se utiliza para la construcción de las redes.

6.1.1. Propiedades de los árboles

Entre las propiedades más importantes de los árboles está la presencia de un paseo entre cualquiera de dos vértices del árbol; segundo, que el número de vértices no es menor al número de aristas del árbol y que un árbol con más de dos vértices tiene por lo menos dos hojas.

Un ejemplo claro de los árboles en la vida cotidiana son los árboles genealógicos. Para este caso, los vértices representan a los miembros de la familia y los arcos representan la relación de parentesco. Conforme los conocimientos adquiridos con anterioridad, el árbol no deja de ser un grafo, pero es del tipo no dirigido.

Ejemplo de árbol genealógico:
En este ejemplo cabe señalar que los recuadros representan los vértices del grafo y los arcos son las líneas que representan las relaciones de parentesco conforme a esta familia:

6.1.2. Representación de árboles

El árbol es un grafo no dirigido conectado con circuitos no simples; además, no contiene arcos múltiples, con la propiedad de que hay un único camino simple entre cada par de vértices, teniendo el siguiente teorema:

Teorema 1. “Un grafo no dirigido es un árbol si y solo si hay un camino simple único entre cualesquiera dos de sus vértices”.

Conforme los siguientes grafos, ¿cuál de ellos es del tipo de árboles?
Ejemplo:
¿Cuáles de los grafos de la figura 6.2  son árboles?
Fig. 6.2 Grafo 1,2 y 3
Si se observan los siguientes grafos, se concluye que el grafo G1 no es un árbol porque se observa un circuito simple, pero los grafos G2 Y G3  son de árboles, porque están conectados con circuitos no simples.
Como se sabe, existen grafos que no tienen conexión y podría existir confusión el pensar que un árbol es un grafo conectado que tiene circuitos no simples, pero es importante mencionar que existen árboles del tipo que contienen circuitos no simples que no necesariamente están conectados, y esos árboles reciben el nombre de bosques, cuya característica es que cada uno de sus componentes conectados es un árbol.
            Los árboles son mostrados a continuación:

En gran parte de las aplicaciones de árboles, se designa a un vértice particular del árbol como la raíz, por lo que se pude asignar una dirección a cada arco, debido que hay un camino único de la raíz a cada vértice del grafo dirigiéndose cada arco alejándose de la raíz, conforme lo enunciado en el teorema 1, en el apartado 6.1.2, por lo tanto es un grafo de árbol con raíz, esto es simplemente el árbol que junto con su raíz forman un grafo y en caso que fuesen diferentes s vértices como raíz, se producen diferentes árboles con raíz.
A continuación se muestra un grafo de árbol con raíz:

De acuerdo a lo anterior se muestran los árboles con raíz en donde a y c son las raíces correspondientes del grafo R.
Lo usual es elaborar un grafo de árbol con raíz en la parte superior del grafo, en donde las flechas muestran la dirección de los arcos, como se muestra en la siguiente figura:

En el apartado 6.1.1 se elaboró un árbol genealógico con el propósito de familiarizarse con la terminología, que es de origen genealógico y botánico. 

Observe el siguiente grafo:
 
 
En esta figura se deduce que E es un árbol con raíz a, se observa que los padres son b, c y d y, a su vez, don hermanos, f y g son hijos de b; además, e es hijo de c.

Otro ejemplo: si se supone que A es un árbol con raíz, si v es un vértice en A diferente de la raíz, el padre de v es el único vértice u tal que hay un arco dirigido de u a v. Cuando u es el padre de v, v es llamado un hijo de u. Los vértices con el mismo padre son llamados hermanos. Los ancestros de un vértice diferente de la raíz son los vértices en el grafo de la raíz a ese vértice, excluyendo el vértice mismo e incluyendo a la raíz. Los descendientes de un vértice v son aquellos vértices que tienen a v como ancestro. Un vértice de un árbol es llamado hoja si no tiene hijos. Los vértices que tienen hijos son llamados vértices internos. La raíz es un vértice interno a menos que sea el único vértice del grafo, en ese caso es una hoja. Si a es un vértice en un árbol, el subárbol con a como raíz, es el subgrafo del árbol que consiste de a y sus descendientes y todos los arcos incidentes en estos descendientes.
 
Observa la siguiente figura:
En este árbol Z, la raíz está en el vértice a; el padre de c es b y su descendencia es b y e; los hermanos de i son h y j, quienes son hijos de g; además, se pueden observar todos los ancestros de m; y también se observa que en este árbol con raíz existe otro subárbol con raíz en el vértice g, si se hace un acercamiento en esta parte se percibe lo siguiente:
 

El padre es g, los hijos son h,i,j y los descendentes de j son i e m, y el descendente de h es k.
También existe un árbol con raíz, de nombre m-ario, que consiste en que cada vértice interno no tiene más de m hijos, y  el árbol m-ario completo consiste si cada vértice tiene exactamente m hijos y  un árbol m-ario con cuando m=2 es llamado árbol binario.

Conforme lo visto anteriormente se distinguirán los diferentes tipos de grafos:
Este grafo representa un árbol binario completo porque cada uno de sus vértices internos tiene un hijo.

En esta figura se representa un árbol, árbol 3-ario, completo porque cada uno de sus vértices internos tiene tres hijos.
 
Este es un árbol 5-ario completo porque cada vértice interno tiene 5 hijos.


Este grafo representa un árbol m-ario completo para alguna m, porque algunos de sus vértices internos tienen dos hijos y otros tienen tres hijos.

También existe el caso de un árbol con raíz ordenado debido que los hijos de cada vértice interno están ordenados, y estos se expresan en el grafo de tal forma que los hijos de cada vértice interno se representan en orden de izquierda a derecha. Si el árbol con raíz ordenado tiene un vértice interno del cual emanan dos hijos, el primero se nombra hijo izquierdo y el segundo es llamado hijo derecho.

6.1.3. Árboles de decisión

Los árboles con raíz pueden ser empleados para modelar problemas en los que una serie de decisiones conducen a la solución. Por ejemplo, un árbol binario de búsqueda es utilizado para localizar elementos basados en una serie de comparaciones, donde cada comparación nos dice si hemos localizado el elemento o si debemos ir a la izquierda o a la derecha. Un árbol con raíz, en el que cada vértice interno corresponde a una decisión, con un subárbol en esos vértices para cada posible resultado de la decisión, se llama árbol de decisión.
El siguiente ejemplo ilustra una aplicación de los árboles de decisión. Supóngase que hay 7 monedas, todas con el mismo peso y una moneda falsa que pesa menos que las otras, el cuestionamiento es: ¿Cuántas pesadas son necesarias usando una balanza para determinar cuál de las ocho monedas es la falsa? Con el algoritmo se puede encontrar la moneda falsa.

Existen tres posibilidades cada vez que se realiza una pesada con la balanza, Las dos charolas tienen un mismo peso, la primera charola pesa más o la segunda pesa más. En consecuencia, el árbol de decisión de la secuencia de pesadas es un árbol 3-ario. Hay al menos 8 hojas en el árbol de decisión, ya que hay ocho posibles resultados, y cada posible resultado tiene que ser representado por al menos una hoja. El número más grande de pesadas que se necesita para determinar la moneda falsa es la altura del árbol de decisión. Así, al menos dos pesadas son necesarias. Es posible determinar la moneda falsa usando dos pesadas:

Algoritmos de recorrido y busqueda a lo ancho y profundidad

Ambas técnicas constituyen métodos sistemáticos para visitar todos los vértices y arcos del grafo, exactamente una vez y en un orden específico predeterminado, por lo cual podríamos decir que estos algoritmos simplemente nos permiten hacer recorridos controlados dentro del grafo con algún propósito.
Siendo la búsqueda una de las operaciones más sencillas y elementales en cualquier estructura de datos, se han estandarizado el uso de estos algoritmos para ello, por lo que se conocen como algoritmos de búsqueda. Sin embargo, es importante resaltar que pueden utilizarse para muchísimas otras operaciones con grafos que no necesariamente incluyan la búsqueda de algún elemento dentro del grafo.

Manejo de grafos

El recorrido que se hace con este tipo de algoritmos es acíclico por lo que se dice que el recorrido es de árbol, sin embargo la entrada sigue siendo un grafo.
Información
Un árbol es un grafo conexo acíclico, y para cada par de vértices distintos sólo existirá un único camino. Un árbol está conformado por vértices (nodos) y arcos (aristas), al igual que un grafo. Si existe un camino de longitud 1 entre dos nodos en un árbol uno de ellos será el padre y el otro será el hijo. Los vértices pueden ser de tres tipos:
  • Raíz: Es el único nodo que no tiene padre.
  • Nodo Interno: Es aquel que posee al menos un hijo.
  • Nodo Externo: Es aquel que no tiene hijos, también se le denomina hoja.
Búsqueda BFS:DFS árboles
Si deseas repasar algunos conceptos referentes a la teoría de grafo puedes visitar el tutorial: Algoritmo de Dijkstra.
Haremos nuestra implementación en el lenguaje de programación C++, utilizando Visual Studio 10.0. Lo primero que hay que hacer tanto en BFS como en DFS es crear toda la estructura base que nos permitirá manejar el grafo. Utilizaremos listas de adyacencia para representar el grafo, en este caso trabajaremos con un grafo no dirigido y no ponderado. En el tutorial antes mencionado se explica en detalle las consideraciones base para manejar el grafo, puedes consultarlo como referencia para la creación de esta primera parte del código.
En ambos casos el recorrido se inicia desde un nodo raíz, elegido arbitrariamente (En el caso de que el grafo sea un árbol, la raíz ya está determinada). También se debe indicar el elemento que ha de ser buscado.
Es fundamental marcar cada uno de los nodos que han sido visitados, de lo contrario podremos repetir los nodos una y otra vez, con lo cual no podremos salir nunca de nuestro algoritmo. Esta característica es lo que garantiza precisamente que el recorrido sea acíclico. Para ello usaremos un vector de booleanos, que inicializamos en falso, y la declararemos como una variable global.
1vector<bool> mark;
2mark = vector<bool>(graph.V, false);
La diferencia entre BFS y DFS es el orden en que ha de recorrerse el grafo, para algunos problemas no tiene ninguna relevancia cual de los dos algoritmos ha de aplicarse, pero en otros casos esta elección es crucial.

Algoritmo de Búsqueda en Anchura (BFS)

La búsqueda en anchura supone que el recorrido se haga por niveles. Para entender más fácilmente de que se trata, hemos indicado en la siguiente imágen un grafo ejemplo en donde cada color representa un nivel, tomando como raíz o nodo inicial el que tiene el número 1. El recorrido se hará en orden numérico de forma consecutiva hasta llegar al nodo número 7.
Búsqueda BFS
La estrategia que usaremos para garantizar este recorrido es utilizar una cola que nos permita almacenar temporalmente todos los nodos de un nivel, para ser procesados antes de pasar al siguiente nivel hasta que la cola esté vacía.
1queue<State> q;
2q.push(State(raiz));
3mark[raiz] = true;
Inmediatamente después de declarar nuestra estructura de cola, agregamos el nodo raíz para poder iniciar el proceso de búsqueda. Esto se hace porque necesitamos tener al menos un elemento en nuestra cola, dado que la condición de salida es que la cola esté vacía. Luego marcamos el nodo raíz como visitado.
1while(!q.empty())
2{
3    State st = q.front();
4    q.pop();
5    if (st.node == nodo){
6        printf("'%d'n",nodo);
7        return;
8    }else printf("%d ",st.node);
9
10    int T = (int)graph.G[st.node].size();
11    for(int i = 0; i < T; ++i)
12    {
13        if (!mark[graph.G[st.node][i].node])
14        {
15            mark[graph.G[st.node][i].node] = true;
16            q.push(State(graph.G[st.node][i].node));
17        }
18    }
19}
Cada vez que visitamos un nodo, lo desencolamos e imprimimos por pantalla el valor del nodo para ir indicando el recorrido. Luego agregamos a la cola todos los nodos del siguiente nivel y los marcamos como visitados antes de comenzar el ciclo de nuevo, en el que procesaremos estos nuevos nodos que hemos agregado a la cola.
Si encontramos el elemento buscado, la función BFS retornará al “main” e imprimirá entre comillas simples el valor del elemento buscado.
Podríamos hacer otro tipo de mensaje para indicar que el elemento ha sido encontrado, calcular el número de nodos que fueron visitados o incluso generar un mensaje especial en el caso de que el elemento no haya sido encontrado. Por el momento la función ha quedado lo más sencilla posible para enfocarnos únicamente en cómo hacer el recorrido.

Algoritmo de Búsqueda en Profundidad (DFS)

En el caso de la búsqueda en profundidad lo que se quiere es recorrer desde la raíz hasta los nodos extremos u hojas por cada una de las ramas. En este caso los niveles de cada nodo no son importantes. En la siguiente imagen podemos ver el orden en que se hace el recorrido desde el nodo raíz, indicado con el número uno, hasta el nodo número siete.
Búsqueda DFS
La forma más intituiva de hacer este algoritmo es de forma recursiva, de lo contrario tendríamos que usar en lugar de una cola una pila, pero con la recursión nos ahorramos la necesidad de utilizar esta estructura explícitamente y en lugar de ello nos valemos de la pila de recursión.
En este caso pasaremos por parámetro el nodo a buscar y el nodo actual (El nodo que esta siendo visitado en cada ambiente de recursión), que en la primera llamada será el nodo raíz.
1void DFS(const int nodo, int nodoAct){
2    mark[nodoAct] = true;
3    if(mark[nodo]==true) return;
4    if (nodoAct == nodo){
5            printf("'%d'n",nodo);
6            return;
7    }else if(mark[nodo]==true) return;
8    else{
9        printf("%d ",nodoAct);
10
11        int T = (int)graph.G[nodoAct].size();
12        for(int i = 0; i < T; ++i)
13        {
14            if (!mark[graph.G[nodoAct][i].node]) DFS(nodo, graph.G[nodoAct][i].node);
15        }
16    }
17}
En cada llamada recursiva marcaremos el nodo actual como visitado y luego verificamos si es el nodo buscado para salir de la recursión, este será nuestro caso base. De no ser el nodo requerido, se hace la llamada recursiva con todos los nodos hijos del nodo actual, pero en este caso, a diferencia del recorrido BFS, no se visitarán todos los hijos de forma consecutiva, sino que el algoritmo recorrerá en profundidad hasta llegar a un nodo extremo o nodo hoja, antes de retornar al ambiente de recursión en donde se encuentran los otros nodos hijos.
El orden en que se eligen las ramas en un recorrido DFS está determinado por el tipo de recorrido de procesamiento de árbol que se haya elegido, estos pueden ser:
  • Pre-orden: Se procesa primero la raíz, luego la rama izquierda y luego las ramas siguientes hasta llegar a la que se encuentra más a la derecha.
  • Post-orden: Se procesa el árbol desde las ramas izquierdas hasta la que se encuentra más a la derecha. Finalmente se procesa el nodo raíz
  • Simétrico o In-orden: Se procesa la rama de la izquierda, luego el nodo raíz y luego la rama derecha.


Un grafo de ejemplo

Supongamos que tenemos el siguiente grafo leído:
Búsqueda BFS:DFS ejemplo


Queremos ver el orden en que cada algoritmo ha de recorrer el grafo. Llamaremos a la función BFS en el main y posteriormente la función DFS. Tomaremos el nodo ’2′ como nuestro nodo raíz para ambos algoritmos y la salida obtenida será: