Pular para o conteúdo
~/.primo-academy.sh
☰ Aulas · Estruturas de Dados · 0/14
Recomendado: essencial

Travessia de Grafos: BFS e DFS na Prática

3 min de leitura

fonte

BFS e DFS são os dois algoritmos fundamentais pra percorrer todos os vértices de um grafo. A diferença é a ordem: BFS visita por "camada" (todos os vizinhos antes dos vizinhos dos vizinhos), DFS visita "mergulhando" (vai fundo, volta, vai pra outro lado). Cada um destrava problemas diferentes.

O essencial 🟢

BFS (Breadth-First Search / Busca em Largura): usa fila. Visita o vértice inicial, depois todos os vizinhos dele, depois todos os vizinhos dos vizinhos, e assim por diante. Garante o caminho mais curto em grafo não ponderado.

from collections import deque

def bfs(grafo, inicio):
    visitados = {inicio}
    fila = deque([inicio])
    ordem = []  # ordem de visita

    while fila:
        v = fila.popleft()
        ordem.append(v)
        for vizinho in grafo[v]:
            if vizinho not in visitados:
                visitados.add(vizinho)
                fila.append(vizinho)
    return ordem

DFS (Depth-First Search / Busca em Profundidade): usa pilha (ou recursão - a pilha de chamadas). Vai fundo em um caminho até não dar mais, volta e tenta outro.

def dfs(grafo, v, visitados=None):
    if visitados is None:
        visitados = set()
    visitados.add(v)
    print(v)  # visita
    for vizinho in grafo[v]:
        if vizinho not in visitados:
            dfs(grafo, vizinho, visitados)
BFS a partir de A visita: A, B, C, D, E, F (nível por nível). DFS visita: A, B, D, E, C, F (vai fundo, volta, vai pro próximo caminho).

Complexidade de ambos: O(V + E) - você visita cada vértice e cada aresta uma vez. O que muda é só a ordem e a estrutura auxiliar usada (fila vs pilha).

Aplicações clássicas de BFS:

  • Caminho mais curto em grafo não ponderado.
  • Nível de cada nó (distância em saltos do inicial).
  • Detectar bipartição (coloração em 2 cores).
  • Componentes conectados (cada BFS é uma componente).
  • Spreading em redes (epidemias, viralização, network broadcast).
  • Menor número de movimentos (quebra-cabeças, labirintos).

Aplicações clássicas de DFS:

  • Detectar ciclos (aresta "back" = ciclo).
  • Ordenação topológica (em DAG).
  • Componentes fortemente conectados (Kosaraju, Tarjan).
  • Pontes e articulações (arestas/nós que desconectam o grafo).
  • Sudoku, N-rainhas, geração de labirintos (backtracking).
  • Tree traversal (que é um caso especial de DFS em árvore).

Aprofundamento 🟡

BFS com reconstrução de caminho: guarde o pai de cada nó (quem colocou ele na fila) e reconstrua o caminho voltando do fim ao início.

def bfs_com_caminho(grafo, inicio, fim):
    if inicio == fim: return [inicio]
    pais = {inicio: None}
    fila = deque([inicio])
    while fila:
        v = fila.popleft()
        if v == fim:
            # Reconstrói caminho
            caminho = []
            while v is not None:
                caminho.append(v)
                v = pais[v]
            return caminho[::-1]
        for vizinho in grafo[v]:
            if vizinho not in pais:
                pais[vizinho] = v
                fila.append(vizinho)
    return None  # não há caminho

DFS com detecção de ciclo (grafos não direcionados):

def tem_ciclo(grafo):
    visitados = set()
    def dfs(v, pai):
        visitados.add(v)
        for vizinho in grafo[v]:
            if vizinho not in visitados:
                if dfs(vizinho, v): return True
            elif vizinho != pai:  # back edge = ciclo
                return True
        return False
    for v in grafo:  # cobre grafo desconexo
        if v not in visitados:
            if dfs(v, None): return True
    return False

Ordenação topológica (DAG): use DFS, adicione o nó à lista quando terminar de processar (pós-ordem), depois inverta. Funciona porque em DAG, todo nó depende apenas dos processados depois dele.

def ordenacao_topologica(grafo):
    visitados = set()
    ordem = []
    def dfs(v):
        visitados.add(v)
        for vizinho in grafo[v]:
            if vizinho not in visitados:
                dfs(vizinho)
        ordem.append(v)  # pós-ordem
    for v in grafo:
        if v not in visitados:
            dfs(v)
    return ordem[::-1]

Complexidade espacial: BFS guarda a fila (pode ser até O(V)). DFS recursivo usa a pilha de chamadas (pode ser até O(V) em lista ligada). Em grafos muito profundos, prefira DFS iterativo (com pilha explícita) pra não estourar a call stack.

Iterativo vs recursivo: BFS é iterativo por natureza (fila). DFS pode ser recursivo (elegante) ou iterativo (com pilha explícita). Pra grafos muito grandes, iterativo é mais seguro.

Pra quem quer ir além 🔴

Busca bidirecional: pra "caminho de A até B" em grafo grande, faça BFS de A e de B simultaneamente, e pare quando as duas buscas se encontram. Na prática, é 2× mais rápido que BFS só de A (em grafos com fator de ramificação alto).

Algoritmo de Kosaraju: encontra componentes fortemente conectados (SCC) em O(V + E). Idéia: roda DFS, guarda ordem de finalização, transpõe o grafo, roda DFS de novo na ordem inversa. Cada árvore do segundo DFS é uma SCC.

Algoritmo de Tarjan: mesma coisa, em uma única passada de DFS, usando uma pilha. Mais elegante e eficiente em constantes, mas mais difícil de implementar corretamente.

Busca A* (A-star): generalização de BFS pra grafos ponderados, com heurística que guia a busca. Usado em GPS (menor rota entre duas cidades), pathfinding em jogos (menor caminho entre dois pontos em grid), e navegação em robótica. Heurística boa = busca muito mais rápida. Heurística ruim = degenera em Dijkstra.

Leitura recomendada:

  • Capítulo 22 do Introduction to Algorithms (CLRS) - BFS e DFS com provas de correção.
  • Capítulo 4 do Algorithms (Dasgupta et al., online) - versão mais didática, com exemplos visuais.

Dica: BFS pra "menor caminho em grafo não ponderado" e "ordem por nível". DFS pra "detectar ciclo", "ordem topológica", "backtracking" e "explorar tudo". Quando o grafo é ponderado, vai pra Dijkstra (próximo nó).

No próximo nó, vamos ver os algoritmos clássicos de grafos ponderados: Dijkstra (caminho mínimo), MST (árvore geradora mínima) e ordenação topológica (já vimos DFS, agora com pesos).

// Quiz

Você precisa achar o menor caminho (em número de saltos) entre dois usuários de uma rede social. Qual algoritmo?

Escolha uma alternativa

// recursos

// avaliação da trilha

—
ainda sem avaliações