Documentos de Académico
Documentos de Profesional
Documentos de Cultura
general
La gira del caballo es un caso especial de una búsqueda en profundidad donde el objetivo
es crear el árbol de búsqueda en profundidad más profundo, sin ramas. La búsqueda en
profundidad más general es realmente más fácil. Su objetivo es buscar lo más
profundamente posible, conectando tantos nodos en el grafo como sea posible y
ramificando donde sea necesario.
Incluso es posible que una búsqueda en profundidad cree más de un árbol. Cuando el
algoritmo de búsqueda en profundidad crea un grupo de árboles llamamos a esto
un bosque de profundidad. Al igual que con la búsqueda en anchura, nuestra búsqueda
en profundidad hace uso de los enlaces a los predecesores para construir el árbol. Además,
la búsqueda en profundidad hará uso de dos variables de instancia adicionales en la
clase Vertice. Las nuevas variables de instancia son los tiempos de descubrimiento y de
finalización. El tiempo de descubrimiento rastrea el número de pasos en el algoritmo antes
de que un vértice sea encontrado por primera vez. El tiempo de finalización es el número
de pasos en el algoritmo antes de que un vértice se pinte de negro. Como veremos después
de examinar el algoritmo, los tiempos de descubrimiento y de finalización de los nodos
proporcionan algunas propiedades interesantes que podemos usar en algoritmos
posteriores.
El código para nuestra búsqueda en profundidad se muestra en el Programa 5. Puesto que
las dos funciones bep y su auxiliar visitabep usan una variable para realizar un
seguimiento del tiempo entre llamadas a visitabep, hemos elegido implementar el código
como métodos de una clase que hereda de la clase Grafo. Esta implementación extiende
la clase Grafo agregando una variable de instancia tiempoy los dos
métodos bep y visitabep. Mirando la línea 11, usted notará que el método bep itera sobre
todos los vértices del grafo llamando a visitabep sobre los nodos que sean blancos. La
razón por la que iteramos sobre todos los nodos, en lugar de simplemente buscar desde
un nodo de partida elegido, es asegurarnos de que se consideren todos los nodos en el
grafo y que no haya vértices que se queden fuera del bosque de profundidad. Puede
parecer inusual ver la instrucción for unVertice in self, pero recuerde que en este
caso self es una instancia de la clase grafoBEP, y que iterar sobre todos los vértices en una
instancia de un grafo es algo natural que hacer.
Programa 5
1
from pythonds.graphs import Grafo
2
class grafoBEP(Grafo):
3
4 def __init__(self):
5 super().__init__()
6 self.tiempo = 0
8 def bep(self):
10 unVertice.asignarColor('blanco')
11 unVertice.asignarPredecesor(-1)
13 if unVertice.obtenerColor() == 'blanco':
14 self.visitabep(unVertice)
15
16 def visitabep(self,verticeInicio):
17 verticeInicio.asignarColor('gris')
18 self.tiempo += 1
19 verticeInicio.asignarDescubrimiento(self.tiempo)
21 if siguienteVertice.obtenerColor() == 'blanco':
22 siguienteVertice.asignarPredecesor(verticeInicio)
23 self.visitabep(siguienteVertice)
24 verticeInicio.asignarColor('negro')
25 self.tiempo += 1
verticeInicio.asignarFinalizacion(self.tiempo)
Aunque nuestra implementación de bea sólo estaba interesada en considerar nodos para
los que había una ruta que llevaba de regreso al inicio, es posible crear un bosque de
anchura que represente la ruta más corta entre todas las parejas de nodos en el grafo.
Dejamos esto como ejercicio. En nuestros próximos dos algoritmos vamos a ver por qué
es importante el seguimiento del árbol de profundidad.
El método visitabep comienza con un solo vértice llamado verticeInicio y explora todos los
vértices blancos vecinos lo más profundamente posible. Si usted examina atentamente el
código de visitabep y lo compara con la búsqueda en anchura, lo que debería notar es que
el algoritmo visitabep es casi idéntico a bea, excepto que en la última línea del
ciclo for interno, visitabep se llama a sí misma recursivamente para continuar la búsqueda
a un nivel más profundo, mientras que bea añade el nodo a una cola para su exploración
posterior. Es interesante observar que donde bea usa una cola, visitabepusa una pila. Usted
no verá una pila en el código, pero está implícita en la llamada recursiva a visitabep.
La siguiente secuencia de figuras ilustra en acción el algoritmo de búsqueda en
profundidad para un grafo pequeño. En estas figuras, las líneas punteadas indican aristas
que están comprobadas, aunque el nodo en el otro extremo de la arista ya se ha añadido
al árbol de profundidad. En el código esta prueba se realiza comprobando que el color del
otro nodo no sea blanco.
La búsqueda comienza en el vértice A del grafo (Figura 14). Puesto que todos los vértices
son blancos al comienzo de la búsqueda, el algoritmo visita el vértice A. El primer paso
al visitar un vértice es pintarlo de gris, lo que indica que se está explorando el vértice y al
tiempo de descubrimiento se le asigna 1. Dado que el vértice A tiene dos vértices
adyacentes (B, D), cada uno de ellos requiere ser visitado también. Tomaremos la
decisión arbitraria de que visitaremos los vértices adyacentes en orden alfabético.
El vértice B se visita a continuación (Figura 15), por lo que se pinta de gris y se asigna 2
a su tiempo de descubrimiento. El vértice B también es adyacente a otros dos nodos (C,
D), así que seguiremos en orden alfabético y visitaremos a continuación el nodo C.
Visitar el vértice C (Figura 16) nos lleva al final de una rama del árbol. Después de pintar
el nodo de gris y asignarle 3 a su tiempo de descubrimiento, el algoritmo también
determina que no hay vértices adyacentes a C. Esto significa que hemos terminado de
explorar el nodo C y por lo tanto podemos pintar el vértice de negro y asignarle 4 al
tiempo final. Usted puede ver el estado de nuestra búsqueda en este punto en la Figura
17.
Dado que el vértice C era el final de una rama, ahora regresamos al vértice B y seguimos
explorando los nodos adyacentes a B. El único vértice adicional que se debe explorar
desde B es D, por lo que ahora podemos visitar D (Figura 18) y continuar nuestra
búsqueda desde el vértice D. El vértice D nos conduce rápidamente al vértice E (Figura
19). El vértice E tiene dos vértices adyacentes, B y F. Normalmente exploraríamos estos
vértices adyacentes en orden alfabético, pero como B ya está pintado de gris, el algoritmo
reconoce que no debería visitar B, ya que hacerlo pondría al algoritmo en un ciclo. Así,
la exploración continúa con el siguiente vértice de la lista, a saber F (Figura 20).
El vértice F tiene sólo un vértice adyacente, C, pero como C está pintado de negro, no
hay nada más que explorar, y el algoritmo ha llegado al final de otra rama. De aquí en
adelante, verá usted desde la Figura 21hasta la Figura 25 que el algoritmo regresa al
primer nodo, asignando los tiempos de finalización y pintando los vértices de color negro.
Figura 14: Construcción del árbol de búsqueda en profundidad-10
El tradicional juego del 8-puzzle consiste, en dado un tablero con 9 casillas, las cuales
van enumeradas del 1 al 8 más una casilla vacía. Dicha casilla vacia, es la que, con
movimientos horizontales, verticales, hacia la izquierda o derecha, debe ser desplazada e
intercambiada con alguno de sus vecinos, de manera que, dada una configuración inicial
se llegue a una configuración final (meta). Este problema, al tratar de ser resuelto
computacionalmente representa un problema al que debemos de tratar con sumo cuidado.
Aunque las reglas del juego sean sencillas de realizar (y evidentemente de programar)
conlleva una complejidad mayor al momento de obtener la solución, es por esta razón que
resulta un ejemplo clásico y muy didáctico para poner en práctica algoritmos de búsqueda
que encuentren la solución eficiente a una configuración de 8-puzzle.
Búsqueda No Informada
Para la solución del problema del 8-puzzle presentamos 3 soluciones haciendo uso de
algoritmos de busqueda no informada:
1. Búsqueda primero en amplitud: expande el nodo más interno sin visitar,
colocando sus nodos borde en una estructura FIFO.
2. Búsqueda primero en profundidad limitada: expande el nodo más
profundo que aún no había sido expandido, colocando sus nodos borde en una
estructura LIFO, expandiendose como máximo hasta una profundidad límite.
3. Búsqueda en profundidad iterativa: combina la búqueda primero en
profundidad con la búsqueda primero en amplitud.
Desde aquí pueden descargar un documento de referencia. Es importante aclarar que
este tipo de búsqueda no aseguran que siempre, dada una configuración inicial se
encontrará la solución, esto debido a lo que se conoce como error de paridad.
Para entender que es error de paridad, primero debemos saber que es una inversión.
Una inversión se presenta cuando un número mayor está "delante" de un número menor
al colocar los números en una sola fila.
Implementación
Se ha implementado los 3 algoritmos de búsqueda no informada. La implementación de
los algoritmos de búsqueda primero en amplitud, primero en profundidad limitada y
en profundidad iterativa se ha realizado usando C++ sobre el compilador MingW 5.1.4.
El codigo fuente y ejecutable para la solución del problema del 8 puzzle se puede
descargar desde aqui.
Experimentos
Se realizaron experimentos acerca de la complejidad de los algoritmos implementados,
las características del computador sobre el que se realizaron los experimentos son:
procesador Pentium IV con memoria RAM 512 MB sobre el sistema operativo
Windows XP usando el lenguaje de programación C++ con el compilador MinGW
5.1.4.
Teoría
El algoritmo A* es usado para encontrar la ruta más cercana para ir de un lugar
a otro (llamados nodos), es el más usado debido a que es sencillo y rápido.
La representación es la siguiente:
f(n) = g(n) + h(n)
g(n) es la distancia total que se toma de ir de la posición inicial a la posición
actual
h(n) es la distancia estimada desde la posición inicial a la posición de destino
de final, en este caso se usa una función heurística para calcular el valor
estimado
f(n) es la suma de g(n) y h(n), y es el valor calculado más corto.
En palabras humanas lo que se necesita es:
– Un nodo o punto inicial
– Un nodo final que representa el final del algoritmo
– Un método para identificar que nodos son traspasables y cuales son
sólidos
– Un método para determinar el costo directo (g) de moverse entre los
nodos
– Un método para determinar el costo indirecto (h)
– Una lista de nodos abiertos, en esta lista se guardaran todos los nodos
que se han identificado como posibles movimientos, pero aún no han sido
evaluados
– Una lista de nodos cerrados, donde se guardaran todos los nodos
evaluados y descartados, aunque no es necesario una lista, basta con un
estado que indique que el nodo se encuentra cerrado
– Una forma de identificar que nodo procede a otro, para poder retornar la
cadena de los nodos
Vamos a continuar con los tutoriales del mapa de tiles, para poder satisfacer la
forma de identificar los nodos sólidos y los que son traspasables y cada nodo
será representado por un tile.
Si vamos a permitir que los actores del juego puedan moverse diagonalmente,
debemos darle un costo directo más bajo al movimiento en diagonal que al
movimiento en línea recta de dos nodos, por ejemplo:
Cómo funciona?
El algoritmo es el siguiente:
1. Si el nodo inicial es igual al nodo final, se retorna el nodo inicial como solución
2. Si no, se adiciona el nodo inicial a la lista abierta
3. Mientras la lista abierta no esté vacía, se recorre cada nodo que haya en la lista abierta y
se toma el que tenga el costo total más bajo
4. Si el nodo obtenido es igual al nodo final, se retornan todos los nodos sucesores al nodo
encontrado
5. Si no , se toma el nodo y se elimina de la lista abierta para guardarse en la lista cerrada
y se buscan todos los nodos adyacentes al nodo obtenido y se adicionan a la lista
abierta a menos que el nodo se encuentre en la lista cerrada o que el nodo sea sólido
6. Si el nodo adyacente ya se encuentra en la lista abierta se verifica que el costo sea
menor, si es menor se cambian los valores de costo, sino se ignora
7. Se vuelve al paso 3 y se repite hasta que el punto 4 sea verdadero o que la lista abierta
quede vacía
Siempre he dicho que una imagen vale más que mil palabras por eso vamos a
tomar como ejemplo la siguiente imagen
El nodo inicial es el nodo (2,4) o el que esta de color azul, el nodo final es el
nodo (3,2) o nodo rojo, los nodos de color verde son nodos sólidos y no pueden
ser traspasados.
Al empezar el algoritmo tendremos el nodo azul como opción y lo agregamos a
la lista abierta, luego como no es el nodo final, obtenemos sus nodos
adyacentes y luego lo dejaremos en la lista cerrada, calculamos los valores de
los nodos de la lista abierta (a menos que ya los hayamos calculado) y
tomamos el de menor valor:
El algoritmo en el código:
Ahora que se ha “explicado” de qué se trata el algoritmo, vamos a crear las
clases y métodos necesarios para implementar el algoritmo.
Agregamos una clase al proyecto SceneManager ( Estoy continuando con el
proyecto de Motor de Tiles), la clase es llamada nodo y contiene la información
del nodo:
1 public class Nodo
2 {
3 #region declaraciones
10
11 #region propiedades
{
13
get
14
{
15
return posicion;
16
}
17
set
18 {
19 posicion = new Vector2((float)MathHelper.Clamp(value.X, 0f, (float)Camara2D.an
21 }
}
22
23
public Int32 GrillaX
24
{
25
get { return (Int32)posicion.X; }
26
}
27
28
public Int32 GrillaY
29 {
30 get { return (Int32)posicion.Y; }
31 }
32 #endregion
33
{
35
NodoPadre = nodoPadre;
36
NodoFinal = nodoFinal;
37
Posicion = posicion;
38
costoG = costo;
39 if (nodoFinal != null)
40 {
42 }
43 }
44
public float Calcularcosto()
45
{
46
return Math.Abs(GrillaX - NodoFinal.GrillaX) + Math.Abs(GrillaY - NodoFinal.Gri
47
}
48
49
public Boolean esIgual(Nodo nodo)
50 {
51 return (Posicion == nodo.Posicion);
52 }
53 }
54
55
56
57
58
La variable NodoPadre sirve para saber que nodo lo precede, y así poder
después formar una cadena con el camino más corto, la variable NodoFinal
indica el nodo destino al cual hay que llegar, esta variable sirve para calcular el
costo indirecto o la heurística.
La variable Posicion representa las coordenadas del nodo en el mapa, las
propiedades GrillaX y GrillaY permiten acceder más rápido a las coordenadas.
Cada vez que se crea un nuevo nodo, se calcula su costo total, con el costo
directo (g) y su costo indirecto (h), la condición de que el nodo final sea
diferente a nulo, es para cuando se crea el nodo final, el método esIgual es útil
para comparar dos nodos, verificando sus posiciones.
Ahora, agregamos otra clase que servirá para encontrar la ruta, la llamaremos
gestorBusqueda:
public class gestorBusqueda
1
{
2
private const Int32 costoIrDerecho = 10;
3
private const Int32 costoIrDiagonal = 15;
4
private List<Nodo> listaAbierta = new List<Nodo>();
5 private List<Vector2> listaCerrada = new List<Vector2>();
6 public TileEngine motor;
9 {
this.motor = motor;
10
}
11
12
/// <summary>
13
/// Adiciona un Nodo a la lista abierta, ordenadamente
14
/// </summary>
15
/// <param name="nodo"></param>
16 private void adicionarNodoAListaAbierta(Nodo nodo)
17 {
18 Int32 indice = 0;
26
{
28
if (motor == null)
29
{
30
return null;
31
}
32 Tile tileInicial = motor.Mapa.tileMapLayers[0].obtenerTile((int)posTileInicial
33 Tile tileFinal = motor.Mapa.tileMapLayers[0].obtenerTile((int)posTileFinal.X,
34
35 if (tileInicial.Colision || tileFinal.Colision)
36 {
37 return null;
}
38
39
listaAbierta.Clear();
40
listaCerrada.Clear();
41
42
Nodo nodoInicial;
43
Nodo nodoFinal;
44
47
49 adicionarNodoAListaAbierta(nodoInicial);
{
51
Nodo nodoActual = listaAbierta[listaAbierta.Count - 1];
52
// si es el nodo Final
53
if (nodoActual.esIgual(nodoFinal))
54
{
55
56 List<Vector2> mejorCamino = new List<Vector2>();
{
58
mejorCamino.Insert(0, nodoActual.Posicion);
59
nodoActual = nodoActual.NodoPadre;
60
}
61
return mejorCamino;
62 }
63 listaAbierta.Remove(nodoActual);
64
66 {
if (!listaCerrada.Contains(posibleNodo.Posicion))
68
{
69
// si ya se encuentra en la lista abierta
70
if (listaAbierta.Contains(posibleNodo))
71
{
72 if (posibleNodo.costoG >= posibleNodo.costoTotal)
73 {
74 continue;
75 }
76 }
adicionarNodoAListaAbierta(posibleNodo);
77
}
78
}
79
// se cierra el nodo actual
80
listaCerrada.Add(nodoActual.Posicion);
81 }
82 return null;
83 }
84
86 {
87 List<Nodo> nodosAdyacentes = new List<Nodo>();
88 Int32 X = nodoActual.GrillaX;
Int32 Y = nodoActual.GrillaY;
89
Boolean arribaIzquierda = true;
90
Boolean arribaDerecha = true;
91
Boolean abajoIzquierda = true;
92
Boolean abajoDerecha = true;
93
94 //Izquierda
95 if ((X > 0) && (!motor.Mapa.tileMapLayers[0].obtenerTile(X - 1, Y).Colision))
96 {
98 }
else
99
{
100
arribaIzquierda = false;
101
abajoIzquierda = false;
102
}
103
104 //Derecha
105 if ((X < motor.Mapa.NumXTiles - 1) && (!motor.Mapa.tileMapLayers[0].obtenerTile
106 {
}
109
else
110
{
111
arribaDerecha = false;
112
abajoDerecha = false;
113 }
114
115 //Arriba
117 {
118 nodosAdyacentes.Add(new Nodo(nodoActual, nodoFinal,
}
120
else
121
{
122
arribaIzquierda = false;
123
arribaDerecha = false;
124 }
125
126 // Abajo
128 {
136
139 {
143
if ((arribaDerecha) && (!motor.Mapa.tileMapLayers[0].obtenerTile(X + 1, Y - 1).
144
{
145 nodosAdyacentes.Add(new Nodo(nodoActual, nodoFinal,
146 new Vector2(X + 1, Y - 1), costoIrDiagonal + nodoActual.costoG));
147 }
148
149 if ((abajoIzquierda) && (!motor.Mapa.tileMapLayers[0].obtenerTile(X - 1, Y + 1)
150 {
154
if ((abajoDerecha) && (!motor.Mapa.tileMapLayers[0].obtenerTile(X + 1, Y + 1).C
155
{
156 nodosAdyacentes.Add(new Nodo(nodoActual, nodoFinal,
157 new Vector2(X + 1, Y + 1), costoIrDiagonal + nodoActual.costoG));
158 }
159
161 }
}
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
4 Tipo = tipo;
5 Colision = false;
Color = Color.White;
6
}
7
2 // Código Temporal
MouseState ms = Mouse.GetState();
3
if ((ms.X > 0) && (ms.Y > 0) &&
4
(ms.X < Camara2D.TamanoPantalla.X) && (ms.Y < Camara2D.TamanoPantalla.Y))
5
{
6
Vector2 mouseLoc = Camara2D.ScreenToWorld(new Vector2(ms.X, ms.Y));
7 if (ms.LeftButton == ButtonState.Pressed)
8 {
9 mouseLoc = mapa.Mapa.GetSquareAtPixel(mouseLoc);
12
List<Vector2> camino = GestorBusqueda.encontrarCamino(mouseLoc, posTile);
13 if (camino != null)
14 {
16 {
23
2 {
3 if (tile.Colision)
4 {
5 tile.Color = Color.Red;
}
6
else
7
{
8
tile.Color = Color.White;
9
}
10 }
11 else
12 {
13 if (tile.Color == Color.Transparent)
14 {
tile.Color = Color.White;
15
}
16
}
17
18
spriteBatch.Draw(layer.sheet.Textura, Vector2.Zero, sourceRect, tile.Color,
19
0, position, scale, SpriteEffects.None, 0.0f);
20
21
algoritmosbúsqueda en escaladaheurísticahill-climbingoptimizaciónpython
sumNk=1k2
Para simplificar permitiremos que los n números se puedan repetir
por lo que la respuesta obvia es que el conjunto de los números sea
[0, 0, 0, ..., 0] por lo que la solución exacta sería 0. Si pensamos por
un momento que la solución no es obvia, la búsqueda exhaustiva
nunca sería óptima puesto que estamos buscando entre los números
reales. Por tanto, ¿como resolveríamos esto con una búsqueda
heurística? Primero de todo pondremos un poco de código, lo
explicamos y luego haremos uso del mismo para ver los resultados
que obtenemos:
import numpy as np
plt.ion()
class Busqueda:
u"""
un algoritmo heurístico
"""
def __init__(self, ngeneraciones, ngenes, lim_sup, lim_inf):
self.ngeneraciones = ngeneraciones
self.ngenes = ngenes
self.lim_sup = lim_sup
self.lim_inf = lim_inf
def padre(self):
self.individuo0 = np.random.rand(self.ngenes) *
return self.individuo0
return np.sum(funcion)
return nindividuo
def calculos(self):
fit_acum = []
individuo = self.individuo0
poblacion = self.individuo0
nindividuo = self.hijo(individuo)
individuo = nindividuo
plt.subplot(311)
plt.xlim(self.lim_inf, self.lim_sup)
plt.text(poblacion[0, gen],
self.fitness(poblacion[0, gen]),
gen+1)
plt.subplot(312)
plt.xlim(self.lim_inf, self.lim_sup)
plt.text(poblacion[generacion, gen],
self.fitness(poblacion[generacion, gen]),
gen+1)
plt.subplot(313)
plt.xlim(0, self.ngeneraciones)
plt.ylim(0., np.max(fitness_acumulado))
plt.plot(fitness_acumulado)
In [3]: busqueda.padre()
In [3]: busqueda.padre()
Out[3]:
Saludos.