Procura, jogos e manipulação simbólica

Comparar DFS e BFS, evitar ciclos, calcular minimax e transformar termos por regras recursivas.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

No grafo com arestas a→b, a→c, b→d, d→e, c→e e d→a, há dois caminhos simples de a até e. DFS segue o primeiro ramo até [a,b,d,e]; BFS encontra [a,c,e], com menos arestas.

Em a→b→d, os visitados do caminho já contêm a, por isso excluem o retorno d→a.

Evitar ciclos sem esconder respostas

Na procura em profundidade, começamos por um vizinho e continuamos nesse ramo antes de tentar o vizinho seguinte. Uma lista de visitados do caminho atual impede regressar a um estado do mesmo caminho, ou seja não é a mesma coisa que proibir esse estado em todos os caminhos: um caminho alternativo pode precisar dele.

A relação caminho(X,Z,V,C) da página anterior pressupõe X, Z e as ligações conhecidos. Cada passo escolhe um vizinho que não está em V e aumenta os visitados. Num grafo finito, esta condição limita cada caminho ao número de estados. Sem ela, um ciclo pode impedir o motor de chegar a uma solução num ramo posterior.

A fila BFS passo a passo

Com os caminhos guardados ao contrário, a fila evolui assim:

Fila antesRetiradoNovos caminhosFila depois
[[a]][a][b,a], [c,a][[b,a],[c,a]]
[[b,a],[c,a]][b,a][d,b,a][[c,a],[d,b,a]]
[[c,a],[d,b,a]][c,a][e,c,a][[d,b,a],[e,c,a]]

Depois destas três expansões, o caminho objetivo já está na fila mas ainda não foi retirado. Expandimos [d,b,a]: a aresta d→a é excluída pelos visitados e acrescentamos [e,d,b,a], deixando a fila [[e,c,a],[e,d,b,a]].

Só agora retiramos [e,c,a] e devolvemos [a,c,e]. Distingue descobrir um candidato de o aceitar quando chega à frente da fila.

Guardar a fronteira numa fila

BFS, procura em largura, guarda os caminhos por explorar numa fila. Retira o mais antigo, verifica se chegou ao objetivo e coloca os novos caminhos no fim. Assim, explora todos os caminhos de uma aresta antes dos de duas, e assim sucessivamente.

O programa guarda cada caminho ao contrário, [EstadoAtual,...,Inicio], para acrescentar um vizinho à cabeça em tempo constante e só usar reverse ao encontrar a resposta.

DFS e BFS sobre o mesmo grafo
:- use_module(library(lists)).
ligado(a,b). ligado(a,c). ligado(b,d). ligado(d,e). ligado(c,e).
ligado(d,a).

caminho(X,X,_,[X]).
caminho(X,Z,V,[X|R]) :- ligado(X,Y), \+ member(Y,V), caminho(Y,Z,[Y|V],R).

bfs([[Z|R]|_],Z,C) :- reverse([Z|R],C).
bfs([[X|R]|Fila],Z,C) :-
    X \= Z,
    findall([Y,X|R],(ligado(X,Y), \+ member(Y,[X|R])),Novos),
    append(Fila,Novos,Seguinte),
    bfs(Seguinte,Z,C).

write_portatil(X) :- write(X), nl.

main :-
    once(caminho(a,e,[a],D)), write_portatil(D),
    bfs([[a]],e,B), write_portatil(B).
Resultado

Executa o código para ver a saída e os gráficos.

DFS imprime [a,b,d,e]; BFS imprime [a,c,e]. Há também uma ligação de d para a, que os visitados impedem de repetir no caminho.

BFS explora por número de arestas, desde que cada expansão termine. Com custos iguais, o primeiro objetivo tem também menor custo. Com arestas de custo 10, 1 e 1, um caminho de duas arestas pode custar 2 e ser melhor que uma ligação direta de custo 10. Para otimizar pesos, precisamos de uma estratégia que ordene pelo custo acumulado. A fila BFS pode guardar muitas alternativas em memória.

