Implementação
Existem várias técnicas para se efetuar uma busca. Como já vimos, uma delas é a estratégia de busca uniforme. No decorrer deste texto veremos como foi implementado o algoritmo de busca uniforme na linguagem prolog. A formalização do problema já foi apresentada na página Formalização do Problema usando Espaço de Estados.
O programa que efetua a busca tem um parâmetro denominado Fila. Este parâmetro contém as trajetórias que estão esperando a expansão e verificação se uma delas é o estado meta. Cada uma destas trajetórias pode ser representada por uma lista de estruturas do tipo "t(A,[r(R, N),r(R1,N1)])", onde o primeiro argumento de "t(_,_)" fornece g(N), g(N) é o custo conhecido de ir-se da raiz até o nódulo N. O segundo parâmetro de "t" fornece a trajetória feita para se chegar até o estado r(R,N), R é o ramo que liga o nódulo N ao pai dele. Quando o nó não tem pai ele é representado pela estrutura r(raiz,_). Durante a execução do algoritmo a fila de espera é colocada em ordem crescente pelo predicado "sort(_,_)", com isso, as estruturas da fila "t(_,_)" com menor g(N) virão para o inicio da fila e, portanto, serão examinadas primeiro. Na apresentação do código abaixo estas características são explicadas com mais detalhes.
/*--------------------------------------------------------------------------
Declaração dos operadores
--------------------------------------------------------------------------*/
:- op(35, xf, [e_uma_meta, atinge_uma_meta]).
:- op(35,xfx,[transforma,nao_produz_circulos_em]).
:- op(30,xfx,[a,em]).
/*--------------------------------------------------------------------------
ap : concatena duas listas.
--------------------------------------------------------------------------*/
ap([],X,X):- !.
ap([X|Y],Z,[X|W]):- ap(Y,Z,W).
/* --------------------------------------------------------------------------
membro : verifica se um elemento está em uma lista
--------------------------------------------------------------------------*/
membro(X,[X|_]):- !.
membro(X,[_|Y]):- membro(X,Y).
/* --------------------------------------------------------------------------
ache_todos: acha todos os estados que podem ser
expandidos a partir do estado Y.
--------------------------------------------------------------------------*/
ache_todos(X,Y,Z):- findall(X,Y,Z), !.
ache_todos(_,_,[]).
/*--------------------------------------------------------------------------
imprima : serve para imprimir uma trajetória.
A trajetória é representada por uma lista de estruturas do tipo
t(G,[r(R,Nodo),....]), onde G é o custo de processamento até
o nó atual, R é o ramo que liga nódulo N ao pai dele.
--------------------------------------------------------------------------*/
imprima(t(GN,T)) :- imprima_trajet(T).
imprima_trajet([r(raiz,Raiz)]):- !,
write('Estado Incial: '), write(Raiz), write('.').
imprima_trajet([r(Ramo,Nodo)|R]):-
imprima_trajet(R), nl,
write(Ramo), write(' e, portanto, temos: '), nl,
write(Nodo), write('.').
/*--------------------------------------------------------------------------
resolva: chama o programa que faz a busca uniforme
Inicialmente, a fila contém a mini-trajetória [t(0,[r(raiz,E)])],
a qual começa e acaba na raiz.
--------------------------------------------------------------------------*/
resolva:- estado_inicial(E),
busca([t(0,[r(raiz,E)])], Solucao),
imprima(Solucao), nl.
/*--------------------------------------------------------------------------
busca: realiza a busca uniforme, procurando o caminho que leva
a uma das metas.
Solução é um caminho que leva da raiz à meta.
T é o primeiro elemento de uma lista de trajetórias candidatas a ser
Solução. Se o predicado "atinge_a_meta" verificar que T chegou à
meta, o problema está resolvido e basta fazer Solução ser igual a T.
--------------------------------------------------------------------------*/
busca([T|_],Solucao):- T atinge_uma_meta, !, Solucao = T.
/*--------------------------------------------------------------------------
Tem-se agora o caso de que T ainda não chegou até a Solução.
Note que T é uma lista de nódulos. Deve-se agora expandir T
de modo a incluir seus filhos na Fila. A lista das extensões possíveis
é colocada em Extensoes, o predicado "ap" concatena Extensoes com
a Fila anterior retornando a FilaEstendida. A FilaEstendida será
ordenada pelo predicado "sort" de acordo com g(N), gerando a nova fila
denominada FilaComMelhorNaFrente.
E a busca continua, agora com FilaComMelhorNaFrente.
--------------------------------------------------------------------------*/
busca([T|Fila],Solucao):-
ache_todos(ExtensaoAteUmFilho, estende_ate_filho(T,ExtensaoAteUmFilho),Extensoes),
ap(Fila,Extensoes,FilaEstendida),
sort(FilaEstendida,FilaComMelhorNaFrente),
busca(FilaComMelhorNaFrente,Solucao).
/*--------------------------------------------------------------------------
Agora a trajetória é estendida de r(Ramo,Nodo) até um Filho do nódulo
Nodo. O filho de Nodo é gerado a partir de uma operação Op. Se este
filho ainda não se encontra na Trajetória, o custo para se obter o mesmo
é somado com o custo de seu nó pai, obtendo-se um novo custo para o
mesmo.
--------------------------------------------------------------------------*/
estende_ate_filho(t(G,[r(Ramo,Nodo)| Trajetoria]),
t(G1,[r(Op,NodoFilho),r(Ramo,Nodo) | Trajetoria])) :-
operacao(Op,Custo) transforma Nodo em NodoFilho,
not( NodoFilho nao_produz_circulos_em Trajetoria),
G1 is G + Custo.
/*--------------------------------------------------------------------------
Verifica se o estado que está sendo gerado já está
na trajetória. Isso elimina os possíveis ciclos.
--------------------------------------------------------------------------*/
Estado nao_produz_circulos_em Trajetoria:-
membro(r(Operacao,Estado),Trajetoria).
/*--------------------------------------------------------------------------
A trajetória que fornece a solução atinge a meta.
O processo de busca termina.
--------------------------------------------------------------------------*/
t(_,[r(Ramo,M)|_]) atinge_uma_meta :- M e_uma_meta.
Conteúdo |