Trilha 03 · Algoritmos e complexidade

Grafos e travessia BFS/DFS

Grafos são a estrutura de dados mais versátil da computação — modelam redes sociais, mapas, dependências de pacotes, páginas da web e muito mais. Para percorrer um grafo existem dois algoritmos fundamentais: BFS (largura, nível por nível) e DFS (profundidade, vai fundo antes de voltar). Cada um é ideal para problemas diferentes.

① Intuição

Explorar um labirinto: duas estratégias

Imagine explorar um labirinto. A estratégia BFS (Breadth-First Search) é como jogar pedras: você espalha pedras em todas as direções ao mesmo tempo, atingindo primeiro os corredores mais próximos da entrada, depois os mais distantes. O caminho mais curto até a saída é garantido pelo BFS.

A estratégia DFS (Depth-First Search) é como rolar um fio: você escolhe um corredor, vai até o fundo, volta se não há saída, e tenta o próximo. Você pode demorar mais para achar a saída, mas explora caminhos completos antes de mudar de rota — ideal para detectar se a saída existe de alguma forma.

Complexidade: ambos são O(V + E) onde V = vértices e E = arestas. Em grafos esparsos (poucas arestas), E ≈ V e é quase O(V). Em grafos densos, E ≈ V² e é O(V²). A diferença entre BFS e DFS não é de complexidade — é de quais problemas cada um resolve bem.
② Visualização interativa

BFS vs DFS no mesmo grafo

Alterne entre BFS e DFS e avance passo a passo. Observe como a ordem de visita muda completamente — BFS vai nível por nível, DFS vai fundo antes de voltar.

ABCDEFG
Fila (fronteira)
A
Visitados
A
visitado atual na fronteira
Início: colocamos A na fila.
1 / 16
③ Explicação técnica

Representação e BFS

# Representações de grafo em Python

# 1. Lista de adjacência (mais comum — eficiente para grafos esparsos)
grafo = {
    "A": ["B", "C"],
    "B": ["D", "E"],
    "C": ["F", "G"],
    "D": [], "E": [], "F": [], "G": []
}

# 2. Matriz de adjacência (útil para grafos densos e Floyd-Warshall)
# grafo[i][j] = 1 se há aresta de i para j, 0 caso contrário
# Custo: O(n²) espaço — ruim para grafos grandes e esparsos

# BFS — Busca em Largura — O(V + E)
from collections import deque

def bfs(grafo, inicio):
    visitados = {inicio}
    fila = deque([inicio])
    ordem = []
    while fila:
        no = fila.popleft()    # desenfila da frente
        ordem.append(no)
        for vizinho in grafo[no]:
            if vizinho not in visitados:
                visitados.add(vizinho)
                fila.append(vizinho)
    return ordem           # A B C D E F G (nível por nível)

DFS e quando usar cada um

# DFS — Busca em Profundidade — O(V + E)

# Versão recursiva (mais elegante)
def dfs(grafo, no, visitados=None):
    if visitados is None: visitados = set()
    visitados.add(no)
    resultado = [no]
    for vizinho in grafo[no]:
        if vizinho not in visitados:
            resultado += dfs(grafo, vizinho, visitados)
    return resultado       # A B D E C F G (vai fundo antes de voltar)

# Aplicações de BFS vs DFS:
#
# BFS — ideal para:
#   ✓ Caminho mais curto (grafo sem pesos)
#   ✓ Verificar conectividade por níveis
#   ✓ Web crawling (explorar por camadas)
#
# DFS — ideal para:
#   ✓ Detectar ciclos
#   ✓ Ordenação topológica
#   ✓ Explorar labirintos / paths completos
#   ✓ Componentes conectados
Grafos com pesos: BFS e DFS assumem que todas as arestas têm o mesmo custo. Para grafos ponderados (distâncias, tempos, custos), use Dijkstra (arestas com pesos positivos, O((V+E) log V)) ou Bellman-Ford (suporta pesos negativos, O(VE)). Para grafos densos, Floyd-Warshall computa o caminho mínimo entre todos os pares em O(V³). O GPS do seu celular usa Dijkstra ou A* (uma variante heurística).
④ Projeto para programar

Grafos na prática

Mini projeto: implemente BFS para encontrar o caminho mais curto em um labirinto representado como uma grade 2D (lista de listas). Cada célula é "." (livre) ou "#" (parede). Retorne a menor sequência de movimentos do ponto S ao ponto E. Dica: trate cada célula como um vértice do grafo; as arestas são os 4 vizinhos cardeais.

Projeto principal: implemente o algoritmo de Dijkstra para encontrar o caminho mais curto em um grafo ponderado. Entrada: dicionário de adjacência com pesos {"A": [("B", 4), ("C", 2)]}. Use uma heap (heapq) para processar sempre o nó de menor custo acumulado. Teste com uma rede de cidades e calcule a rota mais barata entre pares.

Desafio extra: implemente detecção de ciclos usando DFS. Um grafo direcionado tem ciclo se, durante o DFS, você visita um nó que já está na pilha de chamadas ativa (não apenas visitado — visitado mas já removido da pilha é OK). Implique isso para verificar dependências circulares em um sistema de pacotes (como pip faz ao instalar dependências).

⑤ Exercícios rápidos

Teste sua intuição

Qual algoritmo garante o caminho mínimo (em número de arestas) entre dois nós?
Qual é a diferença estrutural entre BFS e DFS na implementação?
Qual é a complexidade de BFS e DFS em um grafo com V vértices e E arestas?
⑥ Aplicações no mundo real

Onde você encontra isso

📍

Google Maps e GPS — Dijkstra e A*

Calcular a rota mais curta entre cidades é um problema de grafo ponderado: vértices são cruzamentos, arestas são ruas com pesos = distância ou tempo. Google Maps usa variantes de A* (Dijkstra com heurística de distância euclidiana) para encontrar rotas rapidamente em grafos com bilhões de nós. Atualizações de trânsito em tempo real são mudanças nos pesos das arestas.

🕸️

PageRank — grafo da web

O algoritmo original do Google (PageRank) trata a web como um grafo dirigido: páginas são vértices, links são arestas. A importância de uma página depende de quantas outras páginas apontam para ela. O algoritmo itera DFS/BFS sobre esse grafo gigantesco. A web tem ~60 trilhões de links — o grafo de grafos que define a internet.

👥

Redes sociais — grafo de amigos

LinkedIn, Facebook e Twitter são grafos: usuários são vértices, conexões são arestas. BFS calcula "graus de separação" (você tem 2 conexões com fulano = BFS depth 2). Sugestões de amigos usam BFS: "amigos de amigos que você ainda não conhece". Detecção de comunidades usa DFS e algoritmos de componentes conectados para agrupar clusters.

← Anterior: Estruturas de dados ✓ Concluir trilha: Algoritmos →