ALGORITMO: BELLMAN-FORD
L’algoritmo di Bellman-Ford calcola i percorsi minimi da un nodo sorgente a tutti gli altri nodi in un grafo orientato o non orientato pesato, anche con archi a peso negativo, a condizione che non siano presenti cicli negativi raggiungibili dalla sorgente.
PRECONDIZIONI
- Il grafo può contenere archi con peso negativo.
- Non devono esserci cicli negativi raggiungibili dalla sorgente.
- Funziona su grafi orientati e non orientati pesati.
TERMINOLOGIA
- Vertice (Vertex) → nodo del grafo.
- Arco Pesato (Weighted Edge) → connessione tra due vertici con un peso (positivo o negativo).
- Distanza → costo cumulativo minimo dal nodo sorgente a un nodo.
- Predecessore (Parent/Predecessor) → nodo precedente lungo il percorso minimo.
- Rilassamento (Relaxation) → operazione che aggiorna la distanza di un nodo se si trova un percorso più corto.
OPERAZIONI PRINCIPALI
aggiungiVertice(v)→ inserisce un nuovo vertice nel grafo.aggiungiArco(v1, v2, peso)→ aggiunge un arco orientato o non orientato con peso.bellmanFord(start)→ calcola i percorsi minimi dal nodo sorgentestarta tutti gli altri nodi, restituendo:- le distanze minime
- i predecessori per ricostruire i percorsi.
- Rileva cicli negativi: se dopo |V|-1 rilassamenti un arco può ancora essere rilassato, esiste un ciclo negativo raggiungibile dalla sorgente.
FUNZIONAMENTO DELL’ALGORITMO
- Inizializza tutte le distanze a ∞, eccetto il nodo sorgente (distanza = 0).
- Ripeti |V| - 1 volte (dove |V| è il numero di vertici):
- Per ogni arco
(u, v)con pesow:- Se
distanza[u] + w < distanza[v]→ aggiornadistanza[v]e impostaucome predecessore.
- Se
- Per ogni arco
- Controlla tutti gli archi una volta in più per rilevare eventuali cicli negativi.
- Al termine:
- le distanze minime sono corrette se non ci sono cicli negativi.
- è possibile ricostruire i percorsi minimi seguendo i predecessori.
COMPLESSITÀ
- Tempo → O(V * E)
- Spazio → O(V) per memorizzare distanze e predecessori
ESEMPIO DI USCITA
Grafo: A -> B(4), C(2) B -> C(-1), D(5) C -> D(3), E(4) D -> E(7) E ->
Distanze minime da A: { A: 0, B: 4, C: 2, D: 5, E: 6 }
Predecessori: { A: null, B: ‘A’, C: ‘A’, D: ‘C’, E: ‘C’ }
Percorso minimo da A a E: A → C → E, costo 6.
APPLICAZIONI
- Reti di telecomunicazione con costi variabili.
- Calcolo di percorsi minimi in grafi con pesi negativi.
- Rilevamento di cicli negativi nei grafi.
- Pianificazione di percorsi e analisi di dipendenze complesse.
VANTAGGI
- Gestisce archi con pesi negativi.
- Rileva cicli negativi.
- Funziona su grafi orientati e non orientati pesati.
LIMITAZIONI
- Più lento di Dijkstra per grafi senza pesi negativi (O(V*E) vs O(V+E log V)).
- Non può essere utilizzato se esistono cicli negativi raggiungibili dalla sorgente.
NOTE
- Adatto per scenari in cui i pesi degli archi possono essere negativi.
- La struttura dati utilizzata può essere semplice (array/lista) poiché l’algoritmo visita tutti gli archi ripetutamente.
- Può essere combinato con Dijkstra per ottimizzazioni in grafi misti.
CODICE
import { DirectedGraph, Edge } from "../structure/orientedGraph";
/**
* Algoritmo di Bellman-Ford esterno
* @param graph Grafo orientato pesato (può avere pesi negativi, ma non cicli negativi)
* @param start Nodo sorgente
* @returns Oggetto contenente distanze minime e predecessori, o null se ciclo negativo rilevato
*/
export function bellmanFord<E>(
graph: DirectedGraph<E>,
start: string
): { distanze: Record<string, number>; predecessori: Record<string, string | null> } | null {
const distanze: Record<string, number> = {};
const predecessori: Record<string, string | null> = {};
const adjacencyList = graph.getAdjacencyList(); // Map<E, Edge<E>[]>
// Inizializzazione
for (const [vertice] of adjacencyList) {
const key = String(vertice);
distanze[key] = Infinity;
predecessori[key] = null;
}
distanze[start] = 0;
const vertices = Array.from(adjacencyList.keys());
// Relaxation |V|-1 volte
for (let i = 0; i < vertices.length - 1; i++) {
for (const [u] of adjacencyList) {
const uKey = String(u);
const edges = adjacencyList.get(u) ?? [];
for (const edge of edges) {
const vKey = String(edge.nodo);
if (distanze[uKey] + edge.peso < distanze[vKey]) {
distanze[vKey] = distanze[uKey] + edge.peso;
predecessori[vKey] = uKey;
}
}
}
}
// Verifica cicli negativi
for (const [u] of adjacencyList) {
const uKey = String(u);
const edges = adjacencyList.get(u) ?? [];
for (const edge of edges) {
const vKey = String(edge.nodo);
if (distanze[uKey] + edge.peso < distanze[vKey]) {
console.error("Grafo contiene un ciclo negativo!");
return null;
}
}
}
return { distanze, predecessori };
}
// =============================
// Esempio di utilizzo
// =============================
const graph = new DirectedGraph<string>();
graph.aggiungiArco('A', 'B', 4);
graph.aggiungiArco('A', 'C', 2);
graph.aggiungiArco('B', 'C', 5);
graph.aggiungiArco('B', 'D', 10);
graph.aggiungiArco('C', 'D', 3);
graph.aggiungiArco('D', 'E', 7);
graph.aggiungiArco('C', 'E', 4);
graph.display();
const risultato = bellmanFord<string>(graph, 'A');
if (risultato) {
console.log('Distanze minime da A:', risultato.distanze);
console.log('Predecessori:', risultato.predecessori);
} else {
console.log('Grafo contiene un ciclo negativo.');
}