Algoritmo de Dijkstra com fila de prioridades
Implemente Dijkstra usando heapq, percorra as etapas de relaxamento em um grafo ponderado e resolva o problema dos voos mais baratos com no máximo k escalas.
Algoritmo de Dijkstra com fila de prioridades é uma aula grátis de DSA Interview Prep no CoddyKit. Esta é a aula 1 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.
Caminho Mais Curto em Grafos Ponderados
O algoritmo de Dijkstra encontra o caminho mais curto de um único nó de origem até todos os outros nós em um grafo ponderado com pesos de arestas não negativos. Ele funciona processando os nós de forma gulosa, na ordem de sua melhor distância conhecida — sempre expandindo o nó não visitado mais próximo. A estrutura de dados fundamental é um heap mínimo (fila de prioridade), que recupera com eficiência o nó com a menor distância.
Visão Geral das Etapas do Algoritmo
Algoritmo de Dijkstra: (1) Inicialize dist[source] = 0 e dist[all others] = inf. (2) Insira (0, source) em um heap mínimo. (3) Remova o nó u com a menor distância. Se ele já tiver sido visitado com uma distância menor, ignore-o. (4) Para cada vizinho v de u: se dist[u] + weight(u,v) < dist[v], atualize dist[v] e insira (dist[v], v) no heap. (5) Repita até o heap ficar vazio.
Implementação em Python com heapq
O heapq do Python implementa um heap mínimo. Representamos o grafo como uma lista de adjacência: graph[u] = [(v, weight), ...]. O heap armazena tuplas (distance, node). Usamos um conjunto visited para ignorar entradas obsoletas do heap — entradas inseridas antes de uma rota melhor ser encontrada.
import heapq
def dijkstra(graph, source):
n = len(graph)
dist = [float('inf')] * n
dist[source] = 0
heap = [(0, source)] # (distance, node)
visited = set()
while heap:
d, u = heapq.heappop(heap)
if u in visited:
continue
visited.add(u)
for v, weight in graph[u]:
if dist[u] + weight < dist[v]:
dist[v] = dist[u] + weight
heapq.heappush(heap, (dist[v], v))
return distExemplo Resolvido
Considere um grafo com 5 nós e arestas: 0→1 (4), 0→2 (1), 2→1 (2), 1→3 (1), 2→3 (5), 3→4 (3). Os caminhos mais curtos a partir do nó 0 são: até 1, passando por 0→2→1, com custo 3; até 2, com custo 1; até 3, passando por 0→2→1→3, com custo 4; até 4, passando por 0→2→1→3→4, com custo 7. Dijkstra encontra todos esses caminhos em uma única passagem, não apenas o caminho até um único alvo.
import heapq
def dijkstra(graph, source):
dist = [float('inf')] * len(graph)
dist[source] = 0
heap = [(0, source)]
visited = set()
while heap:
d, u = heapq.heappop(heap)
if u in visited:
continue
visited.add(u)
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
return dist
graph = [
[(1,4),(2,1)], # 0
[(3,1)], # 1
[(1,2),(3,5)], # 2
[(4,3)], # 3
[] # 4
]
print(dijkstra(graph, 0)) # [0, 3, 1, 4, 7]Por que Dijkstra falha com pesos negativos
A correção de Dijkstra depende do fato de que, assim que um nó é removido do heap mínimo, sua distância é definitiva. Isso só é válido quando os pesos das arestas não são negativos. Com uma aresta negativa u→v de peso -5, depois de visitar v, podemos encontrar um caminho passando por u que seja mais curto — mas v já está marcado como visitado. Uma única aresta negativa pode invalidar todos os cálculos de distância subsequentes.
Voos mais baratos com no máximo K escalas (LeetCode 787)
Este problema acrescenta uma restrição: no máximo k escalas. O Dijkstra padrão não trata a contagem de etapas nativamente. Solução: amplie o estado para (cost, node, stops_remaining). Use Dijkstra com essa 3-tupla ou use Bellman-Ford com k+1 passagens de relaxamento. O Dijkstra modificado para quando stops_remaining chega a 0, impedindo novos saltos.
import heapq
from collections import defaultdict
def findCheapestPrice(n, flights, src, dst, k):
graph = defaultdict(list)
for u, v, w in flights:
graph[u].append((v, w))
heap = [(0, src, k + 1)] # (cost, node, hops_left)
visited = {} # node -> min hops_left seen at this cost level
while heap:
cost, node, hops = heapq.heappop(heap)
if node == dst:
return cost
if hops == 0:
continue
if visited.get(node, 0) >= hops:
continue
visited[node] = hops
for nxt, w in graph[node]:
heapq.heappush(heap, (cost + w, nxt, hops - 1))
return -1
print(findCheapestPrice(4,[[0,1,100],[1,2,100],[0,2,500]],0,2,1)) # 200Análise da complexidade temporal
Com um heap binário, Dijkstra é executado em O((V + E) log V) de tempo: cada vértice é removido uma vez (V remoções), cada aresta pode provocar uma inserção (E inserções) e cada operação no heap custa O(log V). Com um heap de Fibonacci, o limite melhora para O(E + V log V), mas o heapq do Python é um heap binário. Para grafos esparsos (E ≈ V), a versão com heap binário é O(V log V); para grafos densos (E ≈ V²), ela é O(V² log V).
Reconstrução do caminho mais curto
Para recuperar o caminho real (não apenas as distâncias), mantenha um vetor prev: ao atualizar dist[v], defina prev[v] = u. Depois que o algoritmo terminar, reconstrua o caminho da origem ao destino percorrendo-o de trás para frente: comece em dst, siga os ponteiros de prev até source e inverta o resultado.
import heapq
def dijkstra_path(graph, source, target):
n = len(graph)
dist = [float('inf')] * n
prev = [-1] * n
dist[source] = 0
heap = [(0, source)]
visited = set()
while heap:
d, u = heapq.heappop(heap)
if u in visited: continue
visited.add(u)
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
prev[v] = u
heapq.heappush(heap, (dist[v], v))
# Reconstruct
path, node = [], target
while node != -1:
path.append(node)
node = prev[node]
return dist[target], path[::-1]Usando um dicionário para grafos esparsos
Quando os nós são strings ou inteiros não consecutivos, use um defaultdict(list) para a lista de adjacência e um dict comum para as distâncias. Isso é comum em problemas do LeetCode, como o de tempo de atraso da rede, em que os nós são identificados pelos números de 1 a n. Lembre-se de usar dist = {node: inf for node in all_nodes} e verificar os nós inalcançáveis depois do algoritmo.
import heapq
from collections import defaultdict
def networkDelayTime(times, n, k):
graph = defaultdict(list)
for u, v, w in times:
graph[u].append((v, w))
dist = {i: float('inf') for i in range(1, n+1)}
dist[k] = 0
heap = [(0, k)]
while heap:
d, u = heapq.heappop(heap)
if d > dist[u]: continue
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
ans = max(dist.values())
return ans if ans < float('inf') else -1
print(networkDelayTime([[2,1,1],[2,3,1],[3,4,1]], 4, 2)) # 2Comparação com BFS para grafos não ponderados
Para grafos não ponderados, BFS encontra os caminhos mais curtos em O(V + E) — mais rapidamente que o Dijkstra, com O((V+E) log V). Dijkstra generaliza BFS para grafos ponderados usando uma fila de prioridade em vez de uma fila comum FIFO. Quando todos os pesos das arestas são iguais, Dijkstra se reduz a BFS. Escolha BFS para grafos não ponderados, Dijkstra para pesos não negativos e Bellman-Ford para pesos negativos.
Dijkstra com otimização de redução da chave
O Dijkstra dos livros didáticos usa uma fila de prioridade com redução da chave: quando a distância de um nó melhora, atualiza sua prioridade no próprio local. Isso exige um heap de Fibonacci para obter O(E + V log V), mas é difícil de implementar. A abordagem de remoção preguiçosa usada em entrevistas insere uma nova entrada e ignora as remoções obsoletas — é mais simples, com apenas um custo adicional constante. Em Python, a remoção preguiçosa com heapq é a implementação padrão em entrevistas.
Verificação rápida
Verifique sua compreensão dos conceitos de Estruturas de Dados e Algoritmos — Preparação para Entrevistas de Programação apresentados nesta lição.
Recapitulação da lição
Nesta lição, você aprendeu que: Dijkstra usa um heap mínimo para processar avidamente os nós na ordem da melhor distância atual, é executado em O((V+E) log V) de tempo e falha com arestas de peso negativo e as entradas obsoletas do heap são tratadas verificando um conjunto de visitados ao removê-las. Em seguida, veremos Bellman-Ford, que trata pesos negativos por meio de n-1 passagens de relaxamento.
Perguntas Frequentes
A aula “Algoritmo de Dijkstra com fila de prioridades” é grátis?
Sim — o texto completo de “Algoritmo de Dijkstra com fila de prioridades” é 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 “Algoritmo de Dijkstra com fila de prioridades”?
Implemente Dijkstra usando heapq, percorra as etapas de relaxamento em um grafo ponderado e resolva o problema dos voos mais baratos com no máximo k escalas. 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 1 de 4.
Quanto tempo leva a aula “Algoritmo de Dijkstra com fila de prioridades”?
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
- Algoritmo de Dijkstra com fila de prioridades
- Bellman-Ford e ciclos negativos
- Floyd-Warshall: caminhos mínimos entre todos os pares
- Tempo de atraso da rede e reconstrução de caminhos