Anda di halaman 1dari 34

Búsqueda

M OTIVACI ÓN Y T ERMINOLOG ÍA

Cuando un agente inteligente actúa en el mundo se ve enfrentado a una


multiplicidad de alternativas, dentro de las cuales debe elegir.

La elección que se realice dependerá de las consecuencias de la elección.

Un aspecto esencial de los problemas que se enfrentan en Inteligencia Arti-


ficial es el de búsqueda de soluciones.

Los problemas de búsqueda están siembre relacionados con problemas de


representación. Es necesario tener lenguajes de representación para expre-
sar lo que estamos buscando, como evoluciona el mundo, etc.

Jorge Baier Aranda, PUC 162


El Problema de los Misioneros y Caníbales
Usaremos el problema de los Misioneros y Canı́bales para mostrar qué ele-
mentos necesitaremos en un problema de búsqueda.
En este conocido problema hay tres canı́bales, tres misioneros, un rı́o y un bote. Los
canı́bales, los misioneros y el bote se encuentran en una rivera del rı́o. Los seis sujetos
deben cruzar el rı́o, pero el bote permite trasladar a lo más a dos personas a la vez.
Se debe encontrar una secuencia de movimientos de personas en el bote que permita
cruzar a los seis individuos de manera segura. No se debe permitir que hayan más
canı́bales que misioneros en algun lado del rı́o algún momento.

Otros problema clásicos de búsqueda son:


• El coloreo de un mapa con cuatro colores.
• Planificación de tareas (scheduling).
• Problemas de planificación de rutas (vendedor viajero).
• Navegación de robots.
• Encontrar una estrategia para elegir la próxima jugada en un juego con
adversarios.

Jorge Baier Aranda, PUC 163


Terminología
Para analizar los problemas de búsqueda usaremos la siguiente termino-
logı́a:
Estado: Dado un problema, un estado es una configuración del dominio
asociado al problema que se está analizando.
Por ejemplo, en el caso de los misioneros y canı́bales, un estado está dado
por la posición de los misioneros, de los canı́bales y del bote.
¿cómo es un estado para el problema de coloreo de mapas?
El estado del problema lo representaremos con un functor
estado(Ci,Mi,Cd,Md,B)
donde los argumentos corresponden a:
Ci: Canı́bales en la rivera izquierda.
Mi: Misioneros en la rivera izquierda.
Cd: Canı́bales en la rivera derecha.
Md: Misioneros en la rivera derecha.
B: Indica en qué lado del rı́o se encuentra el bote. Los valores posibles
serán izq y der.

Jorge Baier Aranda, PUC 164


Estado Inicial: Dado un problema de búsqueda, existe uno o más estados
que se toman como de partida.

En nuestro ejemplo, el estado inicial se representa por estado(3,3,0,0,izq).


Descripción de Estados Objetivos: Para un problema de búsqueda pue-
den haber varios estados que representen una solución al problema de
búsqueda.
Estos estados son descritos mediante la definición de un predicado o fun-
ción que, dado un estado, retorna verdadero si el estado es un estado
objetivo y falso en otro caso.

Jorge Baier Aranda, PUC 165


En el ejemplo, podemos utilizar la función final/1 para identificar estados
finales. Ası́:
final(estado(0,0,3,3,der)).
Establece que el estado final es uno en el cual todos los misioneros y
canı́bales están al lado derecho del rı́o.
Solución Optima: En muchos casos se necesita encontrar un estado ópti-
mo. Es decir, que sea un estado objetivo, pero que además sea mejor, en
algún sentido, que todas los estados objetivos alternativos. En este caso,
el problema de búsqueda corresponde a un problema de optimización.
Operador: Un operador es una función que, dado un estado, entrega otro
estado. Por ejemplo, en el caso de los misioneros y de los canı́bales, pode-
mos definir el operador de cruce resulta/3 que recibe un estado y genera
un estado sucesor .

Jorge Baier Aranda, PUC 166


