→ R Ef:
Transkrypt
→ R Ef:
Temat: Minimalne drzewo rozpinające - algorytm Kruskala. Struktury danych dla problemu Union-Find. 1. Minimalne drzewo rozpinające WP: Graf spójny, nieskierowany i z wagami G = V , E , f ( V-zbiór wierzchołków, E- zbiór krawędzi, f - funkcja wag, f : E → R+ ) WK: Acykliczny podzbiór T ⊆ E , który łączy wszystkie wierzchołki grafu i którego łączna waga f (T ) = f (u , v ) ∑ ( ) u ,v ∈T jest najmniejsza. Przykład b c 8 d 9 4 2 14 11 a 7 i 7 e 4 10 6 8 h g 1 2 f 1 2. Algorytm Kruskala Operacje realizowane na zbiorze wierzchołków V: MAKE_SETS(R) - operacja inicjalizacji polegająca na utworzeniu jednoelementowych zbiorów złożonych z pojedynczych wierzchołków grafu. Powstaje n - elementowa rodzina jednoelementowych podzbiorów, którą oznaczymy przez R. FIND_SET(v) - operacja ustalenia zbioru (w rodzinie podzbiorów R), do którego należy wierzchołek v. UNION(A, B)- operacja sumowania zbiorów A i B (z rodziny R). Zakładamy, że podzbiory A i B są rozłączne. Algorytm Kruskala MDR_Kruskal(G) begin T:=∅; MAKE_SETS(R); "Utwórz listę L wszystkich krawędzie zbioru E posortowaną niemalejąco względem wag f"; (*) while ("lista L jest niepusta") { " usuń pierwszą krawędź listy L- krawędź usunięta: {u,v}"; A:=FIND_SET(u); B:=FIND_SET(v); if (A ≠ B) { T=T ∪{{u, v}}; UNION(A, B); } } 2 Przykład Rodzina podzbiorów R po wykonaniu procedury MAKE_SETS(R); R={{a}, {b}, {c}, {d}, {e}, {f}, {g}, {h}, {i}} Lista krawędzi L posortowana niemalejąco według wag f. L: f: {g, h} 1 {c, i} 2 {f, g} 2 {a, b} 4 {c, f} 4 {g, i} 6 {c, d} 7 {h, i} 7 {a, h} 8 {b, c} 8 {d, e} 9 {e, f} 10 {b, h} 11 {d, f} 14 Podzbiór T oraz rodzina podzbiorów R po każdym kroku pętli (*): po I kroku: R={{a}, {b}, {c}, {d}, {e}, {f}, {g, h}, {i}} T={{g, h}} po II kroku: R={{a}, {b}, {c, i}, {d}, {e}, {f}, {g, h}} T={{g, h}, {c, i}} po III kroku: R={{a}, {b}, {c, i}, {d}, {e}, {f, g, h}} T={{g, h}, {c, i}, {f, g}} po IV kroku: R={{a, b}, {c, i}, {d}, {e}, {f, g, h}} T={{a, b},{g, h}, {c, i}, {f, g}} 3 po V i VI kroku: R={{a, b}, {d}, {e}, { c, i, f, g, h}} T={{a, b},{g, h}, {c, i}, {f, g}, {c, f}} po VII i VIII kroku: R={{a, b}, {e}, { c, d, i, f, g, h}} T={{a, b},{g, h}, {c, i}, {f, g}, {c, f}, {c, d}} po IX i X kroku: R={{a, b, c, d ,i, f, g, h}, {e}} T={{a, b},{g, h}, {c, i}, {f, g}, {c, f}, {c, d}, {a, h}} po XI, XII, XIII i XIV (ostatnim) kroku: R={{a, b, c, d , i, f, g, h, e}} T={{a, b}, {a, h}, {g, h}, {c, i}, {f, g}, {c, f}, {c, d}, {d, e}} Złożoność czasowa algorytmu Kruskala zależy od sposobu implementacji struktury danych dla zbiorów rozłącznych rodziny R, na której realizowane są operacje FIND_SET i UNION w pętli (*). Omówimy dwie struktury danych dla problemu UNION-FIND: • implementację listową oraz • implementację drzewiastą z kompresją ścieżek. 3. Implementacja listowa Zakładamy, że każdy wierzchołek grafu jest reprezentowany numerem od 1 do n. W implementacji listowej: • Elementy tego samego zbioru znajdują się na jednej liści. Pod wskaźnikami listy zapamiętane są struktury z polami: - nr (numer wierzchołka), - next (adres kolejnego elementu listy), - zbior (adres na zbiór, do którego należy element) 4 • Adresy elementów umieszczone są w tablicy jednowymiarowej n – elementowej R • Zbiorem jest wskaźnik na strukturę z polami: - licz (liczba elementów zbioru) - pocz (adres na pierwszy element zbioru) Inicjalizacja: MAKE_SETS(R) Powstaje n elementowa rodzina zbiorów R zawierających po jednym elemencie od 1 do n Przykład n=6 pocz licz 1 1 1 1 1 1 nr zbior next 1 2 tablica wskaźników 3 R: 4 1 2 3 5 4 5 6 6 Operacja FIND_SET(v) Zwracany jest adres zbioru, do którego należy element v. FIND_SET(v) FIND_SET=R[v]->zbior; 5 Operacja UNION(A, B) UNION(A, B) if (A->licz > B->licznik) „zamień zbiór A ze zbiorem B”; x=A->pocz; while (x->next) { x->zbior=B; x=x->next; } x->zbior=B; x->next=B->pocz; B->pocz=A->pocz; B->licz=A->licz+B->licz; „usuń zbiór A”; } Przykład n=6 Rodzina zbiorów przed operacją Union(A, B) A B 2 1 3 2 1 3 R: 5 4 1 2 3 4 5 6 6 6 Rodzina zbiorów po operacji Union(A, B) B 5 1 2 3 1 5 4 1 2 3 4 Złożoność czasowa algorytmu Kruskala 5 6 6 Rozmiar zadania: n - liczba wierzchołków grafu, m - liczba krawędzi grafu Koszt operacji MAKE_SETS: O(n) Koszt utworzenia posortowanej listy krawędzi L: O(nlogn) Koszt operacji FIND_SET zrealizowanych w pętli (*): O(m) Koszt operacji UNION zrealizowanych w pętli (*): O(nlogn) Uwaga ! Koszt pojedynczej operacji UNION, w przypadku pesymistycznym jest O(n), ale koszt n-1 operacji UNION w pętli (*) jest O(nlogn), a nie O(n2). Liczymy bowiem, ile razy dany element może mieć zmieniane dowiązanie do zbioru (zmiana adresu zbior). Za każdym razem element przechodzi do listy o rozmiarze co najmniej dwukrotnie większym. Zmian takich może być co najwyżej logn. Wniosek Złożoność czasowa algorytmu Kruskala zrealizowanego w strukturze listowej jest O(m + nlogn) 7 4. Implementacja drzewiasta z kompresją ścieżek W implementacji drzewiastej z kompresją ścieżek: • Elementy tego samego zbioru znajdują się w jednym drzewie. Pod wskaźnikami węzłów drzewa zapamiętane są struktury z polami: - nr (numer wierzchołka), - w_gore (adres na „wyższy”węzeł drzewa), - licz ( liczba elementów w zbiorze) • Ten sam typ wskaźnika, który reprezentuje element zbioru służy też do reprezentowania zbiorów rodziny. • Adresy elementów umieszczone są w tablicy jednowymiarowej n – elementowej R. Operacja MAKE_SETS(R) Tworzymy n elementowy las jednowęzłowych drzew, których adresy zapamiętujemy w tablicy R. Każdy z węzłów ma pole licz ustawione na wartości 1, pole w_ gore na adresie pustym, a pole nr na numerze wierzchołka grafu. Przykład n=6 11 21 31 R 1 41 2 3 4 51 5 61 6 8 Operacja UNION(A,B) UNION(A, B) if (A->licz > B->licz) “zamień zbiór A ze zbiorem B”; A->w_gore= B; B->licz= A->licz + B->licz; Przykład n=6 Rodzina podzbiorów (z pominięciem tablicy R) przed wykonaniem operacji UNION(A, B) A B 52 14 2 3 4 6 Rodzina podzbiorów (z pominięciem tablicy R) po wykonaniu operacji UNION(A, B) B A 16 2 5 3 4 6 9 Operacja FIND_SET(v) FIND_SET(v) x= R[v]; if (x->w_gore!=NULL) FIND_SET= x; else { while (x->w_gore) do x=x->w_gore; FIND_SET= x; y=R[v]; while (y!=x) { temp=y->w_gore; y->w_gore=x; y= temp; } } Przykład x y R v 10 Tw. Hopcrofta -Ullmana: Koszt czasowy realizacji operacji MAKE_SETS a następnie n-1 operacji UNION i m operacji FIND_SET w pętli (*), w algorytmie Kruskala wynosi O((m+n)log*n), gdzie 0 dla n = 0, 1 log* n = i takie, że F (i ) < n ≤ F (i + 1) dla n > 1 0 dla i = 0 F (i ) = F (i −1) dla i > 0 2 log* 0 = log* 1 = 0 log* 16 = 2, ponieważ F (2) < 16 ≤ F(3) F (3) = 2 F (2 ) =2 2 F (1 ) F (2 ) = 2 F (1) = 22 F (0 ) =2 22 2F (0 ) = 24 = 16 = 21 = 2 11