Algoritmo de Busca em Largura (BFS) para Planejamento de Caminhos em Labirintos

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

  1. Adicione o nó inicial à fila e marque-o como visitado.
  2. 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.
  3. 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.

Tags: bfs Busca em Largura algoritmos de busca Labirintos Grafos

Publicado em 7-6 10:45