El predicado resulta(E,A,Er) cuando Er es el estado que se genera al
ejecutar la acción A sobre el estado E.
Podemos escribir este predicado de la siguiente manera:
resulta(E1,cruzar(C,M),E2):-
posible(cruzar(C,M),E1),
E1 = estado(Ci,Mi,Cd,Md,izq),
Cip is Ci-C,
Mip is Mi-M,
Cdp is Cd+C,
Mdp is Md+M,
E2 = estado(Cip,Mip,Cdp,Mdp,der).

resulta(E1,cruzar(C,M),E2):-
posible(cruzar(C,M),E1),
E1 = estado(Ci,Mi,Cd,Md,der),
Cip is Ci+C,
Mip is Mi+M,
Cdp is Cd-C,
Mdp is Md-M,
E2 = estado(Cip,Mip,Cdp,Mdp,izq).

Jorge Baier Aranda, PUC 167


Donde posible/2 se define de la siguiente manera:
posible(cruzar(C,M),estado(Ci,Mi,_,_,izq)):-
C=<Ci, M=<Mi.
posible(cruzar(C,M),estado(_,_,Cd,Md,der)):-
C=<Cd, M=<Md.

Sucesor Inmediato: Dado un estado s y un operador, el estado s0 que se


obtiene al aplicar el operador al estado s se le llama sucesor inmediato de
s. También se dice que el estado s0 es generado por el operador a partir
del estado s.

En nuestro ejemplo, podemos encontrar los sucesores de un estado utili-


zando los siguientes predicados:
sucesor(E1,E2) :- accion(A),
resulta(E1,A,E2),
seguro(E2).

accion(cruzar(C,M)):- (C=0 ; C=1 ; C=2),


(M=0 ; M=1 ; M=2),
C+M=<2, C+M>=1.

Jorge Baier Aranda, PUC 168


seguro(estado(Ci,Mi,Cd,Md,_)):- (Ci=<Mi; Mi=0),
(Cd=<Md; Md=0).

Espacio de Búsqueda: El espacio de búsqueda está formado por el o los


estados iniciales, junto con todos aquellos estados que se pueden obtener
de la aplicación de alguna secuencia de operadores a algún estado inicial.
En nuestro ejemplo, podemos definir el predicado en espaciobusqueda/1
para definir el espacio de búsqueda de la siguiente manera:
en_espaciobusqueda(estado(3,3,0,0,izq)).
en_espaciobusqueda(E) :- en_espaciobusqueda(Ep),
sucesor(Ep,E).
Ası́, es posible consultar por los estados en el espacio de búqueda de la
siguiente manera:
?- en_espaciobusqueda(E).
E = estado(3, 3, 0, 0, izq) ;
E = estado(2, 3, 1, 0, der) ;
E = estado(2, 2, 1, 1, der)

Yes

Jorge Baier Aranda, PUC 169


Espacio de Búsqueda para el PMC
La siguiente figura muestra parcialmente el árbol de búsqueda que resulta
en el ejemplo de los misioneros y los canı́bales.

En el árbol, sólo se incluyen los estados que resultan de ejecutar acciones


que son posibles y que resultan en estados seguros.

h3, 3, 0, 0, izqi

cruzar(1,0) cruzar(1,1) cruzar(2,0)

h2, 3, 1, 0, deri h2, 2, 1, 1, deri h1, 3, 2, 0, deri


cruzar(1,0)
h2, 3, 1, 0, izqi
cruzar(1,0) cruzar(0,1) cruzar(1,1)

cruzar(1,0) cruzar(1,0) cruzar(2,0)


h3, 3, 0, 0, izqi h2, 3, 1, 0, izqi h3, 3, 0, 0, izqi
h2, 2, 1, 1, deri h1, 3, 2, 0, deri h0, 3, 3, 0, deri
... ... ...

Jorge Baier Aranda, PUC 170


Solucionando el Problema
Para este problema podemos redefinir el predicado en espaciobusqueda de
manera de almacenar la secuencia de acciones que generan los estados del
espacio de búsqueda.

