Graphalgorithmen
Seminar parallele Programmierung SS 2003
Bernd Kruthoff und Jochen Olejnik
1
Gliederung
1. Motivation
2. Verbundene Komponenten
- Hirschbergs Algorithmus
3. Minimal Spannender Baum
- Kruskals Algorithmus
- Sollins Algorithmus
4. Kürzeste Pfade von einem Knoten ausgehend
- Moores Algorithmus
5. Zusammenfassung
2
1. Motivation
•
•
•
•
Probleme der Praxis werden komplexer
Lassen sich häufig durch Graphen darstellen
Herkömmliche Algorithmen stoßen an Grenzen
Lösung: Parallelisierung bekannter Algorithmen
3
Verbundene Komponenten
• Finden aller verbundenen Komponenten in einem ungerichteten
Graphen
3 mögliche Ansätze:
A. Suchalgorithmen
• durch Breiten- bzw. Tiefensuche durch den kompletten Graphen
4
Verbundene Komponenten
B. Transitive Hülle
• Grundlage: Adjazenzmatrix
• Bestimmen der transitiven Hülle durch log n Plus-MinMultiplikationen
• Plus-Min-Multiplikation ist Matrixmultiplikation bei der
Skalamultiplikationen durch Additionen und Additionen durch
Minimumoperationen ersetzt werden
• => Strukturanalogie zur Matrixmultiplikation:
• => Laufzeit: log n für eine Plus-Min-Multiplikation
• => log2 n für log n Plus-Min-Multiplikationen
5
C. Hirschbergs Algorithmus
• Grundidee: Knoten zu Knotengruppen zusammenfassen bis kein
weiteres Zusammenfassen mehr möglich ist
• Jeder Knoten gehört zu genau einer Knotengruppe
• Knotengruppen werden durch Wurzel (hier: kleinstes Element)
identifiziert
Der Algorithmus:
1. Schritt: Zu jedem Knoten wird die angrenzenden Knotengruppe
mit der kleinsten Wurzel gesucht
2. Schritt: Verbinden der Wurzeln der in Schritt 1 gefundenen
Knotengruppen
3. Schritt: Die in Schritt 2 gefundenen Knotengruppen werden zu
einer Knotengruppe zusammengefasst
• Endet, wenn es in Schritt 1 keine angrenzende Knotengruppe
mehr gibt
6
Ein Beispiel:
Die Ausgangssituation:
1
4
9
2
7
8
3
6
Knoten
Knotengruppe
10
11
5
1 2 3 4 5 6 7 8 9
10
11
1 2 3 4 5 6 7 8 9
10
11
7
Beispiel: 1. Iteration
1
4
9
2
7
8
3
6
10
11
5
1. Schritt: Zu jedem Knoten wird die angrenzende Knotengruppe
mit der kleinsten Wurzel gesucht
1
4
9
2
7
8
3
6
10
11
5
8
Beispiel: 1. Iteration
2. Schritt: Verbinden der Wurzeln der in Schritt 1 gefundenen
Knotengruppen
1
4
9
2
7
8
3
6
10
11
5
9
Beispiel: 1. Iteration
1
4
9
2
7
8
3
6
10
11
5
3. Schritt: Die in Schritt 2 gefundenen Knotengruppen werden
zu einer Knotengruppe zusammengefasst
1
4
9
2
7
8
3
6
10
11
5
10
Beispiel: 1. Iteration Ergebnis
1
4
9
2
7
8
3
6
10
11
5
Knoten
1
2
3
4
5
6
7
8
9
10
11
Knotengruppe
1
2
2
1
5
2
1
2
2
5
5
11
Beispiel: 2. Iteration
1
4
9
2
7
8
3
6
10
11
Letzte Iteration:
1
4
9
2
7
8
3
6
5
10
11
Startgraph:
5
1. Schritt: Zu jedem Knoten wird die angrenzenden Knotengruppe
mit kleinster Wurzel gesucht
1
4
9
2
7
8
3
6
10
11
5
12
Beispiel: 2. Iteration
1
4
9
2
7
8
3
6
10
11
5
2. Schritt: Verbinden der Wurzeln der in Schritt 1 gefundenen
Knotengruppen
1
4
9
2
7
8
3
6
10
11
5
13
Beispiel: 2. Iteration
1
4
9
2
7
8
3
6
10
11
5
3. Schritt: Die in Schritt 2 gefundenen Knotengruppen werden
zu einer Knotengruppe zusammengefasst
1
4
9
2
7
8
3
6
10
11
5
14
Beispiel: 2. Iteration Ergebnis
1
4
9
2
7
8
3
6
Knoten
1
2
3
4
5
6
7
8
9
10
11
Knotengruppe
1
1
1
1
5
1
1
1
1
5
5
10
11
5
15
Komplexität
• Der Algorithmus benötigt log n Iterationen weil sich die Anzahl
der Knotengruppen mit jeder Iteration mindestens halbiert
• Es werden n2 Prozessoren benötigt, weil maximal n benachbarte
Knotengruppen pro Knoten verglichen werden müssen.
• => Gesamtkomplexität log2 n
16
Verbesserungen
• Betrachten von Brents Theorem:
• Es reichen log n Prozessoren aus um n Elemente anzusprechen
und deren Minimum in log nZeit zu finden.
• Jeder Prozessor kann log n Elemente anstatt nur einem ansprechen
oder das Minimum aus log n Elementen berechnen, anstatt nur das
Minimum aus zwei Elementen ohne die Zeitkomplexität zu
verändern.
• Der Algorithmus benötigt also log2 n Zeit bei n logn n Prozessoren.
17
Eine Implementierung
• Implementierung von Hirschbergs Algorithmus auf das 2D
Mesh SIMD Rechnermodell (Nassimi, Sahni)
• 3 neue Befehle:
• random.read(a,[b]c) liest den Wert der Variablen c im Bereich
von Prozess b in die Variable a ein
• random.write(a,[b]c) schreibt den Wert der Variablen a in die
Variable c im Bereich von Prozess b
• Laufzeit dieser Operationen ist nur vom Rechner abhängig =>
konstant in Bezug auf die Anzahl der Elemente
• bits(i,j,k) gibt den Wert der Stellen j bis k der Zahl i zurück
• z. B. bits (9,3,2) : 9 ist binär 0101 zweite und dritte Stelle: 01
also wird 1 zurückgegeben
• Ist j<k wird 0 zurückgegeben
18
public VERBUNDENE KOMPONENTEN():
Parameter:
d
//maximaler Knotengrad
n
//Anzahl Knoten = Anzahl Prozessoren
Globale Variablen:k
//betrachtete Kante
iteration
//Nummer der Iteration
Lokale Variablen: kandidat
//Wurzel des Nachbarknotens
nachbar[1...d] //Nachbarknoten der aktuellen Knotengruppe
w
//Wurzel
1. begin
2.
for (alle Pi where 1  i  n ){
3.
w = i;
jeder Knoten wird Wurzel
4.
}
5.
for (iteration = 0 tolog n  1 ){
6.
for (alle Pi where 1  i  n ){
es gibt keine bekannten Nachbarn
7.
kandidat=;
=>Entfernung zu Nachbarn wird auf
8.
}
 gesetzt
9.
for (k=1 to d){
10.
for (alle Pi 1  i  n where ){
Suchen des Nach11.
HOLEN.UND.VERGLEICHEN(nachbar[k], w, kandidat); barknotens mit
12.
}
der kleinsten
13.
}
Wurzel
14.
for (alle Pi where 1  i  n ){
15.
AKTUALISIERE.WURZEL(kandidat, w);
16.
}
17.
ZUSAMMENFASSEN(w, n);
//Zusammenfügen der Knotengruppen
18.
}
19. end
19
private HOLEN.UND.VERGLEICHEN(v, w, kandidat); //entspricht 1. Schritt
übergebene Werte:
v
//Nachbarknoten
w, kandidat
lokale Variablen:
tmp
//Wurzel des Nachbarknotens
20. begin
21.
random.read(tmp, [v]w);
22.
if (tmp==w){
23.
tmp=;
Prüfen, ob v schon kandidat als Wurzel
24.
}
hat
25.
kandidat=min(kandidat, tmp);
26. end
private AKTUALISIERE.WURZEL(kandidat, w); //entspricht 2. Schritt
übergebene Werte:
kandidat, w
27. begin
28.
random.write(kandidat[w]w);
29.
if (w==){
hat diese Knotengruppe überhaupt
30.
w=i;
Nachbarn?
31.
}
32.
if (w>i){
33.
random.read(w, [w]w);
gibt es einen Zyklus?
34.
}
35. end
20
private ZUSAMMENFASSEN(w, n);
//entspricht 3. Schritt
übergebene Werte: w, n
36. begin
37.
for (b=1 to log(n)){
38.
for (alle Pi){
39.
if (bits(w, log(n)-1, b)==bits(i, log(n), b)){
40.
random.read(w, [w]w);
41.
}
42.
}
43.
}
44. end
21
Laufzeit dieser Implementierung
22
1. begin
2.
for (alle Pi where 1  i  n ){
3.
w = i;
jeder Knoten wird Wurzel
4.
}
5.
for (iteration = 0 tolog n  1 ){
6.
for (alle Pi where 1  i  n ){
es gibt keine bekannten Nachbarn
7.
kandidat=;
=>Entfernung zu Nachbarn wird auf
8.
}
 gesetzt
9.
for (k=1 to d){
10.
for (alle Pi 1  i  n where ){
Suchen des Nach11.
HOLEN.UND.VERGLEICHEN(nachbar[k], w, kandidat); barknotens mit
12.
}
der kleinsten
13.
}
Wurzel
14.
for (alle Pi where 1  i  n ){
15.
AKTUALISIERE.WURZEL(kandidat, w);
16.
}
17.
ZUSAMMENFASSEN(w, n);
//Zusammenfügen der Knotengruppen
18.
}
19. end
• Loop (Zeilen 5-18) wird log n mal durchlaufen
• In innerem Loop (Zeilen 9-13) wird d mal die
Minimumoperation angewendet, welche O(n) Zeit verbraucht
23
Laufzeit dieser Implementierung
• Loop (Zeilen 5-18) wird log n mal durchlaufen
• In innerem Loop (Zeilen 9-13) wird d mal die
Minimumoperation angewendet, welche O(n) Zeit verbraucht
 Auf dem 2D mesh-SIMD-Rechner mit n Prozessoren und n=2k
