0Pricing
DSA Interview Prep · Lezione

Floyd-Warshall: cammini minimi tra tutte le coppie

Riempa la matrice delle distanze tra tutte le coppie usando l'algoritmo Floyd-Warshall con tre cicli annidati e lo applichi per trovare il numero minimo di salti tra ogni coppia di nodi.

Floyd-Warshall: cammini minimi tra tutte le coppie è una lezione DSA Interview Prep gratuita su CoddyKit. Questa è la lezione 3 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 DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA Interview Prep include 4 lezioni in totale.

Cammini minimi tra tutte le coppie

Floyd-Warshall calcola i cammini minimi tra ogni coppia di nodi in un grafo pesato, inclusi i grafi con archi dal peso negativo, ma non quelli con cicli negativi. Eseguire Dijkstra da ogni sorgente richiede O(V × (V+E) log V); Floyd-Warshall richiede O(V³) indipendentemente dalla densità degli archi. Per i grafi densi con V ≤ 500, Floyd-Warshall è spesso più semplice e ha prestazioni comparabili.

L'idea fondamentale: i nodi intermedi

L'intuizione di Floyd-Warshall: dp[i][j][k] = cammino minimo da i a j usando solo i nodi {0, 1, ..., k} come intermediari. Il cammino minimo usa il nodo k come nodo intermedio oppure non lo usa. Se lo usa: dp[i][j][k] = dp[i][k][k-1] + dp[k][j][k-1]. Altrimenti: dp[i][j][k] = dp[i][j][k-1]. Poiché la terza dimensione procede solo in avanti, può essere eliminata: si aggiorna direttamente in memoria.

Inizializzazione della matrice delle distanze

Si inizi con una matrice V×V: dist[i][i] = 0 per la distanza da un nodo a sé stesso, dist[i][j] = weight per gli archi diretti e dist[i][j] = inf per le coppie non collegate da un arco. Si scorrano quindi tutti i nodi intermedi k, aggiornando le coppie (i, j). Il ciclo esterno su k deve venire per primo, così da costruire correttamente i cammini attraverso un insieme crescente di nodi intermedi consentiti.

def floyd_warshall(V, edges):
    INF = float('inf')
    dist = [[INF]*V for _ in range(V)]
    for i in range(V):
        dist[i][i] = 0
    for u, v, w in edges:
        dist[u][v] = w  # directed graph
    
    for k in range(V):       # intermediate node
        for i in range(V):
            for j in range(V):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]
    
    return dist

Implementazione completa con esempio

Si segua Floyd-Warshall su un grafo con 4 nodi. Dopo aver elaborato ciascun nodo intermedio k, la matrice si completa con cammini più brevi che passano per il nodo k. L'algoritmo gestisce naturalmente più passaggi costruendo progressivamente i cammini minimi.

def floyd_warshall(V, edges):
    INF = float('inf')
    dist = [[INF]*V for _ in range(V)]
    for i in range(V):
        dist[i][i] = 0
    for u, v, w in edges:
        dist[u][v] = w
    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][k] != INF and dist[k][j] != INF:
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
    return dist

V = 4
edges = [(0,1,3),(0,2,7),(1,2,1),(1,3,5),(2,3,2)]
dist = floyd_warshall(V, edges)
for row in dist:
    print([x if x != float('inf') else 'INF' for x in row])

Rilevamento dei cicli negativi

Dopo aver eseguito Floyd-Warshall, si controlli la diagonale principale: se un valore dist[i][i] < 0, esiste un ciclo negativo che passa per il nodo i. Questo accade perché un ciclo negativo consente di raggiungere i partendo da i con un costo negativo. Se non esiste alcun ciclo negativo, tutte le voci della diagonale rimangono pari a 0.

def has_negative_cycle_fw(V, edges):
    dist = floyd_warshall(V, edges)
    for i in range(V):
        if dist[i][i] < 0:
            return True  # negative cycle through node i
    return False

# Negative cycle: 0->1->2->0 with weights 1,-3,1 (sum=-1)
edges_neg = [(0,1,1),(1,2,-3),(2,0,1)]
print(has_negative_cycle_fw(3, edges_neg))  # True

Ricostruzione del cammino

Per ricostruire il cammino effettivo da i a j, si mantenga una matrice next[i][j]: inizialmente next[i][j] = j per gli archi diretti. Quando si aggiorna il cammino passando per il nodo intermedio k, si imposti next[i][j] = next[i][k]. Per recuperare il cammino, si inizi da i e si seguano i puntatori next fino a raggiungere j. Ciò aggiunge O(V²) di memoria e O(V) per ogni ricostruzione del cammino.

def fw_with_path(V, edges):
    INF = float('inf')
    dist = [[INF]*V for _ in range(V)]
    nxt = [[None]*V for _ in range(V)]
    for i in range(V): dist[i][i] = 0
    for u, v, w in edges:
        dist[u][v] = w; nxt[u][v] = v
    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]
                    nxt[i][j] = nxt[i][k]
    return dist, nxt

def get_path(nxt, i, j):
    if nxt[i][j] is None: return []
    path = [i]
    while i != j:
        i = nxt[i][j]; path.append(i)
    return path

Chiusura transitiva