en_espaciobusqueda2(estado(3,3,0,0,izq),[]).
en_espaciobusqueda2(E,L) :-
en_espaciobusqueda2(Ep,Lp),
accion(A),
resulta(Ep,A,E),
seguro(E),
append(Lp,[A],L).

Ası́, en espaciobusqueda2(E,L) se satisface si E pertenece al espacio de


búsqueda y se llega a él mediante la ejecución de las acciones en L.
De esta manera,

?- en_espaciobusqueda2(E,L).
E = estado(3, 3, 0, 0, izq)
L = [] ;

Jorge Baier Aranda, PUC 171


E = estado(2, 3, 1, 0, der)
L = [cruzar(1, 0)] ;

E = estado(2, 2, 1, 1, der)
L = [cruzar(1, 1)] ;

E = estado(1, 3, 2, 0, der)
L = [cruzar(2, 0)] ;

E = estado(3, 3, 0, 0, izq)
L = [cruzar(1, 0), cruzar(1, 0)]

Yes

Para encontrar una solución, podemos hacer la siguiente consulta:

?- en_espaciobusqueda2(E,L),final(E).
E = estado(0, 0, 3, 3, der)
L = [cruzar(1, 1), cruzar(0, 1), cruzar(2, 0), cruzar(1, 0),
cruzar(0, 2), cruzar(1, 1), cruzar(0, 2), cruzar(1, 0),
cruzar(2, 0), cruzar(0, 1), cruzar(1, 1)] ;
...

Jorge Baier Aranda, PUC 172


E = estado(0, 0, 3, 3, der)
L = [cruzar(1, 1), cruzar(0, 1), cruzar(2, 0), cruzar(1, 0),
cruzar(0, 2), cruzar(1, 1), cruzar(0, 2), cruzar(1, 0),
cruzar(2, 0), cruzar(1, 0), cruzar(2, 0)]

Yes

Esto funciona en nuestro ejemplo particular, pero desearı́amos poder exten-


derlo a un caso más general.

Jorge Baier Aranda, PUC 173


Un Algoritmo de Búsqueda Genérico
Para la presentación del algoritmo de búsqueda utilizaremos la siguiente
convención:
Estado: Es un tipo de datos, utilizado para representar los estados de un
problema de búsqueda.

lista estados: Es un tipo de datos que corresponde a una lista cuyos ele-
mentos son estados.

estados iniciales: Es una constante de tipo lista estados que contiene


los estados iniciales del problema de búsqueda.

es objetivo: Es una función que recibe como argumento un estado y retor-


na verdadero si y sólo si el estado que recibe es un estado objetivo. El
chequear un estado especı́fico se conoce como explorar ese estado.

Jorge Baier Aranda, PUC 174


eliminar: Es una función que recibe una lista y un estado. Entrega como
resultado la misma lista a la cual se le ha eliminado el estado que recibe
como argumento.

mezclar: Es una función que recibe dos listas de estados; retorna como re-
sultado la lista que resulta de mezclar ambas listas.

sucesores: Es una función que recibe un estado s y retorna una lista con
los sucesores inmediatos de éste. A la generación de los sucesores de s
se le llama expansión del estado o del nodo s.

Jorge Baier Aranda, PUC 175


Por el momento, no definiremos la forma en que un estado es elegido por la
función elija estado. Tampoco definiremos cómo la función mezclar mez-
cla los estados de las listas que recibe como argumentos.

Función buscar(lista estados_iniciales,


boolean función es_objetivo,
lista_estados función mezclar(lista_estados,
lista_estados)
lista_estados función sucesores(lista_estados));
{
L = estados_iniciales;

Aux = elija_estado(L);
while (not es_objetivo(Aux)) {
L = mezclar(eliminar(Aux,L),sucesores(Aux));
Aux = elija_estado(L);
}
retornar(Aux);
}

Jorge Baier Aranda, PUC 176


El comportamiento de esta función de búsqueda depende fuertemente de la
forma en que se definan las funciones mezclar y elija estado.

Para ejemplificar este comportamiento, utilizaremos el espacio de búsqueda


