Preskoči na glavni sadržaj

Union-find struktura

Autor: Maja Milas

Zadatak

Zamislite da u nekoj zemlji postoji nn gradova i mm cesta koje ih povezuju, a u svakom gradu na početku živi xx ljudi. Tijekom vremena, neke ceste postaju nestabilne i urušavaju se. U tom svijetu provodite 2 oblika queryja:

  • D K - urušila se cesta K
  • P A x - populacija A-tog grada postala je x

Vi ste mladi nadobudni geograf i želite nakon svakog upita odgovoriti koji je broj stanovnika trenutno najnaseljenije regije. Regija je definirana kao podskup gradova u kojem se može doći cestom između bilo koja dva.

Ovaj je zadatak ovdje samo za ilustraciju, ali probajte malo razmisliti kako biste ga riješili. Puni tekst problema možete naći ovdje.

Struktura

Union find (nekad i disjoint set) je struktura podataka koja elemente raspodjeljuje u skupove. Za dva skupa kažemo da su disjunktni (disjoint) ako ne postoji element koji pripada u oba skupa. Struktura je napravljena tako da svaki skup ima svoga predstavnika (representative), a elementi skupa su preko lanca povezani s predstavnikom. Na slici ispod su prikazana tri skupa, {1, 4, 7}, {5} i {2, 3, 6, 8}. Predstavnici skupova su redom 4, 5 i 2.

union_find

Predstavnike također možemo gledati na razini lanca. Npr. reći ćemo da elementi 6 i 8 imaju za predstavnika element 3.

Union find podržava dvije operacije:

  • unite(aa,bb) - operacija koja spaja skup koji sadrži element aa sa skupom koji sadrži element bb
  • find(xx) - operacija koja traži predstavnika skupa koji sadrži element xx
bitno

Obje operacije rade u logaritamskoj složenosti.

Dva skupa možemo spojiti tako da povežemo njihove predstavnike. Na slici ispod spojili smo skupove {1, 4, 7} i {2, 3, 6, 8} tako da 2 postaje novi predstavnik, a skup koji u konačnici dobivamo je {1, 2, 3, 4, 6, 7, 8}.

union_find2

Pokazuje se da je prilikom spajanja uvijek efikasnije spajati manji skup na veći. Na taj je način naš skup više 'razgranat' i možemo postići logaritamsku složenost pretraživanja.

Implementacija

Za ovu ćemo implementaciju koristiti 1D polje link gdje će na i-tom mjestu pisati predstavnik i-tog elemenata te polje len gdje ćemo na mjestima na kojima su predstavnici pisati veličina skupa kojeg predstavljaju. Na gornjoj bi slici bilo link[7]=4 i len[2]=7.

Na početku svaki element pripada svom skupu pa je i sam svoj predstavnik. Duljina svih skupova je 11.

for(int i=0; i<n; i++) link[i] = i;
for(int i=0; i<n; i++) len[i] = 1;

Funkcija find(xx) vraća predstavnika skupa koji sadrži element xx. Pretraživanje obavljamo penjanjem po lancu dok ne dođemo do predstavnika.

int find(int x) {
while(x != link[x]) x = link[x];
return x;
}

Možemo napisati i funkciju same(a,b) koja provjerava jesu li dva elementa u istom skupu.

bool same(int a, int b) {
return find(a) == find(b);
}

Funkcija unite(aa,bb) spaja skupove koji sadrže element aa i element bb. Prvo nalazimo njihove predstavnike i provjeravamo koji je skup manji, a potom spajamo manji skup na veći.

void unite(int a, int b) {
a = find(a);
b = find(b);
if(len[a] < len[b]) swap(a,b);
len[a] += len[b];
link[b] = a;
}

Pokušajte sada sami napisati kod koji bi rješavao problem s početka lekcije. Happy coding!