Una variante più semplice: la Chiusura transitiva risponde alla domanda: il nodo j è raggiungibile dal nodo i? per tutte le coppie. Si sostituiscano le distanze con valori booleani: reach[i][j] = reach[i][j] or (reach[i][k] and reach[k][j]). Si tratta di Floyd-Warshall con l'operatore booleano OR al posto dell'addizione e del minimo. Si inizializzi reach[i][i] = True e reach[i][j] = True per gli archi diretti.

def transitive_closure(V, edges):
    reach = [[False]*V for _ in range(V)]
    for i in range(V):
        reach[i][i] = True
    for u, v, _ in edges:
        reach[u][v] = True
    for k in range(V):
        for i in range(V):
            for j in range(V):
                reach[i][j] = reach[i][j] or (reach[i][k] and reach[k][j])
    return reach

edges = [(0,1,1),(1,2,1)]
R = transitive_closure(3, edges)
print(R[0][2])  # True (0 can reach 2 via 0->1->2)

Complessità e quando usarlo

Floyd-Warshall: O(V³) di tempo, O(V²) di spazio. Per i grafi densi (E ≈ V²) con V ≤ 300, è più veloce che eseguire Dijkstra V volte, anch'esso O(V³) in tal caso. Per i grafi sparsi con V = 1000 e E = 3000, eseguire V volte Dijkstra ha un costo O(V×E×log V) ≈ 33M, mentre Floyd-Warshall ha un costo O(V³) = 10⁹: in questo caso vince Dijkstra. È importante sapere quando ciascun algoritmo è appropriato.

Numero minimo di passaggi tra tutte le coppie

Imposti tutti i pesi degli archi su 1 (oppure utilizzi una matrice di adiacenza booleana con Floyd-Warshall, usando l'addizione al posto del minimo): dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]). In questo modo si calcola il numero minimo di passaggi tra tutte le coppie: il risultato di una BFS per tutte le coppie, ottenuto però con un'unica esecuzione di Floyd-Warshall in O(V³).

def min_hops_all_pairs(V, adj_list):
    INF = float('inf')
    dist = [[INF]*V for _ in range(V)]
    for i in range(V):
        dist[i][i] = 0
        for j in adj_list[i]:
            dist[i][j] = 1
    for k in range(V):
        for i in range(V):
            for j in range(V):
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
    return dist

adj = [[1,2],[2],[3],[],[]]
print(min_hops_all_pairs(5, adj)[0])  # [0, 1, 1, 2, INF]

Contesto dei colloqui: quando gli intervistatori chiedono di Floyd-Warshall

Floyd-Warshall compare nei colloqui per domande che riguardano: (1) le distanze tra tutte le coppie in un grafo piccolo, (2) la verifica dell'esistenza di un ciclo con peso totale negativo, (3) il calcolo dei percorsi minimi nei problemi di propagazione dei vincoli e (4) i problemi che richiedono esplicitamente soluzioni O(V³) con V ≤ 200. Menzioni sempre la struttura a tre cicli e il requisito dell'assenza di cicli negativi affinché il risultato sia corretto.

Grafi non orientati con Floyd-Warshall

Per i grafi non orientati, aggiunga entrambe le direzioni per ogni arco: dist[u][v] = dist[v][u] = weight. Il resto dell'algoritmo è identico. La matrice risultante è simmetrica: dist[i][j] == dist[j][i] per tutte le coppie. Durante l'inizializzazione, faccia attenzione a non assegnare accidentalmente archi orientati: gli archi non orientati devono essere aggiunti in entrambe le direzioni alla matrice iniziale prima di eseguire i tre cicli.

def fw_undirected(V, edges):
    INF = float('inf')
    dist = [[INF]*V for _ in range(V)]
    for i in range(V): dist[i][i] = 0
    for u, v, w in edges:
        dist[u][v] = w
        dist[v][u] = w  # both directions for undirected
    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]
    return dist

Verifica rapida

Verifichi la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep trattati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: Floyd-Warshall calcola i percorsi minimi tra tutte le coppie con tre cicli annidati e la ricorrenza dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]), i cicli negativi possono essere rilevati verificando se, al termine, esiste un dist[i][i] < 0 e l'algoritmo richiede O(V³) tempo e O(V²) spazio. Ora riprenderemo le applicazioni dei percorsi minimi con Network Delay Time e le tecniche di ricostruzione dei percorsi.

Domande Frequenti

La lezione «Floyd-Warshall: cammini minimi tra tutte le coppie» è gratuita?

Sì — il testo completo di «Floyd-Warshall: cammini minimi tra tutte le coppie» è 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 DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Floyd-Warshall: cammini minimi tra tutte le coppie»?

Riempa la matrice delle distanze tra tutte le coppie usando l'algoritmo Floyd-Warshall con tre cicli annidati e lo applichi per trovare il numero minimo di salti tra ogni coppia di nodi. Eserciti DSA 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 DSA Interview Prep?

Non è richiesta alcuna esperienza precedente. DSA 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 3 di 4.

Quanto tempo richiede la lezione «Floyd-Warshall: cammini minimi tra tutte le coppie»?

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 DSA Interview Prep?

Sì. Ogni lezione DSA 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

  1. Algoritmo di Dijkstra con coda di priorità
  2. Bellman-Ford e cicli negativi
  3. Floyd-Warshall: cammini minimi tra tutte le coppie
  4. Network Delay Time e ricostruzione del percorso
← Torna a DSA Interview Prep