Tema 8: Resolución de problemas de espacios de estados
Índice
- 1. Ejemplo de problema mediante espacios de estados: el 8 puzzle
- 2. Búsqueda en profundidad sin ciclos
- 3. Búsqueda en profundidad con ciclos
- 3.1. El problema del grafo
- 3.2. No solución del problema del grafo mediante búsqueda en profundidad sin ciclos
- 3.3. Procedimiento de búsqueda en profundidad con ciclos
- 3.4. Solución del problema del grafo mediante búsqueda en profundidad con ciclos
- 3.5. El problema de las jarras
- 3.6. Solución del problema de las jarras por búsqueda en profundidad con ciclos
- 4. Búsqueda en anchura
- 5. Búsqueda óptima (de menor coste)
- 5.1. Problema del viaje
- 5.2. Solución del problema del viaje mediante búsqueda en profundidad y en anchura
- 5.3. Primer procedimiento de búsqueda óptima
- 5.4. Solución del problema del viaje mediante búsqueda óptima
- 5.5. Segundo procedimiento de búsqueda óptima
- 5.6. Comparación de procedimientos de búsqueda óptima
- 6. Búsqueda en escalada (con heurística).
- 7. Búsqueda por primero el mejor
- 8. Algoritmo A*
- 9. Ejercicios
- 10. Bibliografía
1. Ejemplo de problema mediante espacios de estados: el 8 puzzle
- Para el 8 puzzle se usa un cajón cuadrado en el que hay situados 8
bloques cuadrados. El cuadrado restante está sin rellenar. Cada
bloque tiene un número. Un bloque adyacente al hueco puede deslizarse
hacia él. El juego consiste en transformar la posición inicial en la
posición final mediante el deslizamiento de los bloques. En
particular, consideramos el estado inicial y final siguientes:
- Ejemplos de movimientos
- Solución del 8–puzzle:
- Representación:
- Estado inicial:
[[2,8,3],[1,6,4],[7,h,5]]
- Estado final:
[[1,2,3],[8,h,4],[7,6,5]]
- Operadores:
- Mover el hueco a la izquierda
- Mover el hueco arriba
- Mover el hueco a la derecha
- Mover el hueco abajo
- Estado inicial:
- Número de estados = 9! = 362.880.
- Nota: El estudio del problema continúa en la última sección.
2. Búsqueda en profundidad sin ciclos
2.1. El problema del árbol
2.1.1. Descripción del problema del árbol
- El problema del árbol consiste en encontrar caminos (desde
a
hastaf
oj
) en el siguiente grafo
2.1.2. Representación del problema del árbol
- El problema se representa mediante las relaciones
estado_inicial/1
,estado_final/1
ysucesor/2
definidas a continuación. estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial(a).
estado_final(?E)
se verifica siE
es un estado final.estado_final(f). estado_final(j).
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
.sucesor(a,b). sucesor(a,c). sucesor(b,d). sucesor(b,e). sucesor(c,f). sucesor(c,g). sucesor(d,h). sucesor(e,i). sucesor(e,j). sucesor(f,k).
- El código del problema del árbol se encuentra en p_arbol.pl.
2.2. Procedimiento de búsqueda en profundidad sin ciclos
profundidad_sin_ciclos(?S)
se verifica siS
es una solución del problema mediante búsqueda en profundidad sin ciclos. Por ejemplo, Su definición esprofundidad_sin_ciclos(S) :- estado_inicial(E), profundidad_sin_ciclos(E,S).
profundidad_sin_ciclos(+E,?S)
se verifica siS
es una solución por búsqueda en profundidad sin ciclos a partir deE
; es decir, según el siguiente procedimiento:- 1.
S = [E]
es una solución siE
es un estado final. - 2.
S = [E|S1]
es una solución si existe unE
tal que- 2.1.
E1
es un sucesor deE
- 2.2.
S1
es una solución por búsqueda en profundidad sin ciclos a partir deE1
.
- 2.1.
profundidad_sin_ciclos(E,[E]) :- estado_final(E). % 1 profundidad_sin_ciclos(E,[E|S1]) :- % 2 sucesor(E,E1), % 2.1 profundidad_sin_ciclos(E1,S1). % 2.2
- 1.
- El código de la búsqueda en profundidad sin ciclos se encuentra en
2.3. Solución del problema del árbol mediante búsqueda en profundidad sin ciclos
La solución es
?- [p_arbol, b_profundidad_sin_ciclos]. true ?- profundidad_sin_ciclos(S). S = [a, b, e, j] ?- trace(estado_final,+call). estado_final/1: [call] true. ?- profundidad_sin_ciclos(S). T Call: estado_final(a) T Call: estado_final(b) T Call: estado_final(d) T Call: estado_final(h) T Call: estado_final(e) T Call: estado_final(i) T Call: estado_final(j) S = [a, b, e, j] ?- trace(estado_final,-call). % estado_final/1: Not tracing true.
2.4. El problema de las 4 reinas
2.4.1. Descripción del problema de las 4 reinas
- El problema de las 4 reinas consiste en colocar 4 reinas en un tablero rectangular de dimensiones 4 por 4 de forma que no se encuentren más de una en la misma línea: horizontal, vertical o diagonal.
- Los estados son listas de números que representan las ordenadas de las reinas colocadas. Por ejemplo, [3,1] representa que se ha colocado las reinas (1,1) y (2,3).
2.4.2. Representación del problema de las 4 reinas
- El problema se representa mediante las relaciones
estado_inicial/1
,estado_final/1
ysucesor/2
definidas a continuación. estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial([]).
estado_final(?E)
se verifica siE
es un estado final.estado_final(E) :- length(E,4).
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
.sucesor(E,[Y|E]) :- member(Y,[1,2,3,4]), not(member(Y,E)), no_ataca(Y,E).
no_ataca(Y,E)
se verifica siE=[Yn,...,Y1]
, entonces la reina colocada en(n+1,Y)
no ataca a las colocadas en(1,Y1)
, …,(n,Yn)
.no_ataca(Y,E) :- no_ataca(Y,E,1). no_ataca(_,[],_). no_ataca(Y,[Y1|L],D) :- Y1-Y =\= D, Y-Y1 =\= D, D1 is D+1, no_ataca(Y,L,D1).
- El código del problema de las 4 reinas se encuentra en p_4_reinas.pl.
2.5. Solución del problema de las 4 reinas por búsqueda en profundidad sin ciclos.
La solución es
?- [p_4_reinas, b_profundidad_sin_ciclos]. true. ?- profundidad_sin_ciclos(S). S = [[], [2], [4, 2], [1, 4, 2], [3, 1, 4, 2]]
3. Búsqueda en profundidad con ciclos
3.1. El problema del grafo
3.1.1. Descripción del problema del grafo
- El problema del grafo consiste en encontrar caminos (desde
a
hastaf
oj
) en el siguiente grafo
3.1.2. Representación del problema del grafo
- El problema se representa mediante las relaciones
estado_inicial/1
,estado_final/1
ysucesor/2
definidas a continuación. estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial(a).
estado_final(?E)
se verifica siE
es un estado final.estado_final(f). estado_final(j).
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
.sucesor(a,b). sucesor(a,c). sucesor(b,d). sucesor(b,e). sucesor(c,f). sucesor(c,g). sucesor(d,h). sucesor(e,i). sucesor(e,j). sucesor(f,k). sucesor(h,d).
- El código del problema del grafo se encuentra en p_grafo.pl.
3.2. No solución del problema del grafo mediante búsqueda en profundidad sin ciclos
El problema del grafo no se puede resolver mediante búsqueda en profundidad sin ciclos.
?- [p_grafo, b_profundidad_sin_ciclos]. true. ?- trace(estado_final,+call). true. ?- profundidad_sin_ciclos(S). T Call: estado_final(a) T Call: estado_final(b) T Call: estado_final(d) T Call: estado_final(h) T Call: estado_final(d) T Call: estado_final(h) ...
3.3. Procedimiento de búsqueda en profundidad con ciclos
- Un nodo es una lista de estados
[E(n),...,E(1)]
de forma queE(1)
es el estado inicial yE(i+1)
es un sucesor deE(i)
. profundidad_con_ciclos(?S)
se verifica siS
es una solución del problema mediante búsqueda en profundidad con ciclos.profundidad_con_ciclos(S) :- estado_inicial(E), profundidad_con_ciclos([E],S).
profundidad_con_ciclos(+N,?S)
se verifica siS
es una solución del problema a partir del nodoN
(es decirS = [E(1),...,E(m)]
dondeN = [E(j),E(j-1),...,E(1)]
,E(n)
es un estado final yE(i+1)
es un sucesor deE(i)
), encontrada por búsqueda en profundidad; es decir, mediante el siguiente procedimiento:- 1. Si el primer elemento de
N
es un estado final, entoncesS
es la inversa deN
. - 2. Si
N = [E|C]
yE1
un sucesor deE
que no ha sido visitado (es decir, que no pertenece aC
) y tal que existe una solución,S
, a partir de[E1,E|C]
.
profundidad_con_ciclos([E|C],S) :- estado_final(E), reverse([E|C],S). profundidad_con_ciclos([E|C],S) :- sucesor(E,E1), not(memberchk(E1,C)), profundidad_con_ciclos([E1,E|C],S).
- 1. Si el primer elemento de
- El código de la búsqueda en profundidad con ciclos se encuentra en b_profundidad_con_ciclos.pl.
3.4. Solución del problema del grafo mediante búsqueda en profundidad con ciclos
Solución
?- [p_grafo, b_profundidad_con_ciclos]. true. ?- profundidad_con_ciclos(S). S = [a, b, e, j] ; S = [a, c, f] ; false. ?- trace(estado_final,+call). % estado_final/1: [call] true. ?- profundidad_con_ciclos(S). T Call: estado_final(a) T Call: estado_final(b) T Call: estado_final(d) T Call: estado_final(h) T Call: estado_final(e) T Call: estado_final(i) T Call: estado_final(j) S = [a, b, e, j] ; T Call: estado_final(c) T Call: estado_final(f) S = [a, c, f] ; T Call: estado_final(k) T Call: estado_final(g) false.
3.5. El problema de las jarras
3.5.1. Descripción del problema de las jarras
En el problema de las jarras
- Se tienen dos jarras, una de 4 litros de capacidad y otra de 3.
- Ninguna de ellas tiene marcas de medición.
- Se tiene una bomba que permite llenar las jarras de agua.
- El problema consiste en averiguar cómo se puede lograr tener exactamente 2 litros de agua en la jarra de 4 litros de capacidad.
3.5.2. Representación del problema de las jarras
- El estado inicial es
0-0
. estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial(0-0).
- Los estados finales son de la forma
2-y
estado_final(?E)
se verifica siE
es un estado final.estado_final(2-_).
- Los operadores:
- Llenar la jarra de 4 litros con la bomba.
- Llenar la jarra de 3 litros con la bomba.
- Vaciar la jarra de 4 litros en el suelo.
- Vaciar la jarra de 3 litros en el suelo.
- Llenar la jarra de 4 litros con la jarra de 3 litros.
- Llenar la jarra de 3 litros con la jarra de 4 litros.
- Vaciar la jarra de 3 litros en la jarra de 4 litros.
- Vaciar la jarra de 4 litros en la jarra de 3 litros.
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
.sucesor(X-Y,4-Y) :- X < 4. sucesor(X-Y,X-3) :- Y < 3. sucesor(X-Y,0-Y) :- X > 0. sucesor(X-Y,X-0) :- Y > 0. sucesor(X1-Y1,4-Y2) :- X1 < 4, T is X1+Y1, T >= 4, Y2 is Y1-(4-X1). sucesor(X1-Y1,X2-3) :- Y1 < 3, T is X1+Y1, T >= 3, X2 is X1-(3-Y1). sucesor(X1-Y1,X2-0) :- Y1 > 0, X2 is X1+Y1, X2 < 4. sucesor(X1-Y1,0-Y2) :- X1 > 0, Y2 is X1+Y1, Y2 < 3.
- El código del problema de las jarras se encuentra en p_jarras.pl.
3.6. Solución del problema de las jarras por búsqueda en profundidad con ciclos
Solución
?- [p_jarras, b_profundidad_con_ciclos]. true. ?- profundidad_con_ciclos(S). S = [0-0,4-0,4-3,0-3,3-0,3-3,4-2,0-2,2-0] ; S = [0-0,4-0,4-3,0-3,3-0,3-3,4-2,0-2,2-0,2-3] ; S = [0-0,4-0,1-3,4-3,0-3,3-0,3-3,4-2,0-2,2-0]
4. Búsqueda en anchura
4.1. El problema del paseo
4.1.1. Descripción del problema del paseo
- Una persona puede moverse en línea recta dando cada vez un paso hacia
la derecha o hacia la izquierda. Podemos representarlo mediante su
posición
X
. El valor inicial deX
es 0. El problema consiste en llegar a la posición -3.
4.1.2. Representación del problema del paseo
estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial(0).
estado_final(?E)
se verifica siE
es un estado final.estado_final(-3).
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
.sucesor(E1,E2) :- E2 is E1+1. sucesor(E1,E2) :- E2 is E1-1.
- El código del problema del paseo se encuentra en p_paseo.pl.
4.2. No solución del problema del paseo mediante búsqueda en profundidad
El problema del paseo no se puede solucionar mediante búsqueda en profundidad con ciclos.
?- [p_paseo, b_profundidad_con_ciclos]. true. ?- trace(estado_final,+call). true. ?- profundidad_con_ciclos(S). T Call: estado_final(0) T Call: estado_final(1) T Call: estado_final(2) T Call: estado_final(3) T Call: estado_final(4) ...
4.3. Procedimiento de búsqueda en anchura
- Un nodo es una lista de estados
[E(n),...,E(1)]
de forma queE(1)
es el estado inicial yE(i+1)
es un sucesor deE(i)
. - Abiertos es la lista de nodos pendientes de analizar.
anchura(?S)
se verifica siS
es una solución del problema mediante búsqueda en anchura.anchura(S) :- estado_inicial(E), anchura([[E]],S).
anchura(+Abiertos,?S)
se verifica siS
es la solucion encontrada por búsqueda en anchura a partir de la lista deAbiertos
; es decir, mediante el siguiente procedimiento:- 1. Si
- 1.1. el primer elemento de
Abiertos
es[E|C]
y - 1.2.
E
es un estado final, entonces - 1.3.
S
es la inversa de[E|C]
.
- 1.1. el primer elemento de
- 2. Si
- 2.1.
Abiertos
es la lista[[E|C]|R]
, - 2.2. la lista de los sucesores de
E
que no están enC
ni enAbiertos
esSucesores
y - 2.3. los nuevos abiertos,
NAbiertos
, es la lista obtenida añadiendo dichosSucesores4 a continuación de ~R
, entonces - 2.4.
S
es la solución obtenida por búsqueda en anchura con los nuevos abiertos.
- 2.1.
anchura([[E|C]|_],S) :- % 1.1 estado_final(E), % 1.2 reverse([E|C],S). % 1.3 anchura([N|R],S) :- expande([N|R],Sucesores), % 2.2 append(R,Sucesores,NAbiertos), % 2.3 anchura(NAbiertos,S). % 2.4
- 1. Si
expande(+Abiertos,?Sucesores)
se verifica siSucesores
es la lista de los sucesores del primer elemento deAbiertos
que no pertenecen al camino que lleva a dicho elemento ni aAbiertos
.expande([[E|C]|R],Sucesores) :- findall([E1,E|C], (sucesor(E,E1), not(memberchk(E1,C)), not(memberchk([E1|_],[[E|C]|R]))), Sucesores).
- El código del procedimiento de búsqueda en anchura se encuentra en b_anchura.pl.
4.4. Solución del problema del paseo mediante búsqueda en anchura.
La solución es
?- [p_paseo, b_anchura]. true. ?- true. ?- [p_paseo, b_anchura]. true. ?- anchura(S). S = [0,-1,-2,-3] ?- trace(estado_final,+call). true. ?- anchura(S). T Call: estado_final(0) T Call: estado_final(1) T Call: estado_final(-1) T Call: estado_final(2) T Call: estado_final(-2) T Call: estado_final(3) T Call: estado_final(-3) S = [0,-1,-2,-3]
5. Búsqueda óptima (de menor coste)
5.1. Problema del viaje
5.1.1. Descripción del problema del viaje:
- Nos encontramos en una capital andaluza (p.e. Sevilla) y deseamos
ir a otra capital andaluza (p.e. Almería). Los autobuses sólo van de cada
capital a sus vecinas, como se muestra en el siguiente mapa
5.1.2. Representación del problema del viaje
estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial(sevilla).
estado_final(?E)
se verifica siE
es un estado final.estado_final(almería).
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
.sucesor(E1,E2) :- ( conectado(E1,E2) ; conectado(E2,E1) ).
conectado(?E1,?E2)
se verifica siE1
yE2
están conectados.conectado(huelva,sevilla). conectado(huelva,cádiz). conectado(sevilla,córdoba). conectado(sevilla,málaga). conectado(sevilla,cádiz). conectado(córdoba,jaén). conectado(córdoba,granada). conectado(córdoba,málaga). conectado(cádiz,málaga). conectado(málaga,granada). conectado(jaén,granada). conectado(granada,almería).
coste(+E1,+E2,?C)
se verifica siC
es la distancia entreE1
yE2
.coste(E1,E2,C) :- coordenadas(E1,P1), coordenadas(E2,P2), distancia(P1,P2,C).
coordenadas(E,C)
se verifica siC
son las coordenadas deE
según la siguiente tabla:almería 409.5 93 cádiz 63 57 córdoba 198 207 granada 309 127.5 huelva 3 139.5 jaén 295.5 192 málaga 232.5 75 sevilla 90 153 coordenadas(almería, [409.5, 93 ]). coordenadas(cádiz, [ 63 , 57 ]). coordenadas(córdoba, [198 , 207 ]). coordenadas(granada, [309 , 127.5]). coordenadas(huelva, [ 3 , 139.5]). coordenadas(jaén, [295.5, 192 ]). coordenadas(málaga, [232.5, 75 ]). coordenadas(sevilla, [ 90 , 153 ]).
distancia(+E1,+E2,?D)
se verifica siD
es la distancia deE1
aE2
.distancia([X1,Y1],[X2,Y2],D) :- D is sqrt((X1-X2)**2+(Y1-Y2)**2).
- El código del problema del viaje se encuentra en p_viaje.pl.
5.2. Solución del problema del viaje mediante búsqueda en profundidad y en anchura
La solución es
?- [p_viaje, b_profundidad_con_ciclos, b_anchura]. true. ?- profundidad_con_ciclos(S). S = [sevilla,córdoba,jaén,granada,almería] ?- anchura(S). S = [sevilla,córdoba,granada,almería]
5.3. Primer procedimiento de búsqueda óptima
óptima_1(?S)
se verifica siS
es una solución óptima del problema; es decir,S
es una solución del problema y no hay otra solución de menor coste.óptima_1(S) :- profundidad_con_ciclos(S), coste_camino(S,C), not((profundidad_con_ciclos(S1), coste_camino(S1,C1), C1 < C)).
coste_camino(+L,?C)
se verifica siC
es el coste del caminoL
.coste_camino([E2,E1],C) :- coste(E2,E1,C). coste_camino([E2,E1|R],C) :- coste(E2,E1,C1), coste_camino([E1|R],C2), C is C1+C2.
- El código del primer procedimiento de búsqueda óptima se encuentra en b_optima_1.pl.
5.4. Solución del problema del viaje mediante búsqueda óptima
La solución (y la comparaciones con las anteriores) es
?- [p_viaje, b_profundidad_con_ciclos, b_anchura, b_optima_1]. true. ?- óptima_1(S), b_optima_1:coste_camino(S,C). S = [sevilla,málaga,granada,almería], C = 361.48952882676883 ?- profundidad_con_ciclos(S), b_optima_1:coste_camino(S,C). S = [sevilla,córdoba,jaén,granada,almería], C = 391.54918146974836 ?- anchura(S), b_optima_1:coste_camino(S,C). S = [sevilla,córdoba,granada,almería], C = 363.537398328421
5.5. Segundo procedimiento de búsqueda óptima
- Un nodo es un término de la forma
C-[E(n),...,E(1)]
tal queE(1)
es el estado inicial,E(i+1)
es un sucesor deE(i)
yC
es el coste de dicho camino. óptima(?S)
se verifica siS
es una solución del problema mediante búsqueda óptima; es decir,S
es la solución obtenida por búsqueda óptima a partir de[0-[E]]
, dondeE
el estado inicial.óptima(S) :- estado_inicial(E), óptima([0-[E]],S).
óptima(+Abiertos,?S)
se verifica siS
es una solución del problema a partir de la lista de nodosAbiertos
encontrada por búsqueda óptima. El procedimiento es- 1. Si
- 1.1
C-[E|R]
es el primer elemento deAbiertos
y - 1.2
E
es un estado final, entonces - 1.3
S
es la inversa de[E|R]
y el coste deS
esC
.
- 1.1
- 2. Si
N
es el mejor nodo deAbiertos
yRAbiertos
son los restantes;- 2.1
Sucesores
son los nodos sucesores deN
(es decir, siN = C-[E|R]
, entoncesSucesores
son los nodos de la formaC1-[E1,E|R]
dondeE1
es un sucesor deE
que no pertenece a[E|R]
yC1
es la suma deC
y el coste deE
aE1
), - 2.2
Abiertos1
es la lista obtenida añadiendoSucesores
a continuación deRAbiertos
y - 2.3
Abiertos2
es la lista obtenida ordenandoAbiertos1
; entonces - 2.4
S
es la solución encontrada por búsqueda óptima a partir deAbiertos2
.
- 2.1
óptima([_C-[E|R]|_RA],S) :- estado_final(E), % 1.2 reverse([E|R],S). % 1.3 óptima([N|RAbiertos],S) :- expande(N,Sucesores), % 2.2 append(RAbiertos,Sucesores,Abiertos1), % 2.3 sort(Abiertos1,Abiertos2), % 2.4 óptima(Abiertos2,S). % 2.5
- 1. Si
expande(+N,?Sucesores)
se verifica siSucesores
es la lista de sucesores del nodoN
(es decir, siN=C-[E|R]
, entoncesSucesores
son los nodos de la formaC1-[E1,E|R]
dondeE1
es un sucesor deE
que no pertenece a[E|R]
yC1
es la suma deC
y el coste deE
aE1
).expande(C-[E|R],Sucesores) :- findall(C1-[E1,E|R], (sucesor(E,E1), not(member(E1,[E|R])), coste(E,E1,C2), C1 is C+C2), Sucesores).
- El código del segundo procedimiento de búsqueda óptima se encuentra en b_optima_2.pl.
5.6. Comparación de procedimientos de búsqueda óptima
Las comparaciones son
?- [p_viaje, b_optima_1]. true. ?- time(óptima_1(S)). % 2,458 inferences, 0.000 CPU in 0.000 seconds (100% CPU, 7720577 Lips) S = [sevilla,málaga,granada,almería] ?- [p_viaje, b_optima_2]. true. ?- time(óptima(S)). % 1,487 inferences, 0.001 CPU in 0.001 seconds (100% CPU, 1310784 Lips) S = [sevilla,málaga,granada,almería]
6. Búsqueda en escalada (con heurística).
- La heurística de un estado es una estimación del mínimo coste de las soluciones a partir de dicho estado.
- Un nodo es un término de la forma
H-[E(n),...,E(1)]
donde [E_n,…,E_1] es una lista de estados de forma que E_1 es el estado inicial y E_(i+1) es un sucesor de E_i y H es la heurística de E_n. escalada(?S)
se verifica siS
es una solución del problema mediante búsqueda en escalada. El procedimiento es:- Sea
E
el estado inicial yH
su heurística. La soluciónS
es la solución obtenida por búsqueda en escalada a partir deH-[E]
.
escalada(Sol) :- estado_inicial(E), heuristica(E,H), escalada(H-[E],Sol).
- Sea
escalada(+N,?S)
se verifica siS
es una solución del problema a partir del nodoN
encontrada por búsqueda en escalada. El procedimiento es- 1. Si
N = H-[E|R]
y- 1.1
E
es un estado final, entonces - 1.2
S
es la inversa de[E|R]
.
- 1.1
- 2. Si
N = H-[E|C]
y- 2.1
E1
es un sucesor deE
con heurísticaH1 < H
yE
no tiene sucesores con heurística menor queH1
, entonces - 2.2
S
es la solución por escalada a partir deH1-[E1,E|R]
.
- 2.1
escalada(_H-[E|R],S) :- estado_final(E), % 1.1 reverse([E|R],S). % 1.2 escalada(H-[E|R],S) :- mejor_sucesor(E,H,E1,H1), % 2.1 escalada(H1-[E1,E|R],S). % 2.2 mejor_sucesor(E,H,E1,H1) :- sucesor(E,E1), heuristica(E1,H1), H1 < H, not((sucesor(E,E2), heuristica(E2,H2), H2 < H1)).
- 1. Si
6.1. Heurística del problema del viaje
heuristica(E,H)
se verifica siH4 es la heurística de ~E
(es decir, la distancia deE
al estado final).heuristica(E,H) :- estado_final(E1), coste(E,E1,H).
6.2. Solución del problema del viaje mediante búsqueda en Escalada
La solución es
?- [p_viaje, b_escalada]. true. ?- escalada(S). S = [sevilla,málaga,granada,almería] ?- trace(escalada,+call). true. ?- escalada(S). T Call: b_escalada:escalada(_8632) T Call: b_escalada:escalada(325.08498888752155-[sevilla],_8632) T Call: b_escalada:escalada(177.91290003819284-[málaga,sevilla],_8632) T Call: b_escalada:escalada(106.25676449054902-[granada,málaga,sevilla],_8632) T Call: b_escalada:escalada(0.0-[almería,granada,málaga,sevilla],_8632) S = [sevilla,málaga,granada,almería]
7. Búsqueda por primero el mejor
7.1. Problema de las fichas
7.1.1. Descripción del problema de las fichas
Se considera un tablero con 7 cuadrados consecutivos. Inicialmente, en cada uno de los 3 primeros cuadrados hay una ficha blanca y en cada uno de los 3 últimos cuadrados hay una ficha verde. El objetivo consiste en tener las fichas verdes al principiocipio y las blancas al final.
La situación inicial es
+---+---+---+---+---+---+---+ | B | B | B | | V | V | V | +---+---+---+---+---+---+---+
y la final es
+---+---+---+---+---+---+---+ | V | V | V | | B | B | B | +---+---+---+---+---+---+---+
Los movimientos permitidos consisten en desplazar una ficha al hueco saltando, como máximo, sobre otras dos.
7.1.2. Representación del problema de las Fichas
- Un estado es una lista de siete elementos representando cada una de
las fichas (
B
= blanca,V
= verde) y el hueco (H
). estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial([b,b,b,h,v,v,v]).
estado_final(?E)
se verifica siE
es un estado final.estado_final([v,v,v,h,b,b,b]).
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
. Por ejemplo,?- sucesor([v, v, v, h, b, b, b],E). E = [v, v, h, v, b, b, b] ; E = [v, h, v, v, b, b, b] ; E = [h, v, v, v, b, b, b] ; E = [v, v, v, b, h, b, b] ; E = [v, v, v, b, b, h, b] ; E = [v, v, v, b, b, b, h] ; No
Su definición es
sucesor(E1,E2) :- descompone(I,[F,h],D,E1), compone(I,[h,F],D,E2). sucesor(E1,E2) :- descompone(I,[F,G,h],D,E1), compone(I,[h,G,F],D,E2). sucesor(E1,E2) :- descompone(I,[F,G,H,h],D,E1), compone(I,[h,G,H,F],D,E2). sucesor(E1,E2) :- descompone(I,[h,F],D,E1), compone(I,[F,h],D,E2). sucesor(E1,E2) :- descompone(I,[h,G,F],D,E1), compone(I,[F,G,h],D,E2). sucesor(E1,E2) :- descompone(I,[h,G,H,F],D,E1), compone(I,[F,G,H,h],D,E2).
descompone(?L1,?L2,?L3,+L4)
se verifica siL4
es la concatenación de las listasL1
,L2
yL3
.descompone(L1,L2,L3,L4) :- append(L12,L3,L4), append(L1,L2,L12).
compone(+L1,+L2,+L3,?L4)
se verifica siL4
es la concatenación de las listasL1
,L2
yL3
.compone(L1,L2,L3,L4) :- append(L1,L2,L12), append(L12,L3,L4).
Se considera la heurística que para cada estado vale la suma de piezas blancas situadas a la izquierda de cada una de las piezas verdes. Por ejemplo, para el estado
+---+---+---+---+---+---+---+ | B | V | B | | V | V | B | +---+---+---+---+---+---+---+
su valor es 1+2+2 = 5.
heuristica(E,H)
se verifica siH
es la heurística del estadoE
.heuristica([v|R],H) :- heuristica(R,H). heuristica([h|R],H) :- heuristica(R,H). heuristica([b|R],H) :- heuristica(R,H1), verdes(R,N), H is N+H1. heuristica([],0). verdes([v|R],N) :- verdes(R,N1), N is N1+1. verdes([h|R],N) :- verdes(R,N). verdes([b|R],N) :- verdes(R,N). verdes([],0).
- El código del problema de las fichas se encuentra en p_fichas.pl.
7.2. Procedimiento de búsqueda por primero el mejor
- Un nodo es un término
H-[E(n),...,E(1)]
de forma queE(1)
es el estado inicial,E(i+1)
es un sucesor deE(i)
yH
es la heurística deE(n)
. - Abiertos es una lista de nodos (los nodos pendientes de analizar).
- Cerrados es una lista de estados (los estados analizados).
primero_el_mejor(?S)
se verifica siS
es una solución del problema mediante búsqueda por primero el mejor. El procedimiento es- Sea
E
el estado inicial y H
la heurística deE
.- La solución
S
es la obtenida por búsqueda por primero el mejor con[H-[E]]
como la lista de abiertos y[]
como la lista de cerrados.
primero_el_mejor(S) :- estado_inicial(E), % 1 heuristica(E,H), % 2 primero_el_mejor([H-[E]],[ ],S). % 3
- Sea
primero_el_mejor(+Abiertos,+Cerrados,?Solucion)
se verifica siSolucion
es la solución encontrada por búsqueda por primero el mejor a partir de las listasAbiertos
yCerrados
. El procedimiento es- 1.1. Si el primer elemento de
Abiertos
es_-[E|C]
y - 1.2.
E
es un estado final, entonces - 1.3.
S
es la inversa de[E|C]
. - 2.1. Si
Abiertos
es la lista[_-[E|_]|R]
, - 2.2. la lista de los sucesores de
E
que no están ni enAbiertos
ni enCerrados
esSucesores
, - 2.3. los nuevos abiertos,
NAbiertos1
, es la lista obtenida añadiendo dichosSucesores
a continuación deR
, - 2.4. la lista ordenada de los nuevos abiertos es
NAbiertos
y - 2.5. los nuevos cerrados,
NCerrados
, es la lista obtenida añadiendoE
aCerrados
, entonces - 2.6.
S
es la solución obtenida por búsqueda primero el mejor con los nuevos abiertos y los nuevos cerrados.
primero_el_mejor(Abiertos,_,S) :- Abiertos = [_-[E|C]|_], % 1.1 estado_final(E), % 1.2 reverse([E|C],S). % 1.3 primero_el_mejor(Abiertos,Cerrados,S) :- Abiertos = [_-[E|_]|R], % 2.1 expande(Abiertos,Cerrados,Sucesores), % 2.2 append(R,Sucesores,NAbiertos1), % 2.3 sort(NAbiertos1,NAbiertos), % 2.4 NCerrados = [E|Cerrados], % 2.5 primero_el_mejor(NAbiertos,NCerrados,S). % 2.6
- 1.1. Si el primer elemento de
expande(+Abiertos,+Cerrados,?Sucesores)
se verifica si Sucesores es la lista de los sucesores del primer elemento deAbiertos
que no pertenecen si aAbiertos
ni aCerrados
.expande(Abiertos,Cerrados,Sucesores) :- Abiertos = [_-[E|C]|_], findall(H1-[E1,E|C], (sucesor(E,E1), not(memberchk(_-[E1|_],Abiertos)), not(memberchk(E1,Cerrados)), heuristica(E1,H1)), Sucesores).
- El código de la búsqueda por primero el mejor se encuentra em b_primero_mejor.pl.
7.3. Solución del problema de las fichas mediante búsqueda por primero el mejor
La solución es
?- [p_fichas, b_primero_mejor]. true. ?- primero_el_mejor(S). S = [[b,b,b,h,v,v,v], [b,b,b,v,h,v,v], [b,b,h,v,b,v,v], [b,b,v,v,b,h,v], [b,b,v,v,b,v,h], [b,b,v,v,h,v,b], [b,h,v,v,b,v,b], [b,v,h,v,b,v,b], [b,v,v,v,b,h,b], [b,v,v,v,h,b,b], [b,v,h,v,v,b,b], [h,v,b,v,v,b,b], [v,v,b,h,v,b,b], [v,v,b,v,h,b,b], [v,v,h,v,b,b,b], [v,v,v,h,b,b,b]]
8. Algoritmo A*
8.1. El problema del 8 puzzle
8.1.1. Descripción del problema del 8 puzzle
- La descripción del problema se encuentra al principio del tema.
8.1.2. Representación del problema del 9 puzzle
- Un estado es una lista de los elementos del tablero de arriba a abajo y de izquierda a derecha.
estado_inicial(?E)
se verifica siE
es el estado inicial.estado_inicial([2,8,3,1,6,4,7,h,5]).
estado_final(?E)
se verifica siE
es un estado final.estado_final([1,2,3,8,h,4,7,6,5]).
sucesor(+E1,?E2)
se verifica siE2
es un sucesor del estadoE1
.sucesor(E1,E2) :- operador(O), transforma(E1,O,E2).
operador(O)
se verifica siO
es un operador.operador(izquierda). % Mover hueco a la izquierda operador(arriba). % Mover hueco arriba operador(derecha). % Mover hueco a la derecha operador(abajo). % Mover hueco abajo
transforma(+E1,+O,?E2)
se verifica siE2
es el estado obtenido aplicándole aE1
el operadorO
.transforma(E1,izquierda,E2) :- el_hueco_no_esta_en_la_primera_columna(E1), descompone(I,[A,h],D,E1), compone(I,[h,A],D,E2). transforma(E1,arriba,E2) :- descompone(I,[A,B,C,h],D,E1), compone(I,[h,B,C,A],D,E2). transforma(E1,derecha,E2) :- el_hueco_no_esta_en_la_ultima_columna(E1), descompone(I,[h,A],D,E1), compone(I,[A,h],D,E2). transforma(E1,abajo,E2) :- descompone(I,[h,A,B,C],D,E1), compone(I,[C,A,B,h],D,E2).
el_hueco_no_esta_en_la_primera_columna(E)
se verifica si en el estadoE
, el hueco no está en la primera columna.el_hueco_no_esta_en_la_primera_columna(E) :- nth0(N,E,h), N mod 3 =\= 0.
el_hueco_no_esta_en_la_ultima_columna(E)
se verifica si en el estadoE
, el hueco no está en la ultima columna.el_hueco_no_esta_en_la_ultima_columna(E) :- nth0(N,E,h), N mod 3 =\= 2.
descompone(?L1,?L2,?L3,+L4)
se verifica siL4
es la concatenación de las listasL1
,L2
yL3
.descompone(L1,L2,L3,L4) :- append(L12,L3,L4), append(L1,L2,L12).
compone(+L1,+L2,+L3,?L4)
se verifica siL4
es la concatenación de las listasL1
,L2
yL3
.compone(L1,L2,L3,L4) :- append(L1,L2,L12), append(L12,L3,L4).
heuristica(+E,?H)
se verifica siH
es el número de piezas colocadas enE
en distinto lugar que en el estado final.heuristica(E,H) :- estado_final(EF), diferencias(E,EF,H).
diferencias(+L1,+L2,?D)
se verifica siD
es el número de elementos de la listaL1
distintos de los correspondientes enL2
.diferencias([X|R1],[Y|R2],D) :- diferencias(R1,R2,D1), ( (X==h; X==Y) -> D is D1 ; otherwise -> D is D1+1). diferencias([],[],0).
coste(+E1,+E2,?C)
se verifica si C es el coste de pasar de E1 a E2. En este caso, todos los costes valen 1.coste(_,_,1).
- El código del 8 puzzle se encuentra en p_8_puzzle.pl.
8.2. El algoritmo A*
- Un nodo es un término de la forma
F/C-[E(n),...,E(1)]
donde[E(n),...,E(1)]
es una lista de estados de forma queE(1)
es el estado inicial yE(i+1)
es un sucesor deE(i)
,C
es el coste de dicho camino yF
es la suma deC
y la heurística deE(n)
. - Abiertos es una lista de nodos.
a_estrella(?S)
se verifica siS
es una solución del problema mediante A*. El procedimiento es- Sea
E
el estado inicial yH
su heurística. La soluciónS
es la solución obtenida por A* a partir de[H/0-[E]]
.
a_estrella(S) :- estado_inicial(E), heuristica(E,H), a_estrella([H/0-[E]],S).
- Sea
a_estrella(+Abiertos,?S)
se verifica siS
es una solución del problema a partir deAbiertos
mediante A*. El procedimiento es- 1. Si
_-[E|C]
es el mejor nodo deAbiertos
y- 1.1
E
es un estado final, entonces - 1.2
S
es la inversa de[E|C]
.
- 1.1
- 2. Si
N
es el mejor nodo deAbiertos
yR
son los restantes;- 2.1
Sucesores
son los sucesores deE
que no están en su camino, - 2.2
NAbiertos1
es la lista obtenida añadiendoSucesores
a continuación deR
y - 2.3
NAbiertos2
es la lista obtenida ordenandoNAbiertos1
; entonces - 2.4
S
es la solución encontrada por búsqueda minimal a partir deNodos2
yCo
es su coste.
- 2.1
a_estrella([_-[E|C]|_],S) :- estado_final(E), % 1.1 reverse([E|C],S). % 1.2 a_estrella([N|R],S) :- expande(N,Sucesores), % 2.1 append(R,Sucesores,NAbiertos1), % 2.2 sort(NAbiertos1,NAbiertos2), % 2.3 a_estrella(NAbiertos2,S). % 2.4
- 1. Si
expande(+N,?Sucs)
se verifica siSucs
es la lista de sucesores del nodoN
que no han sido visitados (es decir, los sucesores del primer elemento deN
que no pertenecen al resto deN
).expande(_/C-[E|R],Sucs) :- findall(F1/C1-[E1,E|R], (sucesor(E,E1), not(member(E1,R)), coste(E,E1,C2), C1 is C+C2, heuristica(E1,H1), F1 is C1+H1), Sucs).
- El código del algoritmo A* se encuentra en b_a_estrella.pl.
8.3. Solucióm del 8 puzzle mediante el algoritmo A*
La solución es
?- [p_8_puzzle, b_a_estrella]. true. ?- a_estrella(S). S = [[2,8,3,1,6,4,7,h,5], [2,8,3,1,h,4,7,6,5], [2,h,3,1,8,4,7,6,5], [h,2,3,1,8,4,7,6,5], [1,2,3,h,8,4,7,6,5], [1,2,3,8,h,4,7,6,5]] ?- trace(expande, +call). true. ?- a_estrella(S). T Call: b_a_estrella:expande(4/0-[[2,8,3,1,6,4,7,h,5]],_5016) T Call: b_a_estrella:expande(4/1-[[2,8,3,1,h,4,7,6,5], [2,8,3,1,6,4,7,h,5]],_5996) T Call: b_a_estrella:expande(5/2-[[2,8,3,h,1,4,7,6,5], [2,8,3,1,h,4,7,6,5], [2,8,3,1,6,4,7,h,5]],_7108) T Call: b_a_estrella:expande(5/2-[[2,h,3,1,8,4,7,6,5], [2,8,3,1,h,4,7,6,5], [2,8,3,1,6,4,7,h,5]],_8142) T Call: b_a_estrella:expande(5/3-[[h,2,3,1,8,4,7,6,5], [2,h,3,1,8,4,7,6,5], [2,8,3,1,h,4,7,6,5], [2,8,3,1,6,4,7,h,5]],_9152) T Call: b_a_estrella:expande(5/4-[[1,2,3,h,8,4,7,6,5], [h,2,3,1,8,4,7,6,5], [2,h,3,1,8,4,7,6,5], [2,8,3,1,h,4,7,6,5], [2,8,3,1,6,4,7,h,5]],_10006) S = [[2,8,3,1,6,4,7,h,5], [2,8,3,1,h,4,7,6,5], [2,h,3,1,8,4,7,6,5], [h,2,3,1,8,4,7,6,5], [1,2,3,h,8,4,7,6,5], [1,2,3,8,h,4,7,6,5]]
9. Ejercicios
9.1. Problemas de los bloques
Ejercicio 1.1. En una mesa hay una pila de bloques y sitio para otras dos pilas:
+---+ | C | +---+ | A | +---+ | B | +---+ +---+ +---+
Las operaciones que se pueden realizar son:
- Mover el bloque situado en la cima de una pila a la mesa.
- Mover un bloque de la mesa a la cima de una pila.
- Mover un bloque situado en la cima de una pila a la cima de otra pila.
El problema consiste en poner los bloques de la siguiente forma
+---+ | A | +---+ | B | +---+ | C | +---+
En la representación del problema se consideran que los estado son listas de tres elementos, cada uno de los cuales es una lista con los elementos que la forman (el primero es la cima y el último es la base).
Definir la relación estado_inicial(?E)
que se verifica si E
es el
estado inicial.
Solución
estado_inicial([[c,a,b],[],[]]).
Ejercicio 1.2. Definir la relación estado_final(?E)
que se verifica
si E
es un estado final.
Solución
estado_final(E) :- member([a,b,c],E).
Ejercicio 1.3. Definir la relación sucesor(+E1,?E2)
que se verifica
si E2
es un sucesor del estado E1
.
Solución
sucesor([[B|R],P2,P3],[R,[B|P2],P3]). sucesor([[B|R],P2,P3],[R,P2,[B|P3]]). sucesor([P1,[B|R],P3],[[B|P1],R,P3]). sucesor([P1,[B|R],P3],[P1,R,[B|P3]]). sucesor([P1,P2,[B|R]],[[B|P1],P2,R]). sucesor([P1,P2,[B|R]],[P1,[B|P2],R]).
Ejercicio 1.4. Intentar resolver el problema mediante búsqueda en profundidad sin ciclos.
Solución Con la búsqueda en profundidad sin ciclos no se encuentra solución.
?- [b_profundidad_sin_ciclos]. true. ?- trace(estado_final,+call). true. ?- profundidad_sin_ciclos(S). T Call: estado_final([[c,a,b],[],[]]) T Call: estado_final([[a,b],[c],[]]) T Call: estado_final([[b],[a,c],[]]) T Call: estado_final([[],[b,a,c],[]]) T Call: estado_final([[b],[a,c],[]]) ... ?- trace(estado_final,-call). true.
Ejercicio 1.5. Resolver el problema mediante búsqueda en profundidad con ciclos.
Solución
?- [b_profundidad_con_ciclos]. true. ?- profundidad_con_ciclos(S). S = [[[c,a,b], [], []], [[a,b], [c], []], [[b], [a,c], []], [[], [b,a,c], []], [[], [a,c], [b]], [[a], [c], [b]], [[], [c], [a,b]], [[c], [], [a,b]], [[a,c], [], [b]], [[c], [a], [b]], [[], [c,a], [b]], [[], [a], [c,b]], [[a], [], [c,b]], [[c,a], [], [b]], [[b,c,a], [], []], [[c,a], [b], []], [[a], [c,b], []], [[], [a,c,b], []], [[], [c,b], [a]], [[c], [b], [a]], [[], [b], [c,a]], [[b], [], [c,a]], [[c,b], [], [a]], [[b], [c], [a]], [[], [b,c], [a]], [[], [c], [b,a]], [[c], [], [b,a]], [[b,c], [], [a]], [[a,b,c], [], []]]
Ejercicio 1.6. Resolver el problema mediante búsqueda en anchura.
Solución
?-[b_anchura]. true. ?- anchura(S). S = [[[c,a,b], [], []], [[a,b], [c], []], [[b], [c], [a]], [[], [b,c], [a]], [[], [a,b,c], []]]
9.2. Problema de las 8 reinas
Ejercicio 2.1. El problema de las 8 reinas consiste en colocar 8 reinas en un tablero rectangular de dimensiones 8 por 8 de forma que no se encuentren más de una en la misma línea: horizontal, vertical o diagonal.
Los estados son listas de números que representan las ordenadas de las respectivas reinas. Por ejemplo, [3,5] representa que se ha colocado las reinas (1,5) y (2,3).
Definir la relación estado_inicial(?E)
que se verifica si E
es el
estado inicial.
Solución
estado_inicial([]).
Ejercicio 2.2. Definir la relación estado_final(?E)
que se verifica
si E
es un estado final.
Solución
estado_final(E) :- length(E,8).
Ejercicio 2.3. Definir la relación sucesor(+E1,?E2)
que se verifica
si E2
es un sucesor del estado E1
.
Solución
sucesor(E,[Y|E]) :- member(Y,[1,2,3,4,5,6,7,8]), not(member(Y,E)), no_ataca(Y,E). % no_ataca(Y,E) se verifica si E = [Y_n,...,Y_1] y la reina colocada en % (n+1,Y) no ataca a las colocadas en (1,Y_1),...,(n,Y_n). no_ataca(Y,E) :- no_ataca(Y,E,1). no_ataca(_,[],_). no_ataca(Y,[Y1|L],D) :- Y1-Y =\= D, Y-Y1 =\= D, D1 is D+1, no_ataca(Y,L,D1).
Ejercicio 2.4. Resolver el problema por búsqueda en profundidad sin ciclos.
Solución
?- [b_profundidad_sin_ciclos]. true. ?- profundidad_sin_ciclos(S). S = [[], [1], [5,1], [8,5,1], [6,8,5,1], [3,6,8,5,1], [7,3,6,8,5,1], [2,7,3,6,8,5,1], [4,2,7,3,6,8,5,1]]
Ejercicio 2.5. Resolver el problema por búsqueda en anchura.
Solución
?- [b_anchura]. true. ?- anchura(S). S = [[], [1], [5,1], [8,5,1], [6,8,5,1], [3,6,8,5,1], [7,3,6,8,5,1], [2,7,3,6,8,5,1], [4,2,7,3,6,8,5,1]]
10. Bibliografía
- I. Bratko.
Prolog programming for artificial intelligence (3 ed.)
(Addison–Wesley, 1990)
- Cap. 11 "Basic problem-solving strategies".
- A. Csenki.
Applications of Prolog.
(Bookbook, 2009).
- Cap. 2: Blind search
- Cap. 3: Informed search.
- W. Ertel.
Introduction to artificial intelligence (2E).
(Springer Verlag, 2017).
- Cap. 5.6: A planning example.
- Cap. 6: Search, games and problem solving.
- P. Flach.
Simply logical (Intelligent reasoning by example).
(John Wiley, 1994)
- Cap. 5: "Seaching graphs".
- D. Poole, A. Mackworth y R. Goebel.
Computational intelligence (A logical approach)}
(Oxford University Press, 1998)
- Cap. 4: "Searching".
- Y. Shoham.
Artificial intelligence techniques in Prolog.
(Morgan Kaufmann, 1994)
- Cap. 2 "Search".
- T. Smith.
Artificial intelligence programming in Prolog.
(Univ. of Edinburgh, 2004).
- Cap. 12: AI applications of Prolog: State-space search.
- J.M. Spivey
Introduction to logic programming through Prolog.
(Prentice-Hall International, 1996).
- Cap. 9: Searching problemas.