El algoritmo a (asterisco)

7,993 views
7,618 views

Published on

0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,993
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
285
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

El algoritmo a (asterisco)

  1. 1. I.1.1 El algoritmo A* Introducción al algoritmo a* (star o asterisco) El algoritmo A* (conocido también como A estrella o asterisco) es un algoritmo de búsqueda que encuentra la ruta de menor coste entre dos puntos siempre y cuando se cumplan una serie de condiciones. Está clasificado dentro de los algoritmos de búsqueda en grafos ya que tiene la necesidad de dar a los mecanismos robóticos, vehiculares o virtuales un sistema de navegación autónomo. Es ampliamente utilizado en las ciencias de la computación para encontrar rutas y que tan transitable es una gráfica, es decir, se refiere al problema de visitar todos los nodos en una gráfica dada de forma particular , esto no es más que el proceso de trazado de la ruta más eficiente entre unos puntos llamados nodos. Este algoritmo goza de una aceptable y continua implementación gracias a su desempeño y precisión. Fue descrito por primera vez en 1968 como una extensión del algoritmo de Dijkstra (1959), por Peter Hart, Nils Nilsson y Bertran Raphael, que expusieron que el A* lograba un mejor desempeño con respecto al tiempo usando heurísticas. I.1.1 Orígenes del algoritmo A* En 1964 Nils Nilsson inventó una heurística basada en aproximaciones para incrementar la velocidad del algoritmo de Dijkstra. Este algoritmo fue llamado inicialmente como A1. En 1967 Bertran Raphael hizo mejoras dramáticas sobre este algoritmo pero falló en demostrar su optimalizad. Él llamó a este algoritmo como A2. Hart introdujo un argumento que el A2 era óptimo usando una heurística consistente haciendo unos cambios menores. Su comprobación del algoritmo también demostró que el A2 era el mejor algoritmo dadas algunas condiciones. Así Hart llamó a este nuevo algoritmo con la sintaxis “Kleene star” (debido a que representa una operación con solamente un operando [5, 6]) por ser un algoritmo que empieza con la letra A e incluye todas las versiones de números posibles o A*. El algoritmo A*, (leído A estrella) es un algoritmo de búsqueda en grafos. Fue presentado por Peter E. Hart y Nils J. Nilsson (1968), y Bertram Raphael (1968), el
  2. 2. algoritmo encuentra, siempre y cuando se cumplan unas determinadas condiciones, el camino de menor coste entre una condición inicial y una condición meta. El algoritmo A* es usualmente utilizado en los problemas para encontrar la mejor ruta o camino, lo que realiza el algoritmo es construir todas las rutas desde un punto inicial hasta encontrar alguna que llegue al nodo final o meta. De éste modo solamente construye aquellas rutas que son candidatas a formar una solución o camino desde el inicio hasta el nodo final. Para poder determinar que rutas son las que tienen mayor probabilidad de llegar al nodo meta, el algoritmo utiliza una heurística basada en la distancia de cualquier punto dado hacía la meta. Donde se diferencia el algoritmo A* de otros algoritmos avaros de “best-first search” es el hecho de que va tomando en cuenta la distancia que ya ha recorrido hasta el momento, haciendo de este modo una respuesta mucho más completa y óptima. Este algoritmo en particular es uno de los más adecuados para este proyecto de investigación ya que según los creadores del mismo es un algoritmo que encuentra el camino de menor coste de un recorrido A aun recorrido B. Aplicando dicho algoritmo a este proyecto de investigación pretendemos obtener un buen resultado en cuanto a la optimización de rutas de transportes. I.1.1 Descripción del algoritmo A* Es un algoritmo que se encarga de encontrar la ruta que representa un menor costo entre un punto A y otro B. Es una técnica de búsqueda para grafos que utiliza una función heurística (ya que trata de métodos exploratorios durante la resolución del problema en el cual la solución se descubre por la evaluación del progreso logrado en la búsqueda de un resultado final denotada f ’(x), la cual es una aproximación a f(x) que proporciona la verdadera evaluación de un nodo para determinar el orden en que se realiza la visita a los otros nodos del árbol. La función de evaluación es el resultado de la suma de otras dos funciones, una que indica el coste del camino que se ha seguido hasta un cierto nodo la cual se representa como g(x) y otra que nos da una estimación de la distancia del punto o nodo en que nos encontramos hasta la meta que está representada como h(x). Entonces la función de evaluación es el resultado de la suma de h(x) y g(x).
  3. 3. Entonces cada una de ellas se representa de la siguiente manera: Donde: costo: representa el valor explícito de movernos de nuestro punto actual al siguiente en alguna dirección dada, si no se especifica se toma como unitario ya que pertenece a una distribución uniforme. abs: representa el valor absoluto de la operación dentro del paréntesis. nodox, nodoy: son las coordenadas (x, y) del nodo en el que nos encontramos actualmente. objetivox , objetivoy : representa las coordenadas de posición de nuestra meta u objetivo al cual deseamos llegar. Cuando comenzamos a analizar el problema nuestro nodo origen tiene un valor inicial 0 (cero) tanto en h como en g. Posteriormente g(x) se representa de la siguiente manera: Donde: α: representa un valor entre 0 y 1 dado por las circunstancias del problema a resolver. padreg : es el valor obtenido de la ecuación g(x) del anterior nodo padre. Cuando iniciamos los cálculos por primera vez, este valor es 0 para el nodo origen. En esta ecuación debemos prestar atención especial conforme vayamos avanzado en el “pathfinding” ya que debemos sumar al resultado obtenido en la ecuación anterior el mismo valor g del padre anterior de nueva forma, es decir, tendríamos algo así: De esta forma podemos empezar a realizar los cálculos para cada uno de los nodos que se vayan analizando. Este algoritmo consiste entonces en atravesar una gráfica (de forma definida normalmente) siguiendo una ruta con el menor costo conocido manteniendo una
  4. 4. cola sorteada de prioridades con segmentos de rutas alternas a lo largo del camino para que si en un momento dado la ruta que se está siguiendo arroja un valor (costo) superior en comparación con otro segmento de ruta encontrado, esta abandona esa ruta actual de alto coste y en vez de ella toma la que le brinda un camino más “barato”. De esta forma el proceso sigue hasta alcanzar la meta u objetivo. Empezando en un nodo inicial dado, el algoritmo expande el nodo con el menor valor de f’(x). Este algoritmo mantiene un conjunto de soluciones parciales almacenadas en una cola de prioridad. El proceso continua hasta que una meta tiene un valor f’(x) menor que cualquier otro nodo en la cola o hasta que el árbol ha sido recorrido completamente. Como aspecto positivo podemos considerar que ningún otro algoritmo óptimo nos garantiza expandir menos nodos que A*, pero éste requiere un alto consumo de memoria El siguiente pseudocódigo describe el algoritmo: Initialize OPEN list Initialize CLOSED list Create goal node; call it node_goal Create start node; call it node_start Add node_start to the OPEN list While the OPEN list is not empty { Get node n off de OPEN list with the lowest f(n) Add n to the CLOSED list If n is the same as node_goal we have found the solution; return Solution(n) Generate each successor node n’ of n For each successor node n’ of n { Set the parent of n’ to n Set h(n’) to be the heuristically estimate distance to node_goal Set g(n’) to be g(n) plus the cost to get to n’ from n Set f(n’) to be g(n’) plus h(n’)
  5. 5. If n’ is on the OPEN list and the existing one is as good or better, then discard n’ and continue If n’ is on the CLOSED list and the existing one is as good or better, then discard n’ and continue Remove occurrences of n’ from OPEN and CLOSED Add n’ to the OPEN list } } Return failure (if we reach this point we’ve searched all reachable nodes and still haven’t found the solution, therefore one doesn’t exist). Podemos describir el algoritmo de la siguiente manera: 1) Colocar el nodo inicial en la lista de ABIERTOS. 2) Verificar si la lista de ABIERTOS está vacía. a. Si está vacía entonces no se ha encontrado una solución. Fin. b. Si no está vacía entonces hay que realizar los cálculos de h, g y f correspondientes para obtener el nodo con el mejor valor en f (el de menor costo) de la lista de ABIERTOS y se adiciona a CERRADOS. 3) Evaluar si el nodo que se acaba de enviar a CERRADOS es el objetivo. a. Si es el nodo objetivo entonces se encontró la solución. b. De lo contrario se prosigue con las siguientes operaciones. 4) El nodo sucesor ahora se toma como el nodo actual. 5) Calcular los valores de g, h y f del nodo. 6) El nodo actual posee mejor valor en f que su nodo padre? a. Si posee mejor valor entonces, remueve las ocurrencias de la lista de cerrados. Adiciona sus sucesores (nodos hijos) a la lista de abiertos y regresa al punto 2. b. Si no, termina la operación y regresa a evaluar la condición de si la lista de ABIERTOS se encuentra vacía.
  6. 6. I.1.1 Aplicaciones del algoritmo A* De manera general este algoritmo se utiliza para encontrar el camino más corto entre dos puntos dados, pero dentro de sus otras aplicaciones se utiliza en juegos para determinar el recorrido que un objeto debe realizar; un ejemplo de ello es el videojuego pacman en el que se aplicaba al comportamiento de los fantasmitas y en la resolución de los problemas de Sudoku de 8 variables. También se puede utilizar para resolver el cubo Rubik mostrándonos cómo hacerlo con la menor cantidad de movidas. Debemos tener en cuenta que hay circunstancias en las que no es sencillo encontrar una buena función heurística pero también es válido especificar que sea cero ya que la misma debe ser estimada por defecto. A continuación se muestra una aplicación desarrollada en modo consola la cual consiste en seleccionar de una cuadrícula de 4x5 un punto inicial y un punto final; con esos puntos el algoritmo deberá indicar el recorrido que se tendrá que hacer para llegar de uno a otro de la forma más económica posible. El recorrido estará truncado por una serie de “paredes” que dificultarán el libre trazo de la ruta.
  7. 7. Ilustración 1. Pantalla inicial para selección de puntos de origen y destino. En la figura 1 se puede apreciar a través del uso de ‘ * ’ las barreras que por naturaleza se nos están infligiendo, mismas que provocarán que el algoritmo tome una serie de decisiones para elegir el camino que más le conviene.
  8. 8. Ilustración 2. Pantalla que muestra el resultado de la búsqueda. En la figura anterior ya se muestra el resultado obtenido de haberle indicado al algoritmo que se deseaba ir del punto 1 al 18. En la última parte que se muestra en la ventana donde nos especifica el trayecto o la ruta a recorrer se lee de derecha izquierda siguiendo el orden de las flechas. I.1.2 Motivación y descripción del algoritmo A* El problema de algunos algoritmos de búsqueda en grafos informados, como puede ser el algoritmo voraz, es que se guían en exclusiva por la función heurística, la cual puede no indicar el camino de coste más bajo, o por el coste real de desplazarse de un nodo a otro (como los algoritmos de escalada), pudiéndose dar el caso de que sea necesario realizar un movimiento de coste mayor para alcanzar la solución. Es por ello bastante intuitivo el hecho de que un buen algoritmo de búsqueda informada debería tener en cuenta ambos factores, el valor heurístico de los nodos y el coste real del recorrido. Así, el algoritmo A* utiliza una función de evaluación , donde representa el valor heurístico del nodo a evaluar desde el actual, n, hasta el final, y , el coste real del camino recorrido para llegar a dicho nodo, n, desde el nodo inicial. A* mantiene dos estructuras de datos auxiliares, que podemos denominar abiertos, implementado como una cola de prioridad (ordenada por el valor de cada nodo), y cerrados, donde se guarda la información de los nodos que ya han sido visitados. En cada paso del algoritmo, se expande el nodo que esté primero en abiertos, y en caso de que no sea un nodo objetivo, calcula la nodo evaluado a cerrados. de todos sus hijos, los inserta en abiertos, y pasa el
  9. 9. El algoritmo es una combinación entre búsquedas del tipo primero en anchura con primero en profundidad: mientras que tiende a primero en profundidad, tiende a primero en anchura. De este modo, se cambia de camino de búsqueda cada vez que existen nodos más prometedores. I.1.3 Propiedades del algoritmo Como todo algoritmo de búsqueda en amplitud, A* es un algoritmo completo: en caso de existir una solución, siempre dará con ella. Si para todo nodo n del grafo se cumple , nos encontramos ante una búsqueda voraz. Si para todo nodo n del grafo se cumple , A* pasa a ser una búsqueda de coste uniforme no informada. Para garantizar la optimalidad del algoritmo, la función debe ser admisible, esto es, que no sobrestime el coste real de alcanzar el nodo objetivo. De no cumplirse dicha condición, el algoritmo pasa a denominarse simplemente A, y a pesar de seguir siendo completo, no se asegura que el resultado obtenido sea el camino de coste mínimo. Asimismo, si garantizamos que que para cualquier nodo es consistente (o monótona), es decir, y cualquiera de sus sucesores, el coste estimado de alcanzar el objetivo desde n no es mayor que el de alcanzar el sucesor más el coste de alcanzar el objetivo desde el sucesor. I.1.4 Complejidad computacional del algoritmo La complejidad computacional del algoritmo está íntimamente relacionada con la calidad de la heurística que se utilice en el problema. En el caso peor, con una heurística de pésima calidad, la complejidad será exponencial, mientras que en el caso mejor, con una buena , el algoritmo se ejecutará en tiempo lineal. Para que esto último suceda, se debe cumplir que donde h* es una heurística óptima para el problema, como por ejemplo, el coste real de alcanzar el objetivo.
  10. 10. I.1.5 Complejidad en memoria El espacio requerido por A* para ser ejecutado es su mayor problema. Dado que tiene que almacenar todos los posibles siguientes nodos de cada estado, la cantidad de memoria que requerirá será exponencial con respecto al tamaño del problema. Para solucionar este problema, se han propuesto diversas variaciones de este algoritmo, como pueden ser RTA*, IDA* o SMA*. I.1.6 Implementación en pseudocódigo I.1.1 Tratar punto .:= . // coste del camino hasta . caso . = . perteneciente a () si g(.) < g(.) entonces // (-----) // nos quedamos con el camino de menor coste .:= MEJORNODO actualizar g(.) y f'(.) propagar g a . de . eliminar . añadir . a ._MEJORNODO caso . = . perteneciente a )-----( si g(.) < g(.) entonces // nos quedamos con el camino de menor coste .:= MEJORNODO actualizar g(.) y f'(.) eliminar . añadir . a ._MEJORNODO caso . no estaba en ).( ni (.) añadir . a ).( añadir . a ._MEJORNODO f'(.) := g(.) + h'(.) I.1.2 Implementación en pseudocódigo ABIERTOS := [INICIAL] //inicialización CERRADOS := []
  11. 11. f'(INICIAL) := h'(INICIAL) repetir si ABIERTOS = [] entonces FALLO si no // quedan nodos extraer MEJORNODO de ABIERTOS con f' mínima // cola de prioridad mover MEJORNODO de ABIERTOS a CERRADOS si MEJORNODO contiene estado_objetivo entonces SOLUCION_ENCONTRADA := TRUE si no generar SUCESORES de MEJORNODO para cada SUCESOR hacer TRATAR_SUCESOR hasta SOLUCION_ENCONTRADA o FALLO I.1.3 TRATAR_SUCESOR SUCESOR.ANTERIOR := VIEJO // coste del camino hasta SUCESOR caso SUCESOR = VIEJO perteneciente a CERRADOS si g(SUCESOR) < g(VIEJO) entonces // (no si monotonía) // nos quedamos con el camino de menor coste VIEJO.ANTERIOR := MEJORNODO actualizar g(VIEJO) y f'(VIEJO) propagar g a sucesores de VIEJO eliminar SUCESOR añadir VIEJO a SUCESORES_MEJORNODO caso SUCESOR = VIEJO perteneciente a ABIERTOS si g(SUCESOR) < g(VIEJO) entonces // nos quedamos con el camino de menor coste VIEJO.ANTERIOR := MEJORNODO actualizar g(VIEJO) y f'(VIEJO) eliminar SUCESOR añadir VIEJO a SUCESORES_MEJORNODO caso SUCESOR no estaba en ABIERTOS ni CERRADOS añadir SUCESOR a ABIERTOS añadir SUCESOR a SUCESORES_MEJORNODO f'(SUCESOR) := g(SUCESOR) + h'(SUCESOR) I.1.4 Lectura del Procedimiento del algoritmo A* Según Nils J. Nilsson (1968). 1. Empezar con ABIERTO conteniendo sólo el nodo inicial. Poner el valor g de ese nodo a O, su valor h' al que corresponda, y su valor f' a h' + O, es decir h'. Inicializar CERRADOS como la lista vacía.
  12. 12. 2. Repetir el siguiente procedimiento hasta que se encuentre el nodo meta: si no existen nodos en ABIERTOS, informar del fallo. De lo contrario, tomar aquel nodo de ABIERTOS con mejor valor f'. Llamarle MEJORNODO. Quitarlo de ABIERTOS. Colocarlo en CERRADOS. Mirar si MEJORNODO es un nodo meta. Si lo es, salir e informar de la solución (bien sea MEJORNODO si lo único que queremos es el nodo, o el camino que se ha creado entre el estado inicial y MEJORNODO si estamos interesados en el camino). En caso contrario, generar los sucesores de MEJORNODO, pero no poner MEJORNODO apuntando aún a ellos (antes debemos mirar si alguno de ellos ya ha sido generado). Para cada SUCESOR, hacer lo siguiente: 1. Poner a SUCESOR apuntando a MEJORNODO. Estos enlaces hacia atrás hacen posible recuperar el camino una vez que se ha encontrado la solución. 2. Calcular g (SUCESOR) = g (MEJORNODO) + costes de ir desde MEJORNODO hasta SUCESOR. 3. Mirar si SUCESOR está contenido en ABIERTOS (es decir, si ya ha sido generado pero no procesado). Si es así, llamemos a ese nodo VIEJO. Puesto que este nodo ya existe en el grafo, podemos desechar SUCESOR, y añadir VIEJO a la lista de los sucesores de MEJORNODO. Ahora debemos decidir si el enlace paterno de VIEJO debería apuntar a MEJORNODO. Debería ocurrir así si el camino que hemos encontrado hasta SUCESOR es de menor coste que el mejor camino actual hasta VIEJO (puesto que SUCESOR y VIEJO son en realidad el mismo nodo). Para ver si cuesta menos llegar hasta VIEJO a través de su padre actual o hasta SUCESOR a través de MEJORNODO, comparar sus valores g. Si VIEJO tiene menor (o igual) coste, no necesitamos hacer nada. Si SUCESOR tiene menor coste, entonces hacer que el enlace paterno de VIEJO apunte hacia MEJORNODO, grabar el nuevo camino óptimo en g (VIEJO), y actualizar f'(VIEJO). 4. Si SUCESOR no estaba en ABIERTOS, ver si estaba en CERRADOS. En caso afirmativo, llamar VIEJO al nodo de CERRADOS, y añadir VIEJO a la lista de los sucesores de MEJORNODO. Mirar si el nuevo camino es mejor que el viejo tal como se hizo en el paso 2.3 y actualizar apropiadamente el enlace paterno y los valores de g y f'. Si
  13. 13. acabamos de encontrar un mejor camino a VIEJO, debemos propagar la mejora a los sucesores de VIEJO. Esto es un poco delicado. VIEJO apunta a sus sucesores. Cada sucesor a su vez apunta a sus sucesores, y así sucesivamente hasta que cada rama termina en un nodo que bien ya está en ABIERTOS o no tiene sucesores. Por tanto, para propagar el nuevo coste hacia abajo, podemos hacer una búsqueda transversal en profundidad del árbol, empezando en VIEJO, cambiando el valor g de cada nodo (y por tanto su valor f'), terminando cada rama cuando se alcanza bien un nodo sin sucesores o bien un nodo para el que ya se ha encontrado un camino equivalente o mejor. Es fácil examinar esta condición. El enlace paterno de cada nodo apunta hacia atrás a su mejor predecesor conocido. Conforme propagamos a un nodo siguiente, debemos mirar si su predecesor apunta al nodo del que estamos viniendo. Si lo hace así, debemos continuar la propagación. Si no, su valor g ya refleja el mejor camino del que forma parte. Por tanto la propagación debe parar allí. Pero es posible que al propagar el nuevo valor de g hacia abajo, el camino que estamos siguiendo pueda volverse mejor que el camino a través del antecesor actual. Por tanto debemos comparar los dos. Si el camino a través del antecesor actual es aún mejor, debemos detener la propagación. Si el camino a través del cual estamos propagando es ahora mejor, inicializar el antecesor y continuar la propagación. 5. Si SUCESOR no estaba ya en ABIERTOS o en CERRADOS, ponerlo en ABIERTOS y añadirlo a la lista de sucesores de MEJORNODO. Calcular f'(SUCESOR) = g(SUCESOR) + h'(SUCESOR). Podemos hacer algunas observaciones interesantes sobre este algoritmo. La primera concierne al papel de la función g. Nos permite escoger el nodo a expandir a continuación sobre la base, no sólo de cuán bueno parece el nodo en sí mismo (medido por h'), sino también sobre la base de cuán bueno era el camino hasta el nodo. Al incorporar g en f' no siempre elegiremos como nuestro siguiente nodo a expandir el nodo que parece más cercano a la meta. Esto es Útil si nos preocupa el camino que elijamos. Pero si sólo nos importa llegar a una solución de la forma que sea, podemos definir siempre g como 0, con lo que también elegiríamos siempre el nodo que parece más cercano a la meta. Si queremos encontrar un camino que tenga el menor número de pasos, entonces debemos poner el coste
  14. 14. de ir desde un nodo a su sucesor como una constante, usualmente 1. Si, por otra parte, queremos encontrar el camino de menor coste y algunos operadores cuestan más que otros, entonces debemos poner el coste de ir de un nodo a otro de forma que refleje dichos costes. Así pues, el algoritmo A* puede usarse tanto si estamos interesados en encontrar un camino con el coste total mínimo, como si simplemente queremos encontrar cualquier camino de la forma más rápida posible. La segunda observación atañe a h', el estimador de h, la distancia de un nodo a la meta. Si h' es un estimador perfecto de h entonces A* convergerá. Inmediatamente a la meta sin búsqueda. Cuanto mejor sea h' más cerca estaremos de este enfoque directo. Si por otra parte, h' es siempre O, la búsqueda estará controlada por g. Si g también es O, la estrategia de búsqueda se realizará al azar. Si g es siempre 1, se realizará una búsqueda en anchura. Todos los nodos de un nivel tendrán valores g más bajos, y por tanto f' más bajos, que cualquier nodo del siguiente nivel. ¿Qué ocurre, por otra parte, si h' no es ni perfecto ni O? ¿Podemos decir algo interesante sobre el comportamiento de la búsqueda? La respuesta es afirmativa si podemos garantizar que h' nunca sobrestimará h. En ese caso, el algoritmo A* garantiza que encontraremos un camino óptimo (determinado por g) a la meta, si éste existe. Podemos ver esto fácilmente a partir de algunos ejemplos. Consideremos la situación mostrada en la figura 3.13. Supongamos que el coste de todos los arcos es 1. Inicialmente, todos los nodos excepto A están en ABIERTOS (aunque la figura muestra la situación dos pasos más tarde, después de haber expandido B y E). Para cada nodo, se indica f' como la suma de h' y g. En este ejemplo, el nodo B tiene la f' más baja, 4, por lo que lo expandiremos en primer lugar. Supongamos que tiene un único sucesor E, que también parece estar a tres movimientos de la meta. Ahora f'(E) es 5, igual que f'(C). Supongamos que resolvemos esta disyuntiva a favor del camino que estábamos siguiendo actualmente. Entonces expandiremos E a continuación. Supongamos que también él tiene un único sucesor F', también a tres movimientos de la meta. Es evidente que estamos haciendo movimientos sin realizar ningún progreso. Pero f'(F) = 6, que es mayor que f'(C). Por tanto expandiremos C a continuación. Así vemos que al subestimar h(B) hemos desperdiciado algún esfuerzo. Pero en un momento u otro descubrimos que B estaba más lejos de lo que pensábamos, por lo que podemos regresar y encontrar otro camino.

×