Zbiory

advertisement
Zbiory rozłączne
Zbiory rozłączne
Struktura danych dla zbiorów rozłącznych (ang.
disjoint sets data structures), lub krótko zbiory
rozłączne, to struktura danych, która dla ustalonego
uniwersum przechowuje jego podział na mniejsze,
rozłączne zbiory.
 Struktura danych dla zbiorów rozłącznych pozwala
efektywnie realizować następujące operacje:

◦ find(x) – odnalezienie reprezentanta zbioru, do którego
należy x;
◦ union(x, y) – połączenie dwóch zbiorów zawierających
elementy x i y w jeden zbiór.

Czasami rozważa się zbiory rozłączne z dodatkowymi
operacjami:
◦ extract(x) – odłączenie elementu x ze zbioru, do którego
należy;
◦ append(x) – dodanie do uniwersum nowego elementu x.
Implementacja zbiorów rozłącznych
Struktura danych dla zbiorów rozłącznych
pozwala zarządzać rodziną dynamicznych
(zmieniających się w czasie) zbiorów
rozłącznych.
 Uniwersum pamiętamy w tablicy ze
wskaźnikami na elementy.
 Do każdego zbioru odwołujemy się przez
jego reprezentanta.

Praca ze zbiorami rozłącznymi
Pracę ze zbiorami rozłącznymi
rozpoczynamy po zainicjalizowaniu
struktury: utworzeniu n zbiorów
jednoelementowych dla uniwersum
rozmiaru n.
 Czas inicjalizacji wynosi O(n).
 Następnie wykonujemy na tej strukturze
ciąg m operacji union() i find().
 Pamięć zajmowana przez strukturę
powinna ograniczać się do O(n).

Implementacja listowa zbiorów
rozłącznych



Prostym sposobem implementacji zbiorów
rozłącznych jest zapamiętanie każdego
zbioru jako listy.
Reprezentantem zbioru jest pierwszy
element na liście.
Każdy element ma dodatkowo bezpośredni
wskaźnik do reprezentanta.
Implementacja listowa zbiorów
rozłącznych

Operacje na zbiorach rozłącznych:
◦ find(x) – z węzła x zaglądamy do
reprezentanta i odczytujemy jego
identyfikator – czas O(1)
◦ union(x, y) gdzie x i y są reprezentantami
zbiorów – łączymy dwie listy i poprawiamy
wskaźniki do reprezentanta w tej dołączonej –
czas liniowy względem jej długości dołączonej
listy O(n)
Implementacja listowa zbiorów
rozłącznych
Prosta sztuczka zwana zbalansowanym łączeniem
pozwala zmniejszyć koszt operacji union() w
sensie zamortyzowanym: polega ona na tym, że
podczas operacji union() zawsze dołączamy
krótszą listę na koniec dłuższej (wymaga to
przechowywania w węźle początkowym listy
dodatkowego atrybutu zawierającego rozmiar
zbioru oraz adres ostatniego elementu listy).
 Koszt czasowy wykonania m operacji find() i
union() wynosi O(m + n log n); zamortyzowany
koszt wykonania pojedynczej operacji union()
wynosi O(log n).

Implementacja listowa zbiorów
rozłącznych
Pytanie: Ile razy poszczególny element na
liście może zmienić reprezentanta?
 Odpowiedź: Co najwyżej log(n).
 Uzasadnienie: Nowy zbiór, do którego
będzie należał element jest co najmniej
dwa razy większy.

Implementacja drzewiasta zbiorów
rozłącznych
Prostym sposobem implementacji zbiorów
rozłącznych jest zapamiętanie każdego
zbioru jako drzewa odwrotnie skierowanego
(w węzłach trzymamy wskaźniki na
rodziców).
 Reprezentantem zbioru jest korzeń drzewa.

Implementacja drzewiasta zbiorów
rozłącznych

Operacje na zbiorach rozłącznych:
◦ find(x) – z węzła x rozpoczynamy wędrówkę
do korzenia – czas O(wysokość drzewa)
◦ union(x, y) gdzie x i y są reprezentantami
zbiorów – jedno drzewo podłączamy pod
drugie drzewo – O(1)
Implementacja drzewiasta zbiorów
rozłącznych
Sztuczka zwana zbalansowanym łączeniem
pozwala zmniejszyć koszt operacji find() w sensie
najgorszego przypadku: polega ona na tym, że
podczas operacji union() zawsze dołączamy niższe
drzewo pod wyższe (wymaga to przechowywania
w korzeniu drzewa dodatkowego atrybutu
zawierającego wysokość krawędziową drzewa –
rank).
 Koszt czasowy wykonania m operacji find() i
union() wynosi O(m  log n); koszt wykonania
pojedynczej operacji find() wynosi O(log n),
ponieważ wysokość drzewa jest ograniczona przez
logarytm z liczby węzłów w drzewie.

Implementacja drzewiasta zbiorów
rozłącznych
Pytanie: Ile razy poszczególny zbiór może
być dołączany do innego zbioru?
 Odpowiedź: Co najwyżej log(n).
 Uzasadnienie: Nowy zbiór, do którego
będą należały elementy podłączanego
zbioru na co najmniej wykładniczą w
stosunku do wysokości drzewa liczbę
elementów.

Implementacja drzewiasta zbiorów
rozłącznych


Sztuczka zwana kompresją ścieżek pozwala
zmniejszyć głębokość części elementów po
operacji find(): polega ona na tym, że
podczas operacji find() zawsze dołączamy
odwiedzone (leżące na ścieżce od danego
węzła do korzenia) węzły do korzenia
drzewa.
Koszt czasowy wykonania m operacji find() i
union() wynosi O(m  log* n); koszt
wykonania pojedynczej operacji find()
wynosi w najgorszym przypadku O(log n).
Zadanie 1
Zdefiniuj drzewiastą strukturę dla zbiorów
rozłącznych.
 Przetestuj swoją strukturę dla losowego
ciągu operacji union() i find().

Zadanie 2
Zdefiniuj drzewiastą strukturę dla zbiorów
rozłącznych.
 Zaimplementuj w swojej strukturze
zbalansowane łączenie przy union() i
kompresję ścieżek przy find().
 Przetestuj swoją strukturę dla losowego
ciągu operacji union() i find().

Download