Dijkstrin algoritam: najkraći put u težinskom grafu

Lekcija 11 od 17 · Grafovi i stabla

BFS nalazi najkraći put kada sve grane imaju istu dužinu. Kada grane imaju različite dužine (kilometri, minuti, cene), koristi se Dijkstrin algoritam. Radi za grafove čije težine nisu negativne i uz priority_queue obrađuje i milion grana za delić sekunde.

Pre nego što počneš: potrebno predznanje – BFS, priority_queue.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.

Ideja

        4
   1 ------- 2
   |  \      |
 1 |   \ 7   | 2
   |    \    |
   3 ------- 4 ---- 5
        5       3

Grane: 1–2 (4), 1–3 (1), 1–4 (7), 2–4 (2), 3–4 (5), 4–5 (3).

Čuvamo niz dist[v] – najkraće do sada nađeno rastojanje od starta do v (na početku ∞, a za start 0). U svakom koraku uzimamo neobrađen čvor sa najmanjim dist: njegovo rastojanje je sigurno konačno, jer bi svaki drugi put išao preko čvora koji je već dalji. Zatim pokušavamo da skratimo put do njegovih suseda (relaksacija): ako je dist[v] + w < dist[u], onda je dist[u] = dist[v] + w.

Dijkstra iz čvora 1
KorakUzimamo čvordist[1..5] posle koraka
početak0 ∞ ∞ ∞ ∞
11 (dist 0)0 4 1 7 ∞
23 (dist 1)0 4 1 6 ∞ (1→3→4 = 6 < 7)
32 (dist 4)0 4 1 6 ∞ (1→2→4 = 6, nije kraće)
44 (dist 6)0 4 1 6 9
55 (dist 9)0 4 1 6 9

Program sa priority_queue

Najmanji dist brzo dobijamo iz reda sa prioritetom u kome su parovi (rastojanje, čvor), sa najmanjim na vrhu. Isti čvor može biti u redu više puta (svaki put kada mu se skrati rastojanje); zastarele zapise preskačemo proverom d > dist[v].

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

const long long INF = 1e18;

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<pair<int, int>>> graf(n + 1);    // graf[u] = {(sused, težina)}
    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        graf[u].push_back({v, w});
        graf[v].push_back({u, w});                 // neusmeren graf
    }
    int s = 1;
    vector<long long> dist(n + 1, INF);
    priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
    dist[s] = 0;
    pq.push({0, s});
    while (!pq.empty()) {
        auto [d, v] = pq.top();
        pq.pop();
        if (d > dist[v]) continue;                 // zastareo zapis – v je već obrađen kraćim putem
        for (auto [u, w] : graf[v]) {
            if (dist[v] + w < dist[u]) {           // relaksacija
                dist[u] = dist[v] + w;
                pq.push({dist[u], u});
            }
        }
    }
    for (int v = 1; v <= n; v++) {
        if (dist[v] == INF) cout << v << ": nedostizan" << endl;
        else cout << v << ": " << dist[v] << endl;
    }
    return 0;
}

Zapis auto [d, v] = pq.top(); rastavlja par na dve promenljive (C++17). U starijim prevodiocima piše se long long d = pq.top().first; int v = pq.top().second;.

Rekonstrukcija puta

Da bismo ispisali i sam put, pri svakoj relaksaciji pamtimo odakle smo došli: pret[u] = v. Put do cilja t dobija se praćenjem pret unazad od t do starta i obrtanjem – vidi zadatak 1.

Složenost i ograničenja

Vrednost
SloženostO((n + m) log n)
Negativne težinene radi – koristi Bellman-Ford
Sve težine jednakedovoljan je BFS, O(n + m)
Usmeren grafradi isto – granu dodaj samo u jednom smeru
Graf je DAGbrže: DP po topološkom redosledu, radi i sa negativnim težinama

Zašto ne radi sa negativnim granama: kada uzmemo čvor sa najmanjim dist, pretpostavljamo da se do njega kasnije ne može doći kraće. Negativna grana bi upravo to omogućila.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: Srednje

Najkraći put od 1 do n

Dat je neusmeren težinski graf. Ispiši dužinu najkraćeg puta od čvora 1 do čvora n i sam put, ili -1 ako put ne postoji.

Primer 1

Ulaz

5 6
1 2 4
1 3 1
1 4 7
2 4 2
3 4 5
4 5 3

Izlaz

9
1 3 4 5

Primer 2

Ulaz

3 1
1 2 5

Izlaz

-1
ZADATAK 02Težina: Teže

Najbliža bolnica

U gradu sa n raskrsnica i m dvosmernih ulica (sa dužinama) nalazi se k bolnica. Za svaku raskrsnicu ispiši rastojanje do najbliže bolnice.

Ulaz: n m k, zatim k raskrsnica sa bolnicom, pa m ulica u v w.

Primer

Ulaz

5 6 2
3 5
1 2 4
1 3 1
1 4 7
2 4 2
3 4 5
4 5 3

Izlaz

1 5 0 3 0
Česte greške i saveti
  • Rastojanja čuvaj u long long – zbir mnogo težina lako pređe 2·109.
  • priority_queue je podrazumevano max-hip; za Dijkstru treba greater<…> (ili čuvaj negativna rastojanja).
  • Bez provere if (d > dist[v]) continue; program je tačan, ali može biti mnogo sporiji.
  • Dijkstra ne radi sa negativnim težinama grana.

Proveri znanje

Otvori kviz

Dijkstrin algoritam

1. Za koje grafove Dijkstrin algoritam daje tačan rezultat?




2. Koji čvor Dijkstra obrađuje u sledećem koraku?




3. Šta je relaksacija grane v → u težine w?




4. Koja je složenost Dijkstre sa priority_queue?