A aplicação da Busca em Largura (BFS) em problemas de labirinto oferece uma abordagem sistemática para exploração, mas enfrenta limitações em cenários dinâmicos. Se um grupo de agentes iniciar de um ponto e se espalhar camada por camada, o número de agentes necessário cresce exponencialmente com a profundidade da busca. Entretanto, se o labirinto mudar aleatoriamente após um agente encontrar o objetivo, a estratégia de BFS torna-se inadequada, pois os agentes já estão dispersos e não podem se reorganizar eficientemente para uma nova estrutura.
Apesar disso, o BFS permanece vantajoso para labirintos estáticos. Considere um labirinto representado pela seguinte matriz, onde a pessoa inicia na posição (0,0) orientada para o eixo Y, com paredes nas bordas e o destino marcado por "*". Se a ordem de exploração for (direita, cima, esquerda, baixo), a pessoa pode ficar presa na camada externa. Para evitar ciclos, utiliza-se um conjunto para registrar nós visitados, uma prática comum tanto no BFS quanto no DFS.
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 1 0 0 0
0 0 0 1 0 * 1 0 0 0
0 0 0 1 0 1 1 0 0 0
0 0 0 1 0 0 0 0 0 0 y
0 0 0 0 0 0 0 0 0 0 ^
0 0 0 0 0 0 0 0 0 0 |
0 0 0 0 0 0 0 0 0 0 |______>x
Princípio do Algoritmo BFS
A Busca em Largura (BFS) é um algoritmo para busca em grafos que explora nós camada por camada, começando do nó inicial. Ele utiliza uma fila para armazenar nós a serem visitados, garantindo uma ordem de processamento do tipo "primeiro a entrar, primeiro a sair" (FIFO).
Passos do Algoritmo
- Adicione o nó inicial à fila e marque-o como visitado.
- Enquanto a fila não estiver vazia:
- Remova o nó da frente da fila e processe-o.
- Se o nó for o objetivo, encere a busca e retorne o resultado.
- Caso contrário, adicione todos os vizinhos não visitados à fila e marque-os como visitados.
- Se a fila esvaziar sem encontrar o objetivo, a busca falha.
Exemplo Prático
Considere um labirinto 4x4, onde 0 indica um caminho livre e 1 indica um obstáculo. O início é em (0,0) e o destino em (3,3), representado pela matriz:
0 1 0 0
0 0 0 1
1 0 1 1
0 0 0 0
Resultado da Execução
Caminho encontrado: (0,0) -> (1,0) -> (1,1) -> (2,1) -> (3,1) -> (3,2) -> (3,3). Comprimento do caminho: 6 passos.
Implementação em Python
import collections
class No:
def __init__(self, posX, posY, ancestral=None, nivel=0):
self.posX = posX
self.posY = posY
self.ancestral = ancestral
self.nivel = nivel
def busca_em_largura(labirinto, inicio, fim):
linhas = len(labirinto)
colunas = len(labirinto[0])
visitado = [[False for _ in range(colunas)] for _ in range(linhas)]
fila = collections.deque()
visitado[inicio.posX][inicio.posY] = True
fila.append(inicio)
while fila:
atual = fila.popleft()
if atual.posX == fim.posX and atual.posY == fim.posY:
caminho = []
while atual:
caminho.append((atual.posX, atual.posY))
atual = atual.ancestral
caminho.reverse()
print("Caminho completo:", caminho)
print(f"Comprimento: {len(caminho) - 1} passos")
return True
for dx, dy in [(-1,0), (0,-1), (1,0), (0,1)]:
novoX = atual.posX + dx
novoY = atual.posY + dy
if 0 <= novoX < linhas and 0 <= novoY < colunas and labirinto[novoX][novoY] == 0 and not visitado[novoX][novoY]:
visitado[novoX][novoY] = True
vizinho = No(novoX, novoY, atual, atual.nivel + 1)
fila.append(vizinho)
return False
if __name__ == "__main__":
labirinto = [
[0, 1, 0, 0],
[0, 0, 0, 1],
[1, 0, 1, 1],
[0, 0, 0, 0]
]
inicio = No(0, 0)
fim = No(3, 3)
busca_em_largura(labirinto, inicio, fim)
Análise de Execução
| Passo | Fila Inicial | Nó Removido | Novos Vizinhos | Fila Atualizada |
|---|---|---|---|---|
| 0 | [(0,0)] | (0,0) | (1,0) | [(1,0)] |
| 1 | [(1,0)] | (1,0) | (1,1) | [(1,1)] |
| 2 | [(1,1)] | (1,1) | (1,2), (2,1) | [(1,2), (2,1)] |
| 3 | [(1,2), (2,1)] | (1,2) | (0,2) | [(2,1), (0,2)] |
| 4 | [(2,1), (0,2)] | (2,1) | (3,1) | [(0,2), (3,1)] |
| 5 | [(0,2), (3,1)] | (0,2) | (0,3) | [(3,1), (0,3)] |
| 6 | [(3,1), (0,3)] | (3,1) | (3,0), (3,2) | [(0,3), (3,0), (3,2)] |
| 7 | [(0,3), (3,0), (3,2)] | (0,3) | Nenhum | [(3,0), (3,2)] |
| 8 | [(3,0), (3,2)] | (3,0) | Nenhum | [(3,2)] |
| 9 | [(3,2)] | (3,2) | (3,3) | [(3,3)] |
| 10 | [(3,3)] | (3,3) | Objetivo encontrado | - |
Reflexões sobre Complexidade e Escolha
Intuitivamente, o DFS pode parecer mais lento devido ao retrocesso em becos sem saída, mas em termos de complexidade assintótica, tanto o BFS quanto o DFS têm complexidade O(V + E), onde V é o número de vértices e E o número de arestas.
Para um agente único em um labirinto, o DFS envolve exploração e retrocesso, enquanto o BFS simulado por um único agente requer extensão camada por camada, resultando em tempos totais similares em termos de magnitude.