ilustrado por el árbol de la siguiente figura:.

2 3 4

5 6 7 8 9 10 11 12 13

Figura 10: Un árbol de búsqueda

Supongamos que deseamos explorar este árbol de búsqueda utilizando el


algoritmo genérico descrito anteriormente. Supongamos, además, que nues-
tra función objetivo retorna falso para todos los nodos, excepto para el nodo
con número 13.

Jorge Baier Aranda, PUC 177


A continuación estudiaremos las distintas formas de explorar este espacio
de búsqueda.

Jorge Baier Aranda, PUC 178


Búsqueda Genérica en Prolog
El algoritmo de búsqueda genérica será planteado en P ROLOG mediante la
definición del predicado buscar/5. La invocación:

?- buscar(Frontera, Resultado, Objetivo, Mezclar, Sucesores).

es un predicado que recibe el nombre de otros predicados como argumento.


Los argumentos corresponden a:
Frontera: debe corresponder a la lista de estados que están en la frontera
de búsqueda. En el primer llamado a buscar/5, esta lista deberá ser igual
a la lista de estados iniciales.

Resultado: al satisfacerse el predicado, esta variable unificará con algún


estado que satisfaga el objetivo de la búsqueda.

Objetivo: debe unificar con el nombre de un predicado unario, que se satis-


faga cuando su argumento sea un estado final de la búsqueda.

Jorge Baier Aranda, PUC 179


Mezclar: debe unificar con el nombre de un predicado ternario. Los primeros
dos argumentos de este predicado deben ser listas de estados. El tercer
argumento debe ser una lista con una mezcla de los estados que apare-
cen en las dos primeras. Por ejemplo, el predicado puede ser append/3.

Sucesores: debe unificar con el nombre de un predicado binario. Su primer


argumento debe ser un estado, el segundo argumento es una lista con los
estados que son sucesores inmediatos del primer estado.

Jorge Baier Aranda, PUC 180


Las reglas para buscar/5 son:

buscar([Resultado|_], Resultado, Objetivo, _, _):-


X=..[Objetivo, Resultado],
call(X).
buscar([Estado|Frontera_inicial], Resultado,
Objetivo, Mezclar, Sucesores):-
X=.. [Sucesores, Estado, Suc_inmed],
call(X),
Y=.. [Mezclar, Frontera_inicial,
Suc_inmed, Nueva_frontera],
call(Y),
buscar(Nueva_frontera, Resultado,
Objetivo, Mezclar, Sucesores).

En los ejemplos siguientes, haremos uso del árbol de la figura 10, para ello,
definiremos:

obj(X):- write(X), write(’ - ’), X=13.


suces(1,[2,3,4]).
suces(2,[5,6,7]).
suces(3,[8,9,10]).
suces(4,[11,12,13]).

Jorge Baier Aranda, PUC 181


suces(X,[]):- X>=5, X=<13.

Las definiciones anteriores permiten determinar qué nodo es un nodo objeti-