Knoten und Maximalwert d hat dieser Algorithmus eine
Komplexität von O(dn log n).
24
Minimal Spannender Baum
• Finden des Minimal Spannenden Baumes in einem
ungerichteten verbundenen gewichteten Graphen
• Kruskals Algorithmus
• Idee: Alle Knoten sind anfangs Bäume
• Pro Iteration wird nun die kleinste noch nicht im Minimal
Spannenden Baum vorhandene Kante eingefügt, falls dadurch
kein Zyklus entstünde
25
Beispiel Kruskals Algorithmus
A
4
2
7
A
4
D
4
2
B
C
3
B
8
4
D
F
6
1
5
2
8
A
E
F
C
3
7
2
G
2
B
5
7
6
2
G
B
4
D
4
8
4
D
E
5
2
8
F
6
1
F
C
3
7
2
C
3
A
E
1
4
G
E
6
1
5
G
26
Beispiel Kruskals Algorithmus
A
4
2
B
A
4
4
D
6
5
2
8
F
A
E
1
F
C
3
7
8
D
4
2
B
C
3
7
2
G
4
2
B
C
3
7
2
4
D
8
F
E
6
1
5
G
E
6
1
5
G
27
Der Heap als Datenstruktur
• Parallelisierung durch geschickte Wahl der Datenstruktur
• Hier bietet sich der Heap an! => Gute Laufzeiteigenschaften
• Hier wird der Heap mit -wertigen Knoten zu einem vollständigen
Binärbaum ausgebaut
• Lemma: Man kann mit einer UMA-Multiprozessormaschine mit
log nProzessoren ein Element aus einer n-elementigen Menge in
konstanter Zeit aus dem Heap auslesen.
28
Der Heap als Datenstruktur
• Jede Ebene hat Flag mit Wert => voll wenn alle Knoten belegt
=> leer wenn es leere Knoten in Ebene gibt
• Wert empty_node zeigt pro Ebene Knoten, in dem Wert fehlt
• Jeder Prozessor ist für eine Ebene zuständig. Wird ein Knoten
leer so füllt ihn der zuständige Prozessor aus dem kleinsten
Element der Kinder dieses Knotens wieder auf
• Wird ein Blatt leer, so wird der Wert  eingetragen
• Heapaufbau kann durch Prescheduling parallelisiert werden
29
Ein Beispielheap
E
Heap
1
1
5
2
2
3
7
5
4
3
4
6
9
10
25
18
11 12
EN
Voll
kein
Leer
2
Leer
6
Voll
kein
7
21
8
F
12
30
14
27
15
30
30
Der Gesamtalgorithmus
• Aufbauen des Heap
• Prozess entnimmt fortlaufend Knoten aus dem Heap und
überprüft, ob sie zum Minimal Spannenden Baum gehören
• Die restlichen Prozesse stellen währenddessen den Heap wieder
her
• Terminiert, wenn  ausgelesen wird
• Laufzeit: Wiederaufbau des Heap und Überprüfung auf Zyklus
(Find und Union) geschieht in konstanter Zeit
• Entnahme der Knoten in konstanter Zeit möglich
• =>Gesamtalgorithmus benötigt O(n) Zeit
31
Wechsel....
32

Olejni/a