Busca em Largura (BFS - Breadth-First Search)
1. Explorando em Camadas
A BFS (Busca em Largura) é como jogar uma pedra num lago calmo: as ondas se propagam uniformemente em todas as direções. Ela explora o grafo em níveis a partir de um nó origem \(S\): 1. Primeiro visita \(S\) (Distância 0). 2. Depois todos os vizinhos diretos de \(S\) (Distância 1). 3. Depois os vizinhos dos vizinhos (Distância 2). 4. E assim por diante…
Por causa dessa propriedade, a BFS garante encontrar o Caminho Mínimo (menor número de arestas) em grafos não ponderados (ou com pesos iguais).
2. O Algoritmo
Utilizamos uma Fila (Queue) para gerenciar a ordem de visita (FIFO - First In, First Out).
Estados dos Vértices: - Não Visitado: Ainda não descoberto. - Visitado/Na Fila: Descoberto, mas seus vizinhos ainda não foram processados. - Processado: Já saiu da fila e seus vizinhos já foram checados.
Passo a Passo: 1. Inicialize um vetor visited[] como false e dist[] como \(\infty\). 2. Coloque o nó inicial \(S\) na Fila, marque visited[S] = true e dist[S] = 0. 3. Enquanto a Fila não estiver vazia: - Remova o elemento \(u\) da frente. - Para cada vizinho \(v\) de \(u\): - Se \(v\) não foi visitado: - Marque visited[v] = true. - Defina dist[v] = dist[u] + 1. - Adicione \(v\) na Fila.
3. Implementação em Java
Assumindo que temos a lista de adjacência List<List<Integer>> adj.
import java.util.*;
public class BFS {
public static void bfs(int start, int V, List<List<Integer>> adj) {
// Estruturas Auxiliares
boolean[] visited = new boolean[V];
int[] dist = new int[V];
int[] parent = new int[V]; // Para reconstruir o caminho
Arrays.fill(dist, -1); // -1 indica inalcançável
Arrays.fill(parent, -1);
// Configuração Inicial
Queue<Integer> queue = new LinkedList<>();
queue.add(start);
visited[start] = true;
dist[start] = 0;
System.out.println("Iniciando BFS a partir de " + start);
while (!queue.isEmpty()) {
int u = queue.poll(); // Tira da frente
// System.out.print(u + " "); // Ordem de visita
for (int v : adj.get(u)) {
if (!visited[v]) {
visited[v] = true;
dist[v] = dist[u] + 1;
parent[v] = u; // Guardamos de onde viemos
queue.add(v);
}
}
}
// Exemplo: Imprimir distâncias
for(int i=0; i<V; i++) {
System.out.println("Distância de " + start + " para " + i + ": " + dist[i]);
}
}
// Bônus: Reconstruir o caminho de Start até Target
public static List<Integer> getPath(int target, int[] parent) {
List<Integer> path = new ArrayList<>();
if (parent[target] == -1) return path; // Sem caminho
for (int v = target; v != -1; v = parent[v]) {
path.add(v);
}
Collections.reverse(path); // O caminho foi montado de trás pra frente
return path;
}
}Complexidade
- Tempo: \(O(V + E)\). Cada vértice entra na fila uma vez e cada aresta é verificada duas vezes (uma por ponta, em grafos não direcionados).
- Espaço: \(O(V)\) para a fila e vetores auxiliares.
4. BFS em Grids (Matrizes)
Muitos problemas (ex: labirintos) são dados como uma matriz, onde cada célula (r, c) é um vértice e vizinhos são (r+1, c), (r-1, c), etc.
Não precisamos criar uma Lista de Adjacência explícita. Usamos a matriz diretamente e vetores de direção.
// Direções: Cima, Direita, Baixo, Esquerda
int[] dr = {-1, 0, 1, 0};
int[] dc = {0, 1, 0, -1};
// Na BFS:
while(!queue.isEmpty()) {
Point p = queue.poll();
for(int i=0; i<4; i++) {
int nr = p.r + dr[i];
int nc = p.c + dc[i];
if(isValid(nr, nc) && !visited[nr][nc]) {
visited[nr][nc] = true;
queue.add(new Point(nr, nc));
}
}
}5. Multi-Source BFS
E se quisermos saber a qual a distância da “saída mais próxima” num labirinto com múltiplas saídas? Truque: Coloque todas as saídas na fila inicial com distância 0. A BFS vai expandir todas as “frentes de onda” simultaneamente. O primeiro a chegar num vértice define sua distância mínima para qualquer saída.
Aplicações Típicas: 1. Menor caminho em labirintos/mapas sem peso. 2. Checar conectividade (componentes conexos). 3. Web Crawlers (Explorar links nível a nível). 4. Algoritmo de fluxo Edmonds-Karp (usa BFS para achar caminho aumentante).