BFS: Menor Caminho e Percurso por Níveis
Use BFS para encontrar o menor caminho em um grafo não ponderado, resolva word-ladder nível a nível e clone um grafo usando um mapa hash.
BFS: Menor Caminho e Percurso por Níveis é uma aula grátis de DSA Interview Prep no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de DSA Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DSA Interview Prep inclui 4 aulas no total.
BFS e Caminho Mínimo em Grafos Não Ponderados
O BFS encontra o caminho mínimo (com o menor número de arestas) em um grafo não ponderado porque explora os nós em ordem de distância crescente a partir da origem. A primeira vez que um nó é alcançado durante o BFS, isso ocorre por meio do caminho possível mais curto. Essa propriedade não se aplica ao DFS. Para grafos ponderados com pesos não negativos, use o algoritmo de Dijkstra; o BFS trata implicitamente todas as arestas como se tivessem peso 1.
from collections import deque, defaultdict
def shortest_path(graph, start, end):
if start == end:
return 0
visited = {start}
queue = deque([(start, 0)]) # (node, distance)
while queue:
node, dist = queue.popleft()
for neighbour in graph[node]:
if neighbour == end:
return dist + 1
if neighbour not in visited:
visited.add(neighbour)
queue.append((neighbour, dist + 1))
return -1 # no path found
graph = defaultdict(list)
for u, v in [(0,1),(1,2),(2,3),(0,3),(1,4)]:
graph[u].append(v); graph[v].append(u)
print(shortest_path(graph, 0, 3)) # 1 (direct edge)
print(shortest_path(graph, 0, 4)) # 2 (0->1->4)Rastreando o Caminho Mínimo Real
Para reconstruir o caminho real (não apenas seu comprimento), mantenha um dicionário de predecessores que registre como cada nó foi alcançado. Ao alcançar o destino, percorra o mapa de predecessores do fim até o início e inverta o resultado. Isso adiciona espaço O(V) para o mapa de predecessores, mas fornece o caminho completo em tempo O(comprimento_do_caminho) após a conclusão do BFS.
from collections import deque, defaultdict
def shortest_path_with_route(graph, start, end):
parent = {start: None}
queue = deque([start])
while queue:
node = queue.popleft()
if node == end:
break
for nb in graph[node]:
if nb not in parent:
parent[nb] = node
queue.append(nb)
if end not in parent:
return [] # no path
# Reconstruct path by tracing back
path = []
node = end
while node is not None:
path.append(node)
node = parent[node]
return path[::-1] # reverse
graph = defaultdict(list)
for u, v in [(0,1),(1,2),(2,3),(0,4),(4,3)]:
graph[u].append(v); graph[v].append(u)
print(shortest_path_with_route(graph, 0, 3)) # [0, 4, 3] or [0, 1, 2, 3]Escada de Palavras: BFS em Grafo Implícito
Escada de Palavras (LeetCode #127) pede o número mínimo de alterações de um único caractere necessárias para transformar uma palavra inicial em uma palavra final, sendo que cada palavra intermediária deve estar em um dicionário. Esse é um BFS em um grafo implícito, no qual os nós são palavras e as arestas conectam palavras que diferem em uma letra. Gere todas as mutações de uma letra e verifique se elas estão no conjunto de palavras. O BFS garante a sequência mínima de transformações.
from collections import deque
def word_ladder(begin_word, end_word, word_list):
word_set = set(word_list)
if end_word not in word_set:
return 0
queue = deque([(begin_word, 1)])
visited = {begin_word}
while queue:
word, steps = queue.popleft()
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
new_word = word[:i] + c + word[i+1:]
if new_word == end_word:
return steps + 1
if new_word in word_set and new_word not in visited:
visited.add(new_word)
queue.append((new_word, steps + 1))
return 0
print(word_ladder('hit', 'cog', ['hot','dot','dog','lot','log','cog'])) # 5Percurso por Níveis: Rastreando a Distância
O percurso por níveis agrupa os nós pela distância até a origem, o que é diretamente útil para problemas que exigem processamento por nível. Rastreie a distância armazenando-a no elemento da fila como uma tupla (node, dist) ou usando a técnica do tamanho da fila (registre o tamanho da fila antes de cada nível, processe exatamente essa quantidade de nós e depois incremente um contador de níveis). As duas abordagens produzem resultados idênticos.
from collections import deque, defaultdict
def bfs_levels(graph, start):
levels = {}
visited = {start}
queue = deque([start])
dist = 0
while queue:
# Process all nodes at current distance
for _ in range(len(queue)):
node = queue.popleft()
levels[node] = dist
for nb in graph[node]:
if nb not in visited:
visited.add(nb)
queue.append(nb)
dist += 1
return levels
graph = defaultdict(list)
for u, v in [(0,1),(0,2),(1,3),(2,3),(3,4)]:
graph[u].append(v); graph[v].append(u)
print(bfs_levels(graph, 0)) # {0:0, 1:1, 2:1, 3:2, 4:3}Clonar Grafo
Clonar Grafo (LeetCode #133) cria uma cópia profunda de um grafo não direcionado conectado. Use BFS e um mapa de dispersão que associe os nós originais às suas cópias. Quando visitar um nó pela primeira vez, crie sua cópia e adicione-a ao mapa. Ao processar os vizinhos, procure as cópias deles no mapa ou crie-as e conecte as arestas. O mapa de dispersão tem uma dupla finalidade: acompanhar os nós visitados e associar os originais às cópias.
from collections import deque
class Node:
def __init__(self, val=0, neighbors=None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
def clone_graph(node):
if not node:
return None
old_to_new = {node: Node(node.val)}
queue = deque([node])
while queue:
curr = queue.popleft()
for nb in curr.neighbors:
if nb not in old_to_new:
old_to_new[nb] = Node(nb.val)
queue.append(nb)
old_to_new[curr].neighbors.append(old_to_new[nb])
return old_to_new[node]
# Build a simple graph: 1 -- 2 -- 3 -- 4 -- 1
n1 = Node(1); n2 = Node(2); n3 = Node(3); n4 = Node(4)
n1.neighbors = [n2, n4]; n2.neighbors = [n1, n3]
n3.neighbors = [n2, n4]; n4.neighbors = [n3, n1]
cloned = clone_graph(n1)
print(cloned.val, [n.val for n in cloned.neighbors]) # 1 [2, 4]BFS Bidirecional
O BFS bidirecional começa o BFS simultaneamente na origem e no destino, expandindo um nível de cada vez a partir de cada extremidade. Quando as duas frentes de busca se encontram, você encontrou o caminho mínimo. Em grafos grandes, isso reduz o espaço de busca de O(b^d) para O(2 * b^(d/2)), em que b é o fator de ramificação e d é o comprimento do caminho — uma melhoria significativa para grafos com alta conectividade, como o problema da escada de palavras com dicionários grandes.
from collections import defaultdict
def word_ladder_bidir(begin, end, word_list):
word_set = set(word_list)
if end not in word_set:
return 0
front, back = {begin}, {end}
visited = {begin, end}
steps = 1
while front and back:
# Always expand the smaller frontier
if len(front) > len(back):
front, back = back, front
next_front = set()
for word in front:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
nw = word[:i] + c + word[i+1:]
if nw in back: # frontiers met!
return steps + 1
if nw in word_set and nw not in visited:
visited.add(nw)
next_front.add(nw)
front = next_front
steps += 1
return 0
print(word_ladder_bidir('hit','cog',['hot','dot','dog','lot','log','cog'])) # 5BFS 0–1 para Grafos Ponderados
BFS 0–1 lida com grafos cujos pesos de arestas são apenas 0 ou 1. Em vez de uma fila comum, use uma fila de duas extremidades: use append no final para arestas com peso 1 (próximo nível) e no início para arestas com peso 0 (mesmo nível). Isso permite calcular caminhos mínimos em O(V + E), mais rapidamente que o O((V+E) log V) do algoritmo de Dijkstra quando os pesos são binários. É comum em problemas com grades nos quais alguns movimentos são gratuitos e outros custam 1.
from collections import deque
def zero_one_bfs(graph, start, n):
# graph: list of (neighbour, weight) where weight is 0 or 1
dist = [float('inf')] * n
dist[start] = 0
dq = deque([start])
while dq:
node = dq.popleft()
for nb, w in graph[node]:
if dist[node] + w < dist[nb]:
dist[nb] = dist[node] + w
if w == 0:
dq.appendleft(nb) # same level
else:
dq.append(nb) # next level
return dist
# Simple test:
graph = [[(1, 0), (2, 1)], # node 0: free to 1, cost 1 to 2
[(3, 1)], # node 1: cost 1 to 3
[(3, 0)], # node 2: free to 3
[]]
print(zero_one_bfs(graph, 0, 4)) # [0, 0, 1, 1]Paredes e Portões (BFS com Múltiplas Origens)
Paredes e Portões preenche cada sala vazia com a distância até o portão mais próximo. Use BFS com múltiplas origens: inicialize simultaneamente a fila com todos os portões (valor 0) e expanda para fora. O valor de cada célula é definido como o nível em que ela é alcançada pela primeira vez. Essa solução O(mn) é mais eficiente que executar o BFS separadamente a partir de cada sala vazia, o que teria complexidade O(m²n²).
from collections import deque
def walls_and_gates(rooms):
if not rooms:
return
rows, cols = len(rooms), len(rooms[0])
INF = float('inf')
queue = deque()
# Multi-source: all gates at distance 0
for r in range(rows):
for c in range(cols):
if rooms[r][c] == 0: # gate
queue.append((r, c))
dirs = [(0,1),(0,-1),(1,0),(-1,0)]
while queue:
r, c = queue.popleft()
for dr, dc in dirs:
nr, nc = r+dr, c+dc
if 0<=nr<rows and 0<=nc<cols and rooms[nr][nc]==INF:
rooms[nr][nc] = rooms[r][c] + 1
queue.append((nr, nc))
rooms = [[float('inf'),-1,0,float('inf')],
[float('inf'),float('inf'),float('inf'),-1],
[float('inf'),-1,float('inf'),-1],
[0,-1,float('inf'),float('inf')]]
walls_and_gates(rooms)
print(rooms[0][0], rooms[1][1]) # 3, 2BFS para Cobras e Escadas
Cobras e Escadas (LeetCode #909) é um problema de caminho mínimo com BFS em uma grade numerada. Modele o tabuleiro como um grafo não ponderado no qual você pode avançar de 1 a 6 casas a partir de qualquer casa e talvez cair em uma cobra ou escada que o transporta para outra posição. O BFS encontra o número mínimo de lançamentos de dado. O principal desafio é converter entre a posição 1D e as coordenadas 2D do tabuleiro, levando em conta a disposição em bustrofédon (com direção alternada das linhas).
from collections import deque
def snakes_and_ladders(board):
n = len(board)
def get_board(pos):
r, c = divmod(pos - 1, n)
if r % 2 == 1: c = n - 1 - c # alternating direction
return board[n - 1 - r][c]
visited = {1}
queue = deque([(1, 0)])
while queue:
pos, moves = queue.popleft()
for dice in range(1, 7):
next_pos = pos + dice
if next_pos > n * n:
break
val = get_board(next_pos)
if val != -1:
next_pos = val # snake or ladder
if next_pos == n * n:
return moves + 1
if next_pos not in visited:
visited.add(next_pos)
queue.append((next_pos, moves + 1))
return -1
print('BFS models game as an unweighted shortest-path problem')Complexidade e Otimizações do BFS
A complexidade de tempo do BFS é O(V + E), porque cada vértice é enfileirado uma vez e cada aresta é examinada um número constante de vezes. A complexidade de espaço é O(V) para o conjunto de visitados e a fila. Para grafos em grades, V = m*n e E = 4*m*n (cada célula tem 4 vizinhos); portanto, o BFS em uma grade é O(mn). Otimização importante: use um conjunto para os visitados (consulta em O(1)), não uma lista (consulta em O(n)). Marque os nós como visitados ao enfileirá-los, não ao retirá-los da fila.
# BFS on a graph with V vertices and E edges:
# Time: O(V + E) -- each vertex and edge visited once
# Space: O(V) -- visited set + queue
# BFS on an m x n grid:
# V = m*n cells
# E <= 4*m*n edges (4 directions, max)
# Time: O(m*n)
# Space: O(m*n)
# Common pitfalls:
# 1. Marking visited on dequeue (not enqueue) -> same node queued multiple times
# 2. Using a list for visited -> O(n) membership check -> O(V*E) total
# 3. Not handling disconnected graph -> BFS from single source misses components
print('O(V+E) time, O(V) space -- mark visited on enqueue')Zero Mais Próximo em Matriz Binária
Matriz 01 (LeetCode #542) encontra a distância de cada célula até o 0 mais próximo. O BFS com múltiplas origens, iniciado simultaneamente a partir de todos os zeros, fornece a solução ideal em O(mn). Inicialize a fila com todas as células que contêm 0, com distância 0, e todas as células que contêm 1, com distância infinita. O BFS propaga as distâncias para fora a partir dos zeros, definindo a distância de cada célula com 1 na primeira vez que ela é alcançada (com a garantia de que será a menor distância).
from collections import deque
def update_matrix(mat):
rows, cols = len(mat), len(mat[0])
dist = [[float('inf')] * cols for _ in range(rows)]
queue = deque()
for r in range(rows):
for c in range(cols):
if mat[r][c] == 0:
dist[r][c] = 0
queue.append((r, c))
dirs = [(0,1),(0,-1),(1,0),(-1,0)]
while queue:
r, c = queue.popleft()
for dr, dc in dirs:
nr, nc = r+dr, c+dc
if 0<=nr<rows and 0<=nc<cols:
if dist[r][c] + 1 < dist[nr][nc]:
dist[nr][nc] = dist[r][c] + 1
queue.append((nr, nc))
return dist
mat = [[0,0,0],[0,1,0],[1,1,1]]
result = update_matrix(mat)
for row in result: print(row) # [[0,0,0],[0,1,0],[1,2,1]]Verificação Rápida
Avalie sua compreensão dos conceitos de Estruturas de Dados e Algoritmos — Preparação para Entrevistas de Programação apresentados nesta lição.
Resumo da Lição
Nesta lição, você aprendeu: BFS para caminhos mínimos em grafos não ponderados, com rastreamento de predecessores para reconstruir o caminho; a escada de palavras como um exemplo clássico de BFS em um grafo implícito; o BFS bidirecional para grafos grandes; e o BFS com múltiplas origens para problemas com vários pontos de partida. A seguir, aplicaremos DFS a componentes conectados e ao preenchimento por inundação.
Perguntas Frequentes
A aula “BFS: Menor Caminho e Percurso por Níveis” é grátis?
Sim — o texto completo de “BFS: Menor Caminho e Percurso por Níveis” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de DSA Interview Prep, atualize para CoddyKit PRO. O curso de DSA Interview Prep inclui 4 aulas no total.
O que vou aprender em “BFS: Menor Caminho e Percurso por Níveis”?
Use BFS para encontrar o menor caminho em um grafo não ponderado, resolva word-ladder nível a nível e clone um grafo usando um mapa hash. Você pratica DSA Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar DSA Interview Prep?
Nenhuma experiência prévia é necessária. DSA Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “BFS: Menor Caminho e Percurso por Níveis”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de DSA Interview Prep?
Sim. Cada aula de DSA Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Representações de Grafos e Configuração de Percursos
- BFS: Menor Caminho e Percurso por Níveis
- DFS: Componentes Conectados e Preenchimento por Inundação
- Detecção de Ciclos em Grafos Direcionados e Não Direcionados