Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
2.2 Concepto
A continuación, se exponen una serie de conceptos relativos a la Teoría de pilas
necesarios para este documento.
Las funciones asociadas con stack son:
• empty() – Devuelve si la pila está vacía – Complejidad de tiempo : O(1)
• size() – Devuelve el tamaño de la pila – Complejidad de tiempo : O(1)
• top() – Devuelve una referencia al elemento superior más de la pila –
Complejidad de tiempo : O(1)
• push(g) – Añade el elemento 'g' en la parte superior de la pila – Complejidad
de tiempo : O(1)
• pop() – Elimina el elemento más alto de la pila – Complejidad de tiempo : O(1)
3.3 Operadores
Movimientos de fichas utilizando el espacio en blanco (ficha con valor "0")
4.1.1 Descripción
Dado un grafo o árbol y un vértice inicial s, una búsqueda en anchura procede explorando
las aristas en el grafo para encontrar todos los vértices en G para los cuales hay una
ruta a partir de s. Lo notable de una búsqueda en anchura es que encuentra todos los
vértices que estén a una distancia k de s antes de encontrar cualesquiera vértices que
estén una distancia k+1. Una buena manera de visualizar lo que hace el algoritmo de
búsqueda en anchura es imaginar que está construyendo un árbol, un nivel del árbol a
la vez. Una primera búsqueda en anchura agrega todos los hijos del vértice inicial antes
de que comience a descubrir a alguno de los nietos.
4.1.2 Estrategia.
Esta búsqueda expande siempre el nodo más profundo en el árbol. Una vez que un
nodo no puede ser expandido se vuelve hacia atrás para buscar otro nodo a ser
expandido.
Se expande la raíz, se toma un nodo de los recién expandidos (supongamos el
primero) se expande éste, luego nuevamente se toma un nodo de estos últimos, y así
hasta llegar a un nodo que es la meta o no se pueda expandir más. En este último
caso se retrocede (backtracking) hasta el nodo más reciente que tenga una alternativa
sin explorar.
Sigamos nuestro ejemplo anterior con profundidad primero: Se expande A que es la
raíz del árbol, luego tenemos B y C como los nuevos nodos. Elegimos C para expandir,
que genera D y F. Ahora a diferencia de ancho primero (donde deberíamos elegir B)
elegimos uno de los nuevos (D o F), supongamos D, este genera a E. Ahora elegimos
a E (de los últimos generados) y finalizamos porque es la meta.
En el proceso descrito hemos obviado los nodos repetidos, es decir si ya fue visitado
no lo generamos (por ejemplo cuando expandimos C obviamos A). La siguiente figura
muestra el árbol de búsqueda para profundidad primero.
5.2.1 Estados
Un estado del juego es una configuración del tablero 3X3 con su respectivo movimiento
del espacio, con un número que van de 1 al 8. El estado es como una representación
del juego.
5.2.2 Operadores
Los operadores son todas las acciones disponibles de acuerdo con la mecánica del
juego. En el caso de cualquier n- normal juego de rompecabezas, las posibles acciones
son arriba, abajo, izquierda y derecha.
typedef enum mov
{
ARRIBA, ABAJO, IZQ, DERECHA,
NO_APLICA
} mov;
5.2.3 Nodos
Un nodo constituye nuestro árbol de búsqueda que es diferente a un árbol binario
llegándose a parecer más a un grafo. Contiene información sobre sus padres, hijos,
estados.
Cómo se puede ver, el nodo está compuesto con por un estado en concreto, un nodo
padre y la lista de los Hermanos que tiene ese hijo.
struct Nodo
{
unsigned int d;
Estado* estado; //estado designado para un nodo
Nodo* Padre;
Nodo* listahijo; //lista de nodos secundarios
};
Funciones Descripción
std::stack<Nodo*> pila;
std::vector<Nodo*> listahijos;
std::vector<Estado*> hijosVistos;
Nodo* nodo = (struct Nodo*)malloc(sizeof(Nodo));
//iniciar temporizador almacenara los hijos o
clock_t inicio = clock(); nodos que se expanden
y que sean un
//inicializar la pila con el nodo raíz del árbol de búsqueda movimiento valido
nodo = crearNodo(0, &estado_inicial, NULL);
hijosVistos.push_back(&estado_inicial);
pila.push(nodo); pila que almacenara los
nodos
//mientras haya un nodo en la pila para expandir
while (!pila.empty())
{ bucle casi infinito
//abre el último nodo (pila) de la pila
nodo = pila.top();
pila.pop();
//si el estado del nodo es el estado objetivo
if (estadoHallado(nodo->estado, meta))
quieta el primer break;
elemento //de lo contrario, expande el nodo y actualiza el contador de nodos
expandidos
if (!hijosVistos.empty())
{
listahijos = optenerlistahijo(nodo, hijosVistos); Genera los hijos y
++nodosExpandidos; almacenara en el vector
} listahijos, también
//agrega los hijos del nodo a la cola comparara los estados
for (int i = 0; i < listahijos.size(); i++)
almacenados en el
{ vector hijosVisitos
if (estadoHallado(listahijos.at(i)->estado, meta)) {
nodo = listahijos.at(i);
break; agrega en la pila los
} hijos generados y
else almacena en el vector
{ los mismos en
pila.push(listahijos.at(i)); hijosVistos
hijosVistos.push_back(listahijos.at(i)->estado);
}
}
}
nodo = pila.top();
pila.pop(); False
if (estadoHallado(nodo->estado, meta))
= False
ciclo
listahijos = optenerlistahijo(nodo,
hijosVistos);
++nodosExpandidos;
pila.push(listahijos.at(i));
hijosVistos.push_back(listahijo
s.at(i)->estado);
7 RESULTADOS
La descripción de las operaciones que se realizó para hallar la solución se ve que se tiene
una cantidad de nodos generados de más de nodos que en soluciones anteriores.
8 CONCLUSIONES.
DFS no es ni óptimo ni completo. No es óptimo porque si existe más de una solución, podría
encontrar la primera que estuviese a un nivel de profundidad mayor, y para ver que no es
completo es necesario irse a ejemplos en los que el espacio de búsqueda fuese infinito
También se tuvieron que implementar una especie de tope o límite para parar la iteración.