• Nie Znaleziono Wyników

Minimalne drzewo rozpinaj¡ce

Niech G = (V, E) b¦dzie grafem niezorientowanym. Przez drzewo w tym podrozdziale b¦dziemy rozumieli spójny acykliczny graf niezorientowany. Funkcj¦ W : E → R+ze zbioru kraw¦dzi grafu G do zbioru dodatnich liczb rzeczywistych nazywamy funkcj¡

wag. Graf G, na którym jest okre±lona funkcja wag nazywamy grafem z wagami. Dla (u, v) ∈ E waga W (u, v) mo»e oznacza¢ koszt poª¡czenia u z v, lub odlegªo±¢ z u do v, i tym podobne warto±ci.

Problem znajdowania minimalnego drzewa rozpinaj¡cego polega na znalezieniu zbioru kraw¦dzi, które ª¡cz¡ wszystkie wierzchoªki w 'najta«szy' sposób.

Problem (znajdowanie minimalnego drzewa rozpinaj¡cego).

• Dane wej±ciowe: spójny graf niezorientowany G = (V, E) z funkcja wag W .

• Wynik: spójny (i acykliczny) graf niezorientowany G0 = (V, T )taki, »e T ⊆ E oraz waga zbioru T

W (T ) =X

e∈T

W (e) jest minimalna.

Poni»szy algorytm u»ywa struktury danych dla rodziny zbiorów rozª¡cznych. Jest to algorytm zachªanny. Pochodzi on od Kruskala.

Opis algorytmu Kruskala znajdowania minimalnego drzewa rozpinaj¡cego:

1. T = ∅;

2. dla ka»dego wierzchoªka z V tworzymy zbiór {v};

3. sortujemy kraw¦dzie grafu G wzgl¦dem niemalej¡cych wag;

4. przegl¡damy kraw¦dzie (u, v) ∈ E w porz¡dku niemalej¡cych wag i je»eli f ind(u) 6= f ind(v)to wykonujemy

(a) T = T ∪ {(u, v)};

(b) union(u, v) (dodajemy zbiory do których nale»¡ wierzchoªki u i v).

Czas dziaªania algorytmu Kruskala.

1. Na pocz¡tku inicjalizujemy zbiór T jako zbiór pusty i wykonujemy |V | operacji make-set;