Minimax e poda alfa-beta

Em minimax, MAX escolhe o maior valor e MIN o menor. Uma folha tem um valor medido do ponto de vista de MAX. Para uma árvore finita, calculamos primeiro os filhos e depois aplicamos a escolha do jogador no nó.

O ramo esquerdo vale min(-1,4)=-1 e o direito min(2,3)=2, pelo que a raiz vale max(-1,2)=2. MAX escolhe o ramo direito porque MIN pode impor -1 no ramo esquerdo e não consegue garantir a folha 4.

O modelo pressupõe dois jogadores com interesses opostos, informação completa e as jogadas da árvore disponíveis.

Numa árvore de fator de ramificação b e profundidade d, explorar todos os nós exige ordem b^d visitas. Limitar a profundidade pede uma função de avaliação para os estados não terminais. Esses valores são estimativas, não provas de vitória.

Na poda alfa-beta, alpha é um limite inferior já garantido a MAX e beta é um limite superior já garantido a MIN. Se alpha >= beta, explorar mais aquele ramo não melhora a decisão de um antepassado, e pode ser dispensado. A ordem das jogadas muda a quantidade de poda, mas não o valor minimax numa árvore finita com as mesmas folhas e regras.

Na árvore acima, depois de visitar o filho MIN esquerdo a raiz MAX tem alpha=-1; no filho MIN direito, ler 2 estabelece beta=2 e, como -1 < 2, ainda não podemos podar. Lemos também 3, o MIN fica em 2 e a raiz escolhe 2.

Se visitarmos o ramo direito primeiro, MAX já tem alpha=2. No ramo esquerdo, a primeira folha -1 estabelece beta=-1; agora alpha >= beta e a folha 4 pode ser dispensada. A decisão final continua a ser 2, com menos uma folha examinada.

Termos que representam expressões

Um termo como produto(var(x),var(x)) representa a expressão x*x. Não é uma multiplicação a executar com is; os functors guardam a estrutura para uma transformação simbólica. Uma regra pode desmontar o construtor e construir o resultado:

derivar(soma(A,B), X, soma(DA,DB)) :-
    derivar(A,X,DA), derivar(B,X,DB).

A regra da soma deriva os dois componentes. A regra do produto constrói DA*B + A*DB, conservando também as expressões originais. A derivada de uma variável relativamente a si própria é um; a de outra variável é zero. O programa pressupõe que os nomes das variáveis estão instanciados e que a expressão é uma árvore finita dos construtores apresentados.

Derivar uma expressão e avaliar minimax
:- use_module(library(lists)).
derivar(num(_), _, num(0)).
derivar(var(X), X, num(1)).
derivar(var(X), Y, num(0)) :- X \= Y.
derivar(soma(A,B), X, soma(DA,DB)) :- derivar(A,X,DA), derivar(B,X,DB).
derivar(produto(A,B), X, soma(produto(DA,B),produto(A,DB))) :-
    derivar(A,X,DA), derivar(B,X,DB).

valor(folha(N),N).
max_num([X|Xs], M) :- max_num(Xs, X, M).
max_num([], M, M).
max_num([X|Xs], A, M) :- (X > A -> B = X ; B = A), max_num(Xs, B, M).
valor(no(max,Filhos),V) :-
    Filhos = [_|_], findall(N,(member(F,Filhos),valor(F,N)),Ns), max_num(Ns,V).
min_num([X|Xs], M) :- min_num(Xs, X, M).
min_num([], M, M).
min_num([X|Xs], A, M) :- (X < A -> B = X ; B = A), min_num(Xs, B, M).
valor(no(min,Filhos),V) :-
    Filhos = [_|_], findall(N,(member(F,Filhos),valor(F,N)),Ns), min_num(Ns,V).

