MSTs
Na wejściu dostajemy graf spójny , gdzie jest zbiorem wierzchołków, zbiorem krawędzi, a funkcją wagi krawędzi (zakładamy, że waga krawędzi jest dodatnia). Celem jest wyznaczenie podzbioru krawędzi tego grafu, który pozostawi graf spójnym oraz suma wag tych krawędzi będzie możliwie najmniejsza.
Input: nieskierowany graf .
Output: drzewo , gdzie , który minimalizuje wagę drzewa .
Nim przejdziemy do budowy algorytmów zachłannych rozwiązujących problem MST przyjrzyjmy się pewnym własnościom grafów (drzew nieukorzenionych):
drzewo jest nieskierowanym grafem spójnym i acyklicznym
Własność 1:
Usunięcie krawędzi należącej do cyklu w grafie nie rozspójni grafu.
Własność 2:
Drzewo o wierzchołkach ma krawędzi.
Własność tą można pokazać zaczynając od grafu o wierzchołkach i krawędziach.
Następnie dokładając krawędź:
Zatem pod dodaniu krawędzi otrzymamy jedną spójną składową o wierzchołkach, a całość będzie tworzyła drzewo.
Własność 3:
Każdy spójny nieskierowany graf taki, że jest drzewem.
Aby udowodnić tą własność musimy jedynie pokazać, że jest acykliczny. Załóżmy nie wprost, że zawiera cykl. Usuńmy jedną krawędź należącą do cyklu z tego grafu, otrzymamy , gdzie , który będzie acykliczny i, z własności 1, spójny.
Zatem graf jest drzewem (z definicji drzewa) i, z własności 2, mamy sprzeczność.
Zauważmy, że z powyższych własności wynika np. że aby stwierdzić czy spójny graf jest drzewem wystarczy sprawdzić, czy jego liczba krawędzi jest równa .
Własność 4:
Nieskierowany graf jest drzewem iff gdy istnieje unikatowa ścieżka pomiędzy dwoma wierzchołkami w tym grafie.
Zauważmy, że jeśli między jakimiś dwoma węzłami istniały by dwie różne ścieżki to po połączeniu tych ścieżek otrzymalibyśmy cykl, a więc graf nie byłby drzewem. Ponadto, jeśli w grafie istnieje ścieżka między dowolnymi dwoma węzłami to graf jest spójny.
Cut property:
Załóżmy, że zbiór krawędzi jest podzbiorem krawędzi minimalnego drzewa rozpinającego grafu . Wybierzmy podzbiór wierzchołków taki, że żadne krawędź pomiędzy i nie należy do . Niech będzie krawędzią o minimalnej wadze pomiędzy wierzchołkami z i wierzchołkami . Wtedy jest również podzbiorem krawędzi minimalnego drzewa rozpinającego grafu .
Zauważmy, że jeśli krawędzie są podzbiorem krawędzi minimalnego drzewa rozpinającego grafu oraz wybrana krawędź należy do tego drzewa to wniosek jest oczywisty. Załóżmy zatem, że . Skonstruujemy inne MST , które będą się różnić od MST zmianą jednej krawędzi. Dodajmy zatem krawędź do drzewa . Skoro było spójne to dodanie krawędzi utworzyło cykl. Zatem musi istnieć inna krawędź łącząca wierzchołki ze zbioru z wierzchołkami ze zbioru . Jeśli usuniemy tą krawędź dostaniemy , które będzie drzewem, ponieważ:
Ponadto będzie minimalnym drzewem rozpinającym, ponieważ oraz obie krawędzie i łączą wierzchołki zbioru z wierzchołkami zbioru , a dodatkowo została wybrana tak, że jej waga jest najmniejsza spośród krawędzi przechodzącymi pomiędzy tymi zbiorami. Zatem , co implikuje . Ale skoro było minimalnym drzewem rozpinającym to , a zatem jest również minimalnym drzewem rozpinającym, co kończy dowód.
Zauważmy, że cut property precyzuje które krawędzie można dodawać pomiędzy zbiorami wierzchołków i zakładając, że zbiory te już połączone krawędzią. Możemy zatem zbudować różne algorytmy wyznaczania MST bazując na różnych strategiach wyboru krawędzi do minimalnego drzewa rozpinającego, jeśli strategie te będą wykorzystywać poprawnie cut property.
Dobrą wizualizację cut property daje oraz sekcja w książce Algorithms.
Algorytm ten buduje minimalne drzewo rozpinające dla wejściowego grafu i jest bardzo dobrą ilustracją zachłannego wyboru.
>Do MST dodawaj kolejne krawędzie o najmniejszej wadze, które nie tworzą cyklu.
Zauważmy, że po kilku krokach algorytmu Kruskala mamy częściowe rozwiązanie, które składa się ze zbioru spójnych składowych będących drzewami. Kolejna dodawana krawędź będzie łączyć dwie spójne składowe, powiedzmy oraz . Skoro jest krawędzią o najmniejszej wadze nietworzącą cyklu to jest ona również krawędzią o najmniejszej wadze pomiędzy wierzchołkami należącymi do i . A zatem z cut property jest ona częścią MST, co dowodzi poprawności algorytmu Kruskala.
Ostatnią rzecz, którą pozostaje zrobić aby zaimplementować algorytm Kruskala, to wymyślić w jaki sposób możemy efektywnie stwierdzać, czy wybrana krawędź łączy ze sobą dwie różne komponenty (czyli nie tworzy cyklu). Użyjemy w tym celu struktury zbiorów rozłącznych, która będzie implementować następujące procedury:
makeset
– tworzy jednoelementowy zbiór find
– sprawdza do którego zbioru należy element union
– łączy zbiór, do którego należą elementy ze zbiorem do którego należą elementy Jednym ze sposobów reprezentowania zbiorów jest użycie ukorzenionego drzewa, gdzie:
rank
, które może być chwilowo interpretowana jako wysokość pod-drzewa zawieszonego w danym węźleImplementacja metod struktury dla ukorzenionych drzew:
makeset
:
find
:
while
:
return
union
find
find
if
:
return
if
:
else
:
if
:
Procedura union
łącząc zbiory buduje drzewa reprezentujące te zbiory w taki sposób, że zachowane są następujące własności:
find
oraz union
mają pesymistyczną złożoność obliczeniową dla zbiorów wielkości .W sekcji 5.1.4 książki Algorithms jest pokazane jak można jeszcze usprawnić tę implementację.
Kruskal
:
for all
:
makeset
for all
w kolejności rosnącej:
if
find
find
:
union
Złożoność obliczeniowa: W pesymistycznym przypadku algorytm Kruskala wykona makeset
, find
oraz union
.
Algorytm szukający MST wykorzystując cut property. W przypadku tego algorytmu wybrane krawędzie do zbioru (oznaczenia zgodne z wcześniejszymi z cut property) zawsze formują drzewo (czyli graf spójny, a nie jak w przypadku algorytmu Kruskala rozłączne komponenty), a zbiór wierzchołków jest po prostu zbiorem węzłów tego drzewa tworzonego przez krawędzie .
Prim
:
for all
:
null
MakeQueue
while
:
ExtractMin
for all
:
if
:
DecreaseKey
Algorytm Prima w każdej iteracji pętli while
dodaje krawędź do zbioru (realizowane jest to przez ExtractMin
). Dodawana krawędź ma najmniejszą wagę spośród krawędzi pomiędzy wierzchołkami należącymi do zbioru i , co jest realizacją cut property. Z tego wynika poprawność algorytmu Prima.
Złożoność obliczeniowa: Łatwo zauważyć, że algorytm Prima jest bardzo podobny do algorytmu Dijkstry, a główna różnica polega na tym, że priorytetem wierzchołka w algorytmie Prima jest waga najlżejszej krawędzi łączącej ten wierzchołek z wierzchołkami ze zbioru (w algorytmie Dijkstry była to łączna długość ścieżki od węzła startowego). Zatem algorytm Prima ma taką samą złożoność jak algorytm Dijkstry i jak już wiemy jest ona zależna od implementacji kolejki priorytetowej.