2. Nast¦pnie sortujemy zbiór kraw¦dzi E w czasie O(|E| log(|E|);

3. Na koniec dla ka»dej kraw¦dzi z E wykonujemy dwie operacje find i by¢ mo»e po jednej operacji union i przypisania;

4. W sumie algorytm wykonuje O(|E| + |V |) operacji make-set, union, find, w±ród których jest |V | operacji make-set; zatem, z Faktu 6.3 wynika, »e te operacje s¡ wykonane w czasie O(|E| + |V | log(|V |).

Poniewa» graf jest spójny wi¦c |E| ≥ |V | − 1. St¡d otrzymujemy

Fakt 7.19 Czas dziaªania algorytmu Kruskala dla spójnego niezorientowanego grafu z wagami G = (V, E) jest równy O(|E| log(|E|).

Przykªad. W grae z wagami

1 2 3

kraw¦dzie wybrane przez algorytm maj¡ wagi wzi¦te w ramk¦. Zauwa»my, »e mi-nimalne drzewo rozpinaj¡ce zale»y od porz¡dku w jakim przegl¡damy kraw¦dzie o równych wagach. By zbudowa¢ drzewo takie jak zaznaczone na grae algorytm mógª kolejno doª¡cza¢ nast¦puj¡ce kraw¦dzie:

(4, 2), (5, 3), (2, 3), (3, 6), (4, 8), (4, 7), (8, 9), (4, 1).

Zanim uzasadni¦, »e powy»szy algorytm poprawnie znajduje minimalne drzewo rozpinaj¡ce potrzeba kilku denicji.

Niech G = (V, E) b¦dzie grafem niezorientowanym. Podziaªem zbioru V nazy-wamy par¦ (S, V \ S) tak¡, »e S ⊆ V . Kraw¦d¹ (u, v) przecina podziaª (S, V \ S) je±li jeden z jej ko«ców jest w S a drugi w V \ S. Kraw¦d¹ (u, v) ∈ E jest kraw¦dzi¡

lekk¡ dla podziaªu, (S, V \ S), je±li (u, v) przecina ten podziaª oraz ma najmniejsz¡

wag¦ spo±ród wszystkich kraw¦dzi przecinaj¡cych ten podziaª. Niech A ⊆ E b¦dzie podzbiorem minimalnego drzewa rozpinaj¡cego. Podziaª (S, V \ S) respektuje zbiór kraw¦dzi A, gdy »adna kraw¦d¹ z A nie przecina (S, V \ S). Kraw¦d¹ (u, v) ∈ E jest kraw¦dzi¡ bezpieczn¡ dla A, gdy A ∪ {(u, v)} te» jest podzbiorem kraw¦dzi pewnego minimalnego drzewa rozpinaj¡cego.

Uwaga. Kraw¦dzie bezpieczne to takie które maja interesuj¡ce nas wªasno±ci a kraw¦dzie lekkie to takie, które s¡ charakteryzowane przez ªatwy do sprawdzenia warunek. Poni»szy Lemat mówi, »e lekko±¢ gwarantuje bezpiecze«stwo.

Lemat 7.20 Niech G = (V, E) b¦dzie spójnym grafem niezorientowanym z funkcj¡

wag W : E → R+, A ⊆ E podzbiór jakiego± minimalnego drzewa rozpinaj¡cego dla G. Niech podziaª (S, V \ S) respektuje A oraz (u, v) ∈ E b¦dzie kraw¦dzi¡ lekk¡ dla (S, V \ S). Wtedy (u, v) jest kraw¦dzi¡ bezpieczn¡ dla A.

Dowód. Niech podziaª (S, V \ S) respektuje A oraz (u, v) ∈ E b¦dzie kraw¦dzi¡

lekk¡ dla (S, V \ S) oraz (V, T ) b¦dzie minimalnym drzewem rozpinaj¡cym takim,

»e A ⊆ T . Musimy pokaza¢, »e istnieje minimalne drzewo rozpinaj¡ce zawieraj¡ce A ∪ {(u, v)}.

Je±li (u, v) ∈ T to teza jest oczywista. Zaªó»my zatem, »e (u, v) 6∈ T .

Skonstruujemy drzewo T0 ⊆ E takie, »e A ∪ {(u, v)} ⊆ T0 oraz (V, T0) te» jest minimalnym drzewem rozpinaj¡cym. Poniewa» (V, T ) jest spójny, to istnieje ±cie»ka z u do v w (V, T ). Poniewa» (u, v) przecina (S, V \ S), to na tej ±cie»ce w (V, T )

istnieje co najmniej jedna kraw¦d¹ (x, y) ∈ T i przecinaj¡ca (S, V \ S). Poniewa»

(V, T )jest spójny i acykliczny, to (V, T \ {x, y}) ma dokªadnie dwie skªadowe, które ª¡czy kraw¦d¹ (u, v). Zatem kªad¡c T0 = (T \ {(x, y)}) ∪ {(u, v)} otrzymujemy (V, T0) jako spójny i acykliczny graf. Musimy jeszcze pokaza¢, »e W (T ) = W (T0).

Z minimalno±ci T mamy, »e W (T ) ≤ W (T0). Z drugiej strony W (T0) − W (T ) = X

e∈T0

W (e) −X

e∈T

W (e) = W (u, v) − W (x, y) ≤ 0

gdzie ostatnia nierówno±¢ wynika st¡d, »e (u, v) i (x, y) przecinaj¡ podziaª (S, V \ S) oraz (u, v) jest lekka dla tego podziaªu. Zatem W (T ) ≥ W (T0).

Q.E.D.

Wniosek 7.21 Niech G = (V, E) b¦dzie spójnym grafem niezorientowanym z funkcj¡

wag W : E → R+, A ⊆ E podzbiór jakiego± minimalnego drzewa rozpinaj¡cego dla G.

Niech C b¦dzie skªadow¡ spójn¡ grafu GA = (V, A). Je±li (u, v) ∈ E jest kraw¦dzi¡

lekk¡ dla (C, V \ C) to (u, v) jest kraw¦dzi¡ bezpieczn¡ dla A.

Dowód. Poniewa» C jest skªadow¡ spójn¡ grafu GA, to podziaª (C, V \ C) re-spektuje A. Zatem z Lematu 7.20 otrzymujemy tez¦ Wniosku.

Q.E.D.

W ten sposób pokazali±my

Twierdzenie 7.22 Algorytm Kruskala znajdowania minimalnego drzewa rozpinaj¡-cego jest poprawny.

Poni»ej opiszemy inny algorytm znajdowania minimalnego drzewa rozpinaj¡cego, pochodz¡cy od Prima. Jest on te» algorytmem zachªannym. U»ywa on innej ciekawej struktury danych: kolejki priorytetowej. Najpierw opiszemy dziaªanie samej kolejki.

Kolejka priorytetowa

Kolejka priorytetowa jest to struktura danych na której mo»na wykonywa¢ na-st¦puj¡ce operacje:

1. buduj-kolejke - maj¡c dany zbiór V i funkcj¦ priorytetu d : V → R tworzy kolejk¦ Q;

2. dodaj(Q,v,x) - dodaje do kolejki Q element v z priorytetem x;

3. min(Q) - zwraca element kolejki Q o najni»szym priorytecie;

4. usun(Q) - usuwa z kolejki Q element o najni»szym priorytecie;

5. zmniejsz-klucz(Q,v,x) - zmniejsza priorytet wierzchoªka v do warto±ci x i aktualizuj kolejk¦;

Kolejk¦ priorytetow¡ mo»na implementowa¢ w tablicy przy pomocy kopca takiego jaki byª tworzony przy okazji sortowania przez kopcowanie. Poni»sza implementacja kolejki jest przystosowana do tego by wygodnie obsªugiwaªa kolejki wierzchoªków u»ywane przez algorytmy Prima i Dijkstry. W szczególno±ci elementy przechowywane w kolejce to liczby ze zbioru {1, . . . , n}.

float d[N]; // tablica priorytetow typedef struct element{

float key;

int el;

} element;

element Q[N]; //kolejka

int S[i]; // pozycja i w kolejce: Q[S[i]].el=i; S[i]=-1 gdy i nie ma w kolejce int koniec; // liczba elementow w kolejce

void w-dol(Q,i,j) //odpowiada procedurze 'kopcuj(i,j)' { l=2*i+1; p=2*i+2; w=i;

if ((l<=j) && (Q[l].klucz<Q[i].klucz)) w=l;

if ((p<=j) && (Q[p].klucz<Q[w].klucz)) w=p;

//teraz w Q[w] jest najmniejszy klucz (priorytet) z Q[i],Q[l],Q[p]

if (w!=i) {

zamien(Q[w],Q[i]);

S[Q[w].el]=w;

S[Q[i].el]=i;

w-dol(Q,w,j);

} }

void w-gore(Q,i)

//odpowiada procedurze 'kopcuj' ale w odwrotna strone tzn. do gory { while ((i>0) && (Q[(i-1)/2].klucz>Q[i].klucz) {

zamien(Q[(i-1) / 2],Q[i]);

S[Q[(i-1) / 2].el]=i/2;

S[Q[i].el]=i;

} }

