0 - 1 ricerca in ampiezza (o 0 - 1 BFS) — è una modifica dell'algoritmo classico di ricerca in ampiezza (BFS), utilizzato per trovare i cammini più brevi nei grafi. La differenza di questo algoritmo consiste nel fatto che i pesi degli archi del grafo possono assumere esclusivamente i valori oppure , il che permette di ottimizzare significativamente la ricerca.
Postazione del problema. Immaginiamo di avere un grafo pesato, in cui i pesi degli archi sono oppure . Ad esempio, un grafo del genere si può incontrare quando lavoriamo con una città in cui alcune strade sono gratuite (peso ), mentre per altre bisogna pagare (peso ). Vogliamo trovare il cammino più breve da un vertice del grafo a un altro tenendo conto di questi pesi.
Il problema dell'algoritmo standard di ricerca in ampiezza. L'algoritmo classico di ricerca in ampiezza (BFS) ha una complessità di O(V + E) e viene utilizzato per trovare i cammini più brevi in un grafo, ma è adatto solo per grafi in cui tutti gli archi hanno lo stesso peso. Nel caso in cui i pesi degli archi siano diversi, tale approccio risulta già inefficiente — a tale scopo si utilizza solitamente l'algoritmo di Dijkstra con complessità O(V^2 + E) oppure O(E \cdot log_2V). Ma per i grafi con pesi solo e esiste un approccio più veloce — 0 - 1 BFS.
Algoritmo 0-1 BFS
Nel 0 - 1 BFS utilizziamo una coda a doppia estremità (deque). L'idea è la seguente:
Partiamo dal vertice sorgente e lo aggiungiamo alla coda con distanza iniziale pari a .
Durante l'elaborazione di ciascun vertice (che viene estratto dalla testa della coda), esaminiamo i suoi vicini :
Se l'arco ha peso , allora il vertice adiacente viene inserito all'inizio della coda con la distanza corrente (dist[v] = dist[u]).
Se l'arco ha peso , allora il vertice adiacente viene inserito alla fine della coda con l'incremento della distanza di un'unità (dist[v] = dist[u] + 1).
Ciò consente di mantenere in modo efficiente l'ordine corretto di elaborazione dei vertici: i vertici raggiungibili tramite archi con peso minore vengono elaborati prima.
Così, l'utilizzo di una coda a doppia estremità garantisce che abbiamo sempre accesso al vertice con distanza minima all'inizio della coda, e la complessità di funzionamento dell'algoritmo rimane O(V + E).
Il rilassamento degli archi nella ricerca in ampiezza 0 - 1 è simile al classico rilassamento nell'algoritmo di Dijkstra, ma si differenzia per l'uso di una coda a doppia estremità (deque) invece di una coda con priorità (priority queue) per ordinare i vertici in base al peso degli archi.
L'algoritmo 0-1 BFS (a differenza del classico BFS) non garantisce che ogni vertice venga visitato con la distanza minima già la prima volta, specialmente se lo stesso vertice può essere raggiunto tramite più percorsi. Pertanto il rilassamento degli archi è obbligatorio.
Consideriamo il seguente esempio. Inizializziamo l'array delle distanze minime e la coda :
Consideriamo gli archi uscenti dal vertice . Rilassiamo gli archi e . Il vertice verrà inserito all'inizio della coda, mentre il vertice alla fine della coda.
Tuttavia il valore dist[2] = 1 non è definitivo. Percorriamo l'arco 3 - 4 e impostiamo dist[4] = 0, queue = (4, 2). Poi consideriamo l'arco 4 - 2 e il valore verrà impostato a . Così, ha assunto due valori diversi come risultato del rilassamento (prima , poi ).
Esempio
Il grafo riportato nell'esempio ha la forma:
La lunghezza del cammino più breve dal vertice al vertice è pari a , e il cammino più breve stesso ha la forma: 1 - 2 - 3.
Implementazione dell'algoritmo
Dichiariamo la costante infinito.
#define INF 0x3F3F3F3FDichiariamo l'array delle distanze minime e la lista di adiacenza del grafo .
vector<int> dist;
vector<vector<pair<int, int>>> g;La funzione bfs implementa la ricerca in ampiezza a partire dal vertice .
void bfs(int start)
{Inizializziamo l'array delle distanze minime .
dist = vector<int>(n + 1, INF);
dist[start] = 0;Dichiariamo la coda e inseriamo in essa il vertice iniziale .
deque<int> q;
q.push_back(start);Continuiamo l'algoritmo di ricerca in ampiezza finché la coda non è vuota.
while (!q.empty())
{Estraiamo dalla testa della coda il vertice .
int v = q.front(); q.pop_front();Scorriamo gli archi uscenti dal vertice .
for (auto edge : g[v])
{
int to = edge.first;
int w = edge.second;L'arco corrente (v, to) ha peso . Eseguiamo il suo rilassamento.
if (dist[to] > dist[v] + w)
{Se l'arco si rilassa, allora ricalcoliamo la distanza minima dist[to].
dist[to] = dist[v] + w;A seconda del peso dell'arco , aggiungiamo il vertice alla fine o all'inizio della coda.
if (w == 1)
q.push_back(to);
else
q.push_front(to);
}
}
}
}Parte principale del programma. Leggiamo i dati di input.
scanf("%d %d %d %d", &n, &m, &s, &d);
g.resize(n + 1);Leggiamo il grafo.
for (i = 0; i < m; i++)
{
scanf("%d %d %d", &a, &b, &w);
g[a].push_back({ b, w });
g[b].push_back({ a, w });
}Avviamo la ricerca in ampiezza a partire dal vertice iniziale .
bfs(s);Stampiamo la distanza minima fino al vertice .
printf("%d\n", dist[d]);