vo (con la función obj/1), y qué nodos son sucesores de un nodo dado (con
la función suces/2. En el predicado obj/1, se realizan escrituras con el fin
de que P ROLOG escriba la secuencia de nodos que explora.

Jorge Baier Aranda, PUC 182


Búsqueda en Profundidad

Para realizar una búsqueda en profundidad, se requiere que la función de


mezcla realice un append de la lista de nuevos sucesores del nodo que se
está considerando, con la frontera inicial. Para ello, definimos la siguiente
función:

mezcla_dfs(Frontera_inicial,
Suc_inmed, Frontera_final):-
append(Suc_inmed,
Frontera_inicial,
Frontera_final).

De este modo, el resultado que se obtiene es el siguiente:

?- buscar([1],Res,obj,mezcla_dfs,suces).
1 - 2 - 5 - 6 - 7 - 3 - 8 - 9 - 10 - 4 - 11 - 12 - 13 -
Res = 13 ?

No

Jorge Baier Aranda, PUC 183


Donde aquı́ se observa que los nodos se buscan en el orden DFS, como era
de esperarse.

Jorge Baier Aranda, PUC 184


Búsqueda en Amplitud
Como vimos anteriormente, la búsqueda en amplitud o BFS resulta si uno
realiza la mezcla en la forma inversa a la realizada en DFS. Ası́, podemos
definir:

mezcla_bfs(Frontera_inicial,
Suc_inmed, Frontera_final):-
append(Frontera_inicial,
Suc_inmed,
Frontera_final).

De este modo, el resultado que se obtiene es el siguiente:

?- buscar([1],Res,obj,mezcla_bfs,suces).
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 -
Res = 13 ;

No

Aquı́ se observa que los nodos se buscan en el orden BFS . Nótese, que lo
mismo se obtiene si uno escribe:

Jorge Baier Aranda, PUC 185


?- buscar([1],Res,obj,append,suces).
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 -
Res = 13 ;

No

Jorge Baier Aranda, PUC 186


Mejoras al Algoritmo de Búsqueda Genérico

B ÚSQUEDA G EN ÉRICA SIN R EPETICI ÓN DE E STADOS

Utilizaremos la siguiente terminologı́a:


Abiertos: Es una lista de estados que no han sido explorados.

Cerrados: Es una lista de estados que han sido explorados, pero que no
han sido exitosos (no satisfacen la función objetivo).

mezcla g: Es una función que recibe tres argumentos, la lista de estados


abiertos, la lista de estados cerrados y una lista de estados nuevos. Retor-
na como resultado una nueva lista con los estados abiertos y los estados
nuevos que no aparezcan la lista de cerrados.

El algoritmo buscar gen utiliza las listas de nodos (o estados) abiertos y


nodos cerrados.

Jorge Baier Aranda, PUC 187


Función buscar_gen(lista abiertos,
lista cerrados,
boolean función es_objetivo,
lista_estados función mezcla_g(lista_estados,
lista_estados)
lista_estados función sucesores(lista_estados));
{

Aux = car(abiertos);
while (not es_objetivo(Aux)) {
if not member(Aux,cerrados) {
cerrados = cons(Aux,cerrados);
abiertos = mezcla_g(cdr(abiertos),
cerrados,sucesores(Aux));
}
Aux = car(abiertos);
retornar(Aux);
}

Este nuevo algoritmo de búsqueda genérico elimina las deficiencias que ocu-
rren cuando existen diversas formas de llegar a un mismo estado.

Jorge Baier Aranda, PUC 188


Versión Prolog
El nuevo algoritmo de búsqueda genérica será planteado en P ROLOG me-
diante la definición del predicado buscar g/6. La invocación:

?- buscar_g(Abiertos, Cerrados, Resultado,


Objetivo, Mezclar_g, Sucesores).

La primera regla para buscar g/6 es:

buscar_g([R|Abiertos], Cerrados, Resultado,


Objetivo, Mezclar_g, Sucesores):-
member(R,Cerrados),
buscar_g(Abiertos, Cerrados, Resultado,
Objetivo, Mezclar_g, Sucesores).

Esta regla chequea si el primer nodo abierto ya ha sido explorado, en cuyo


caso, se ignora.

Jorge Baier Aranda, PUC 189


La segunda regla es:

buscar_g([Resultado|_], _, Resultado, Objetivo, _, _):-


X=..[Objetivo, Resultado],
call(X).

Es decir, si el primer elemento de la lista de estados abiertos es una solución,


entonces la búsqueda termina. En otro caso, debemos reemplazar el primer
elemento de esta lista por sus sucesores. Para esto, definimos la regla:

buscar_g([Estado|Otros_abiertos], Cerrados, Resultado,


Objetivo, Mezclar_g, Sucesores):-
write([Estado|Otros_abiertos]),
write(Cerrados),nl,
X=.. [Sucesores, Estado, Nuevos_sucesores],
call(X),
Nuevos_Cerrados = [Estado | Cerrados],
Y=.. [Mezclar_g, Otros_abiertos, Nuevos_Cerrados,
Nuevos_sucesores, Nuevos_Abiertos],
call(Y),
buscar_g(Nuevos_Abiertos, Nuevos_Cerrados,
Resultado, Objetivo,
Mezclar_g, Sucesores).

Jorge Baier Aranda, PUC 190


Búsqueda en Profundidad y en Amplitud
Para poder utilizar una búsqueda en profundidad, definiremos el predicado
mezcla dfs/4 como sigue:

mezcla_dfs(Abiertos, Cerrados,
Nuevos_estados, Nuevos_abiertos):-
menos(Nuevos_estados, Cerrados, N2),
append(N2,Abiertos,N3),
list_to_set(N3,Nuevos_abiertos).

Donde el predicado list to set/2 está predefinido en SWI-Prolog.


menos queda definido como:

menos([],_,[]).
menos(N,[],N).
menos([L|Ls],N,[L|Lr]):- \+member(L,N),!,
menos(Ls,N,Lr).
menos([_|Ls],N,Lr):- menos(Ls,N,Lr).

Jorge Baier Aranda, PUC 191


Por otra parte, para poder utilizar una búsqueda en amplitud, definiremos el
predicado mezcla bfs/4 como sigue:

mezcla_bfs(Abiertos, Cerrados,
Nuevos_estados, Nuevos_abiertos):-
menos(Nuevos_estados, Cerrados, N2),
append(Abiertos,N2,N3),
list_to_set(N3,Nuevos_abiertos).

Jorge Baier Aranda, PUC 192


¿Qué tanto mejoramos?

Veamos nuevamente el problema de los misioneros y los canı́bales.

En este esquema, es necesario definir una función objetivo y una función


sucesores, las que se definen de la siguiente manera:

final(estado(0,0,3,3,der)).
sucesores(E,L) :-
findall(Ep,sucesor(E,Ep),L).

Al hacer la siguiente consulta P ROLOG:

time(buscar([estado(3,3,0,0,izq)],X,final,
mezcla_bfs,sucesores)).

Se obtiene la siguiente respuesta:

% 388,193,575 inferences in 2240.13 seconds (173291 Lips)


ERROR: Out of global stack

Jorge Baier Aranda, PUC 193


La ineficiencia de nuestra respuesta se debe al gran tamaño del espacio de
búsqueda, pero principalmente a la ineficiencia del predicado append.

Si utilizamos nuestro antiguo en espaciobusqueda/1, el cual recorre el árbol


al estilo BFS, obtenemos lo siguiente...

?- time(en_espaciobusqueda(estado(0,0,3,3,der))).
% 5,718,368 inferences in 8.05 seconds (710356 Lips)

Yes

Utilizando una implementación eficiente de listas, serı́a posible que buscar


tuviera una eficiencia similar a en espaciobusqueda. Sin embargo, si usa-
mos búsqueda sin repetición de estados:

?- time(buscar_g([estado(3,3,0,0,izq)],[],X,final,
mezcla_g_bfs,sucesores)).
% 2,846 inferences in 0.01 seconds (284600 Lips)

X = estado(0, 0, 3, 3, der)

Consideremos ahora una versión modificada del problema de los misioneros

Jorge Baier Aranda, PUC 194


y canı́bales. En este problema hay 5 canı́bales y 5 misioneros. El objetivo es
el mismo: los canı́bales y misioneros deben cruzar el rı́o, pero ahora la balsa
tiene capacidad para 3 pasajeros.
En este caso, los resultados son aún más abismantes:

?- time(en_espaciobusqueda(estado(0,0,5,5,der))).
% 65,545,149 inferences in 76.82 seconds (853230 Lips)

Yes
?- time(buscar_g([estado(5,5,0,0,izq)],[],X,final,
mezcla_g_bfs,sucesores)).
% 8,553 inferences in 0.01 seconds (855300 Lips)

X = estado(0, 0, 5, 5, der)

Si usamos una representación eficiente para listas, podrı́amos obtener tiem-


pos aún mejores.

Jorge Baier Aranda, PUC 195