main :-
    derivar(produto(var(x),var(x)),x,D), write(D), nl,
    valor(no(max,[no(min,[folha(-1),folha(4)]),no(min,[folha(2),folha(3)])]),V),
    write(V), nl.
Resultado

Executa o código para ver a saída e os gráficos.

O primeiro resultado é soma(produto(num(1),var(x)),produto(var(x),num(1))). Ainda não está simplificado: uma fase posterior poderia retirar multiplicações por um e obter soma(var(x),var(x)). O segundo resultado é 2, o valor do jogo desenhado acima.

Uma transformação simbólica termina quando cada chamada recursiva recebe uma subexpressão menor. Se acrescentares regras de reescrita nas duas direções, como transformar uma soma numa forma e imediatamente regressar à original, podes criar um ciclo. Escolhe uma direção de simplificação e uma medida de progresso.

Exercícios

Escolher o caminho mais curto

Quando garante BFS um caminho com menor custo?

A tua resposta

Pista

BFS ordena a exploração pelo número de passos.

Solução

Com custos iguais, BFS encontra a solução de menor profundidade. Para pesos diferentes, é preciso considerar o custo acumulado.

  • Quando cada aresta tem o mesmo custo e a expansão termina.. Nesse caso, menor número de arestas equivale a menor custo.
  • Em qualquer grafo com pesos positivos.. Os pesos podem variar.
  • Sempre que DFS também encontra uma solução.. A existência de uma solução não iguala os custos.

Erros frequentes

Não atribuas à fila uma ordenação por pesos que ela não tem.

Voltar à explicação
Conservar a decisão do adversário

Uma raiz MAX tem filhos MIN com folhas [3,8] e [-2,6]. Qual é o valor da raiz?

Pista

Calcula os nós MIN antes da raiz.

Solução

A raiz escolhe max(3,-2)=3. O valor 8 não fica disponível se MIN puder escolher 3.

Erros frequentes

Não tires o máximo de todas as folhas ignorando os níveis do adversário.

Voltar à explicação
Aplicar a regra estrutural

Deriva simbolicamente x*(x+2) relativamente a x, sem exigir simplificação final.

Pista

Usa D(AB)=DAB+A*DB.

Solução

A expressão é 1*(x+2)+x*(1+0). Se simplificares, dá 2*x+2. A transformação manipula a árvore, não avalia x.

Confere a tua resposta:

  • Derivo ambos os fatores.
  • Conservo o outro fator em cada parcela.
  • Obtenho 1*(x+2)+x*(1+0).

Erros frequentes

Não escrevas DA*DB, que não é a regra do produto.

Voltar à explicação
Descobrir e retirar o caminho objetivo

As arestas, na ordem de geração, são a→b, a→c, b→d, d→e, c→e, d→a. A fila começa em [[a]] e guarda caminhos ao contrário. Retira um caminho de cada vez, exclui estados já no caminho e acrescenta as extensões no fim. Segue até retirar o primeiro caminho que termina em e.

Pista
Mantém na fila os caminhos anteriores ainda por explorar.
Solução

As filas depois de cada expansão são:

Caminho retiradoFila seguinte
[a][[b,a],[c,a]]
[b,a][[c,a],[d,b,a]]
[c,a][[d,b,a],[e,c,a]]
[d,b,a][[e,c,a],[e,d,b,a]]

Na quarta expansão, d→a é excluída porque a já pertence ao caminho. Retiramos depois [e,c,a], que satisfaz o objetivo; inverter dá [a,c,e]. O candidato apareceu na terceira expansão, mas a implementação só o aceita quando o retira da frente da fila.

Confere a tua resposta:

  • Registo a fila e os caminhos invertidos.
  • Excluo a aresta de retorno a a.
  • Distingo inserção do objetivo na fila da sua aceitação.

Erros frequentes

Não devolvas o objetivo no instante em que o geras se estás a seguir a implementação que o testa ao retirar.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.