Minimalno razapinjuće stablo: Kruskalov algoritam

Lekcija 14 od 17 · Grafovi i stabla

Kruskalov algoritam sortira sve grane od najjeftinije i redom ih dodaje – osim onih koje bi napravile ciklus. Da li grana pravi ciklus, brzo proverava struktura DSU (Union-Find).

Pre nego što počneš: potrebno predznanje – DSU, Primov algoritam.
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

Treba povezati sve gradove putevima tako da se iz svakog može stići u svaki, a ukupna cena puteva bude najmanja. Izabrane grane tada čine stablo (ciklus bi značio jedan suvišan put) koje pokriva sve čvorove – minimalno razapinjuće stablo (MST, engl. minimum spanning tree). Ima tačno n − 1 granu.

        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).

Kruskal: ukupno 1 + 2 + 3 + 4 = 10
Grana (redom po težini)Da li su krajevi već povezani?Odluka
1–3 (1)neuzimamo
2–4 (2)neuzimamo
4–5 (3)neuzimamo
1–2 (4)neuzimamo – sada su svi povezani (4 grane)
3–4 (5)dapreskačemo (ciklus)
1–4 (7)dapreskačemo

Zašto DSU?

DSU čuva podelu čvorova na grupe (komponente). Operacija nadji(v) vraća predstavnika grupe čvora v, a spoji(a, b) spaja dve grupe. Grana u–v pravi ciklus tačno kada su u i v već u istoj grupi. Uz kompresiju putanje i spajanje po veličini obe operacije su praktično O(1).

Program

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

struct Grana { int w, u, v; };       // težina prva, da sortiranje ide po težini

vector<int> rod, vel;

int nadji(int v) {                   // predstavnik grupe, uz kompresiju putanje
    if (rod[v] == v) return v;
    return rod[v] = nadji(rod[v]);
}

bool spoji(int a, int b) {           // vraća false ako su već u istoj grupi
    a = nadji(a);
    b = nadji(b);
    if (a == b) return false;
    if (vel[a] < vel[b]) swap(a, b); // manju grupu kačimo na veću
    rod[b] = a;
    vel[a] += vel[b];
    return true;
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<Grana> g(m);
    for (auto &e : g) cin >> e.u >> e.v >> e.w;
    sort(g.begin(), g.end(), [](const Grana &a, const Grana &b) { return a.w < b.w; });
    rod.resize(n + 1);
    iota(rod.begin(), rod.end(), 0);   // rod[v] = v
    vel.assign(n + 1, 1);
    long long ukupno = 0;
    vector<Grana> izabrane;
    for (auto &e : g)
        if (spoji(e.u, e.v)) {         // krajevi su bili u različitim grupama
            ukupno += e.w;
            izabrane.push_back(e);
        }
    if ((int)izabrane.size() != n - 1) {
        cout << "graf nije povezan" << endl;
        return 0;
    }
    cout << ukupno << endl;
    for (auto &e : izabrane) cout << e.u << " " << e.v << " " << e.w << endl;
    return 0;
}

Složenost određuje sortiranje: O(m log m).

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: Teže

Podela u k grupa

Dato je n učenika i m parova sa „udaljenošću“ u v d. Učenike treba podeliti u tačno k grupa tako da najmanja udaljenost između dva učenika iz različitih grupa bude što veća. Ispiši tu udaljenost. (Graf je povezan, 2 ≤ k ≤ n.)

Primer 1

Ulaz

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

Izlaz

2

Primer 2

Ulaz

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

Izlaz

4
Česte greške i saveti
  • Grane sortiraj po težini, ne po čvorovima.
  • Ako je izabrano manje od n − 1 grana, graf nije povezan.
  • Bez kompresije putanje nadji može biti O(n) i program postaje spor.

Proveri znanje

Otvori kviz

Kruskalov algoritam

1. Kojim redom Kruskal razmatra grane?




2. Kada Kruskal preskače granu u–v?




3. Šta određuje složenost Kruskalovog algoritma?