void buduj-kolejke(Q,d)

//buduje N elementowa kolejke Q z priorytetami z d { for (i=0;i<N;i++){

Q[i].klucz=d[i];

Q[i].el=i;

S[i]=i;

}for (i= n/2;i>0;i--){

w-dol(Q,i,n);

koniec=n;

}

int min(Q)

//zwraca element o minimalnym priorytecie { return mi=Q[i].el;}

int pusta(Q) //test pustosci kolejki { return (koniec==-1);}

void usun(Q)

//usuwa element o minimalnym priorytecie z Q { S[Q[0].el]=-1;

Q[0]=Q[koniec];

S[Q[0].el]=0;

koniec--;

w-dol[Q,0,koniec];

}

void dodaj(Q,v,x)

//dodaje do Q element v o priorytecie x {

koniec++;

Q[koniec].el=v;

Q[koniec].klucz=x;

S[koniec]=v;

w-gore[Q,koniec];

}

void zmniejsz_klucz(Q,v,x)

//zmniejsza klucz elementu v do x i odtwarza //strukture kolejki priorytetowej w Q

{ d[S[v]]=x;

w-gore[Q,S[v]];

}

Algorytmu Prima znajdowania minimalnego drzewa rozpinaj¡cego

Algorytm Prima inaczej ni» algorytm Kruskala wybiera kraw¦d¹ lekk¡. W czasie dziaªania algorytmu kraw¦dzie wybrane przez algorytm {(v, π[v]) | v ∈ V − Q − {r}}

stanowi¡ poddrzewo (tzn. podgraf spójny i acykliczny) grafu G.

void Prim(G,w,r) { koniec=N-1;

for (i=0;i<N;i++) d[i]=nieskonczonosc;

//nieskonczonosc = liczba wieksza od wszystkich wag w grafie buduj_kolejke(Q,d,koniec);

zmniejsz_klucz(Q,r,0); P[r]=NULL;

while (!pusta(Q)) { u=min(Q); usun(Q);

for (v in LI(u)) {

if ((S[v]!=-1) && (w[u][v]<d[v])) { P[v]=u;

zmniejsz_klucz(Q,v,w[u][v]);

} }

} }

Po wykonaniu procedury Prim(G,w,r) graf (V, T ) jest minimalnym drzewem rozpinaj¡cym , gdzie

T = {(P [v], v)) : v ∈ V, P [n] 6= nil}.

Czas dziaªania algorytmu Prima.

1. Na pocz¡tku inicjalizujemy tablic¦ d w czasie O(|V |);

2. Potem inicjalizujemy kolejk¦ priorytetow¡ Q w czasie O(|V |)

3. P¦tla while (bez wewn¦trznej p¦tli for) jest wykonywana O(|V |) razy.

4. Š¡czna liczba wykona« p¦tli for jest O(|E|), w tej p¦tli instrukcja zmniejsz_klucz jest wykonywana w czasie O(ln |V |).

Fakt 7.23 Czas dziaªania algorytmu Prima dla spójnego niezorientowanego grafu z wagami G = (V, E) jest równy O(|E| ln(|V |).

Uwaga. Zauwa»my, »e mamy O(|E| ln |E|) = O(|E| ln |V |). Zatem czas dziaªania algorytmu Kruskala i algorytmu Prima (przy naszym sposobie implementacji kolejki priorytetowej) jest taki sam. Istnieje bardziej efektywny sposób implementacji kolejki priorytetowej przy którym algorytm Prima dziaªa w czasie O(|E| + |V | ln |V |).