Algoritmo di Dijkstra con coda di priorità
Implementi Dijkstra usando heapq, segua i passaggi di rilassamento su un grafo pesato e risolva il problema cheapest-flights-within-k-stops.
Algoritmo di Dijkstra con coda di priorità è una lezione Coding Interview Prep gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Coding Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Coding Interview Prep include 4 lezioni in totale.
Cammino minimo nei grafi pesati
L'algoritmo di Dijkstra trova il cammino minimo da un singolo nodo sorgente a tutti gli altri nodi di un grafo pesato con pesi degli archi non negativi. Funziona elaborando avidamente i nodi in ordine di distanza attualmente migliore: espande sempre il nodo non visitato più vicino. La struttura dati fondamentale è un min-heap (coda con priorità), che recupera in modo efficiente il nodo con la distanza più piccola.
Panoramica dei passaggi dell'algoritmo
Algoritmo di Dijkstra: (1) Inizializzi dist[source] = 0 e dist[all others] = inf. (2) Inserisca (0, source) in un min-heap. (3) Estragga il nodo u con la distanza più piccola. Se è già stato visitato con una distanza minore, lo ignori. (4) Per ogni vicino v di u: se dist[u] + weight(u,v) < dist[v], aggiorni dist[v] e inserisca (dist[v], v) nell'heap. (5) Ripeta finché l'heap non è vuoto.
Implementazione Python con heapq
Il modulo Python heapq implementa un min-heap. Rappresentiamo il grafo come una lista di adiacenza: graph[u] = [(v, weight), ...]. L'heap memorizza tuple (distance, node). Utilizziamo un set visited per ignorare le voci obsolete dell'heap, cioè quelle inserite prima che venisse trovato un cammino migliore.
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 distEsempio svolto
Consideri un grafo con 5 nodi e gli archi: 0→1 (4), 0→2 (1), 2→1 (2), 1→3 (1), 2→3 (5), 3→4 (3). I cammini minimi dal nodo 0 sono: verso 1 passando per 0→2→1, con costo 3; verso 2, con costo 1; verso 3 passando per 0→2→1→3, con costo 4; verso 4 passando per 0→2→1→3→4, con costo 7. Dijkstra trova tutti questi cammini in un'unica esecuzione, non solo il cammino verso una singola destinazione.
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]Perché Dijkstra non funziona con i pesi negativi
La correttezza di Dijkstra si basa sul fatto che, una volta estratto un nodo dal min-heap, la sua distanza è definitiva. Questo vale solo se i pesi degli archi sono non negativi. Con un arco negativo u→v di peso -5, dopo aver visitato v potremmo trovare un cammino che passa per u e che è più breve, ma v è già contrassegnato come visitato. Un singolo arco negativo può invalidare tutti i calcoli delle distanze successivi.
Voli più economici con al massimo K scali (LeetCode 787)
Questo problema aggiunge un vincolo: al massimo k scali. Dijkstra standard non gestisce nativamente il conteggio dei passaggi. Soluzione: estendere lo stato a (cost, node, stops_remaining). Si può usare Dijkstra con questa 3-upla, oppure Bellman-Ford con k+1 passaggi di rilassamento. La versione modificata di Dijkstra si arresta quando stops_remaining raggiunge 0, impedendo ulteriori passaggi.
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)) # 200Analisi della complessità temporale
Con un heap binario, Dijkstra richiede O((V + E) log V) di tempo: ogni vertice viene estratto una volta (V estrazioni), ogni arco può causare un inserimento (E inserimenti) e ogni operazione sull'heap costa O(log V). Con un heap di Fibonacci, il limite migliora a O(E + V log V), ma heapq di Python è un heap binario. Per i grafi sparsi (E ≈ V), la versione con heap binario è O(V log V); per i grafi densi (E ≈ V²) è O(V² log V).
Ricostruzione del cammino minimo
Per recuperare il cammino effettivo, non solo le distanze, si mantenga un array prev: quando si aggiorna dist[v], si imposti prev[v] = u. Al termine dell'algoritmo, si ricostruisca il cammino dalla sorgente alla destinazione procedendo a ritroso: si inizi da dst, si seguano i puntatori prev fino a source e si inverta il risultato.
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]Uso di un dict per i grafi sparsi
Quando i nodi sono stringhe o interi non contigui, si usi un defaultdict(list) per la lista di adiacenza e un normale dict per le distanze. Questo è comune nei problemi di LeetCode, come Network Delay Time, in cui i nodi sono etichettati da 1 a n. Si ricordi di usare dist = {node: inf for node in all_nodes} e di controllare i nodi irraggiungibili dopo l'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)) # 2Confronto con BFS per i grafi non pesati
Per i grafi non pesati, BFS trova i cammini minimi in O(V + E), più rapidamente rispetto a Dijkstra, che richiede O((V+E) log V). Dijkstra generalizza BFS ai grafi pesati usando una coda con priorità invece di una normale coda FIFO. Quando tutti i pesi degli archi sono uguali, Dijkstra si riduce a BFS. Si scelga BFS per i grafi non pesati, Dijkstra per i pesi non negativi e Bellman-Ford per i pesi negativi.
Dijkstra con ottimizzazione decrease-key
La versione di Dijkstra presentata nei manuali usa una coda con priorità dotata di decrease-key: quando la distanza di un nodo migliora, se ne aggiorna direttamente la priorità. Ciò richiede un heap di Fibonacci per ottenere O(E + V log V), ma è difficile da implementare. L'approccio della lazy deletion usato nei colloqui inserisce invece una nuova voce e ignora le estrazioni obsolete: è più semplice e comporta solo un sovraccarico di un fattore costante. In Python, la lazy deletion con heapq è l'implementazione standard nei colloqui tecnici.
Verifica rapida
Verifichi la propria comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep presentati in questa lezione.
Riepilogo della lezione
In questa lezione ha imparato che: Dijkstra usa un min-heap per elaborare i nodi secondo l'ordine della migliore distanza corrente, richiede O((V+E) log V) di tempo e non funziona con gli archi dal peso negativo e le voci obsolete dell'heap vengono gestite controllando un insieme visited al momento dell'estrazione. Nel prossimo argomento tratteremo Bellman-Ford, che gestisce i pesi negativi tramite n-1 passaggi di rilassamento.
Domande Frequenti
La lezione «Algoritmo di Dijkstra con coda di priorità» è gratuita?
Sì — il testo completo di «Algoritmo di Dijkstra con coda di priorità» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Coding Interview Prep, passa a CoddyKit PRO. Il corso Coding Interview Prep include 4 lezioni in totale.
Cosa imparerò in «Algoritmo di Dijkstra con coda di priorità»?
Implementi Dijkstra usando heapq, segua i passaggi di rilassamento su un grafo pesato e risolva il problema cheapest-flights-within-k-stops. Eserciti Coding Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Coding Interview Prep?
Non è richiesta alcuna esperienza precedente. Coding Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.
Quanto tempo richiede la lezione «Algoritmo di Dijkstra con coda di priorità»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Coding Interview Prep?
Sì. Ogni lezione Coding Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Algoritmo di Dijkstra con coda di priorità
- Bellman-Ford e cicli negativi
- Floyd-Warshall: cammini minimi tra tutte le coppie
- Network Delay Time e ricostruzione del percorso