Hill Climbing, Beam Search, Minimax e Alpha-Beta
Carga Horária Recomendada: 12 horas
Dois algoritmos fundamentais para exploração sistemática de espaços de estados:
def dfs(no, visitados):
if no in visitados:
return
visitados.add(no)
if objetivo(no):
return no
for vizinho in no.vizinhos:
resultado = dfs(vizinho, visitados)
if resultado:
return resultado
Onde:
from collections import deque
def bfs(inicio):
fila = deque([inicio])
visitados = {inicio}
while fila:
no = fila.popleft()
if objetivo(no):
return no
for vizinho in no.vizinhos:
if vizinho not in visitados:
visitados.add(vizinho)
fila.append(vizinho)
Onde:
| Critério | DFS | BFS |
|---|---|---|
| Estrutura | Pilha | Fila |
| Memória | Baixa | Alta |
| Ótimo | Não | Sim* |
| Completo | Limitado | Sim |
* Em grafos não ponderados.
"Uma heurística estima o quão próximo estamos da solução."
Problema das cidades:
enquanto houver melhoria:
gerar vizinhos
escolher melhor vizinho
mover para ele
| Beam Width | Qualidade | Custo |
|---|---|---|
| Pequeno | Média | Baixo |
| Grande | Alta | Elevado |
A* é um dos algoritmos de busca heurística mais importantes da Inteligência Artificial. Ele combina o custo já percorrido com uma estimativa do custo restante.
f(n) = g(n) + h(n)
O algoritmo sempre expande o nó com menor valor de f(n).
f(n).
def a_star(inicio, objetivo):
aberta = PriorityQueue()
aberta.put((0, inicio))
g = {inicio: 0}
pai = {}
while not aberta.empty():
_, atual = aberta.get()
if atual == objetivo:
return reconstruir_caminho(pai, atual)
for vizinho, custo in atual.vizinhos:
novo_g = g[atual] + custo
if vizinho not in g or novo_g < g[vizinho]:
g[vizinho] = novo_g
f = novo_g + heuristica(vizinho)
aberta.put((f, vizinho))
pai[vizinho] = atual
Encontrar rota entre duas cidades.
Uma heurística é admissível quando nunca superestima o custo real.
h(n) ≤ h*(n)
h(n) ≤ c(n,n') + h(n')
| Algoritmo | Usa h(n) | Ótimo |
|---|---|---|
| Dijkstra | Não | Sim |
| Greedy Best-First | Sim | Não |
| A* | Sim | Sim |
minimax(nó):
se folha:
retorna valor
se MAX:
retorna máximo dos filhos
se MIN:
retorna mínimo dos filhos
O(bd)
Se α ≥ β, pode podar.
A Computação Evolutiva reúne técnicas de otimização baseadas nos princípios da evolução biológica, como seleção natural, reprodução e mutação.
"Boas soluções, rapidamente, para problemas difíceis."
Propostos por John Holland na década de 1970, simulam a evolução de uma população de soluções candidatas.
Exemplo:
1011010110010101
Mede a qualidade de cada indivíduo.
Quanto maior o fitness, melhor a solução.
Indivíduos mais aptos possuem maior probabilidade de reprodução.
Combina características de dois pais.
Pai 1: 110011|1010
Pai 2: 001101|0111
Filho: 1100110111
Introduz diversidade genética.
Antes: 1100110111
Depois:1100100111
def genetic_algorithm():
populacao = inicializar()
while not criterio_parada():
fitness = avaliar(populacao)
pais = selecionar(populacao, fitness)
filhos = crossover(pais)
filhos = mutacao(filhos)
populacao = substituir(populacao, filhos)
return melhor_individuo(populacao)
Maximizar:
f(x) = x²
com x ∈ [0, 31]
| Geração | Melhor Fitness |
|---|---|
| 0 | 576 |
| 10 | 841 |
| 20 | 961 |