06.06.2014 Aufrufe

Parallele Algorithmen - Ra.informatik.tu-darmstadt.de - Technische ...

Parallele Algorithmen - Ra.informatik.tu-darmstadt.de - Technische ...

Parallele Algorithmen - Ra.informatik.tu-darmstadt.de - Technische ...

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

Globaler Zellularautomat:<br />

<strong>Parallele</strong> <strong>Algorithmen</strong><br />

Diplomarbeit<br />

von<br />

Christine Ehrt<br />

Prüfer: Prof. Dr. Rolf Hoffmann<br />

Betreuer: Dipl.-Ing. Wolfgang Heenes<br />

Fachgebiet Rechnerarchitek<strong>tu</strong>r<br />

Fachbereich Informatik<br />

<strong>Technische</strong> Universität Darmstadt<br />

29. November 2005


Erklärung<br />

Ich versichere, dass ich diese Arbeit ohne unzulässige frem<strong>de</strong> Hilfe und nur unter Benutzung<br />

<strong>de</strong>r in <strong>de</strong>r Arbeit angegebenen Litera<strong>tu</strong>r und sonstigen Hilfsmitteln angefertigt<br />

habe.<br />

Darmstadt, <strong>de</strong>n 29. November 2005<br />

Danksagung<br />

Im <strong>Ra</strong>hmen meines S<strong>tu</strong>diums und meiner Diplomarbeit haben mir viele Menschen sehr<br />

geholfen. Bei einigen möchte ich mich gerne persönlich bedanken:<br />

Dipl.-Ing. Wolfgang Heenes, <strong>de</strong>r mit seinem Repititorium erstmals in mir das Interesse<br />

für die <strong>Technische</strong> Informatik weckte und dies dann mit <strong>de</strong>m Rechnertechnologiepraktikum<br />

noch vertiefte. Außer<strong>de</strong>m war er während meiner Diplomarbeitsphase immer für<br />

mich da und hat eine für mich individuell sehr intensive und gute Betreuung geboten.<br />

Prof. Dr. Rolf Hoffmann, <strong>de</strong>r mit seinen für mich interessanten Vorlesungen dieses Interesse<br />

noch weiter vertieft hat und mir schließlich auch ermöglichte, in diesem Gebiet<br />

meine Diplomarbeit zu schreiben.<br />

Dipl.-Inform. Mathias Halbach, <strong>de</strong>r mir bei Problemen <strong>de</strong>r Implementierung geholfen<br />

hat.<br />

Herrn Endisch, <strong>de</strong>r sein 1999 gegebenes Versprechen eingehalten und meine gesamte<br />

Diplomarbeit auf grammatikalische und Rechtschreibfehler überprüft hat. Außer<strong>de</strong>m<br />

hat er mich mit seinen Anmerkungen oft zum Nach<strong>de</strong>nken gebracht und somit zu einer<br />

besseren Arbeit beigetragen.<br />

Meinen Freun<strong>de</strong>n, die mir sowohl als Stütze als auch Korrek<strong>tu</strong>rleser zur Seite stan<strong>de</strong>n:<br />

Marcel Thies, Silke Schnei<strong>de</strong>r, Denis Endro und Stefan Müller.<br />

Ihnen allen spreche ich hiermit meinen herzlichen Dank aus und hoffe, mit meiner Diplomarbeit<br />

ihre Erwar<strong>tu</strong>ngen erfüllt zu haben.<br />

2


Inhaltsverzeichnis<br />

1. Einlei<strong>tu</strong>ng 10<br />

1.1. Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />

1.2. Aufwandsbetrach<strong>tu</strong>ng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

1.3. Struk<strong>tu</strong>r <strong>de</strong>r Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

2. Zellularautomaten und an<strong>de</strong>re Mo<strong>de</strong>lle 13<br />

2.1. Die <strong>Ra</strong>ndom-Access-Maschinen . . . . . . . . . . . . . . . . . . . . . . . 13<br />

2.1.1. Prinzip <strong>de</strong>r RAM . . . . . . . . . . . . . . . . . . . . . . . . . . . 14<br />

2.1.2. Die P-RAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14<br />

2.2. Die Pointer-Maschinen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />

2.2.1. Maschinenmo<strong>de</strong>lle <strong>de</strong>r Pointer-Maschinen . . . . . . . . . . . . . . 16<br />

2.2.1.1. Kolmogorov-Uspenskii Maschinen . . . . . . . . . . . . . 16<br />

2.2.1.2. Storage Modification Maschines . . . . . . . . . . . . . . 16<br />

2.2.1.3. Knuths Linking Automat . . . . . . . . . . . . . . . . . 17<br />

2.2.1.4. Die LISP Maschinen . . . . . . . . . . . . . . . . . . . . 17<br />

2.2.2. Programmiermo<strong>de</strong>lle <strong>de</strong>r Pointer-Maschinen . . . . . . . . . . . . 17<br />

2.2.3. Parallel-Pointer Maschinen . . . . . . . . . . . . . . . . . . . . . . 18<br />

2.3. Die Zellularautomaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />

2.3.1. Der klassische Cellular Automata“ (CA) . . . . . . . . . . . . . 19<br />

”<br />

2.3.1.1. Tesselation CA . . . . . . . . . . . . . . . . . . . . . . . 20<br />

2.3.1.2. Iterative CA . . . . . . . . . . . . . . . . . . . . . . . . 21<br />

2.3.1.3. Dynamische CA . . . . . . . . . . . . . . . . . . . . . . 21<br />

2.3.1.4. CA Networks (CAN) . . . . . . . . . . . . . . . . . . . . 21<br />

2.3.2. Der Struc<strong>tu</strong>rally Dynamic Cellular Automata“ (SDCA) . . . . . 21<br />

”<br />

2.3.2.1. Das Relative Location Mo<strong>de</strong>ll . . . . . . . . . . . . . . . 22<br />

2.3.2.2. Das Labeled Link Mo<strong>de</strong>ll . . . . . . . . . . . . . . . . . 23<br />

2.3.2.3. Das Symmetric Mo<strong>de</strong>ll . . . . . . . . . . . . . . . . . . . 23<br />

2.3.3. Der Dynamic Struc<strong>tu</strong>re Cellular Automata“ (DSCA) . . . . . . . 23<br />

”<br />

2.3.4. Der Global Cellular Automata“ (GCA) . . . . . . . . . . . . . . 24<br />

”<br />

3. Die Mo<strong>de</strong>llierung von Graphenalgorithmen auf <strong>de</strong>m GCA 27<br />

3.1. Erkennung von zusammenhängen<strong>de</strong>n Komponenten eines Graphen . . . . 28<br />

3.1.1. Der Warshall-Algorithmus . . . . . . . . . . . . . . . . . . . . . . 28<br />

3.1.1.1. Der Warshall auf einem Einprozessor-System . . . . . . 29<br />

3


3.1.1.2. Der Warshall-Algorithmus auf <strong>de</strong>m GCA . . . . . . . . . 30<br />

3.1.2. Der Floyd-Warshall-Algorithmus . . . . . . . . . . . . . . . . . . 34<br />

3.1.3. Der Algorithmus von Hirschberg et al. . . . . . . . . . . . . . . . 36<br />

3.1.3.1. Ablauf auf <strong>de</strong>r P-RAM . . . . . . . . . . . . . . . . . . . 36<br />

3.1.3.2. Komplexitätsbetrach<strong>tu</strong>ng auf <strong>de</strong>r P-RAM . . . . . . . . 42<br />

3.1.3.3. Mo<strong>de</strong>llierung auf <strong>de</strong>m GCA . . . . . . . . . . . . . . . . 46<br />

3.1.3.4. Komplexitätsbetrach<strong>tu</strong>ng und Verbesserungen auf <strong>de</strong>m<br />

GCA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

3.1.3.4.1. Speicherzellenmo<strong>de</strong>llierung . . . . . . . . . . . . 49<br />

3.1.3.4.2. Laufzeitkomplexitätsverbesserung . . . . . . . . 53<br />

3.2. Minimal aufspannen<strong>de</strong> Bäume . . . . . . . . . . . . . . . . . . . . . . . . 54<br />

3.2.1. Der Kruskal-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . 54<br />

3.2.1.1. Der Heap-Sort . . . . . . . . . . . . . . . . . . . . . . . 55<br />

3.2.1.2. Der Heap-Sort auf <strong>de</strong>m GCA . . . . . . . . . . . . . . . 58<br />

3.2.1.3. Der Kruskal auf <strong>de</strong>m GCA . . . . . . . . . . . . . . . . . 65<br />

3.2.2. Modifikation <strong>de</strong>s Hirschberg für minimal aufspannen<strong>de</strong> Bäume . . 68<br />

3.2.2.1. Realisierung auf <strong>de</strong>m GCA . . . . . . . . . . . . . . . . 73<br />

3.2.3. Derzeitiger Forschungsstand . . . . . . . . . . . . . . . . . . . . . 74<br />

3.3. NP-vollständige Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . 75<br />

3.3.1. Das Graphenfärbbarkeitsproblem . . . . . . . . . . . . . . . . . . 76<br />

3.3.1.1. Kantenfärbung von bipartiten Graphen . . . . . . . . . . 78<br />

3.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88<br />

4. Die Mo<strong>de</strong>llierung von Krypto-<strong>Algorithmen</strong> auf <strong>de</strong>m GCA 91<br />

4.1. Der erweiterte euklidische Algorithmus . . . . . . . . . . . . . . . . . . . 91<br />

4.2. Die Anwendung <strong>de</strong>s Chinesische Restsatzes . . . . . . . . . . . . . . . . . 93<br />

4.3. Einordnung <strong>de</strong>r Anwendung <strong>de</strong>s Chinesischen Restsatzes . . . . . . . . . 97<br />

5. Implementierungseigenheiten 99<br />

6. Zusammenfassung und Ausblick 101<br />

A. Traversierungsstrategien 103<br />

A.1. Backtracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103<br />

A.2. Divi<strong>de</strong> and Conquer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106<br />

B. <strong>Parallele</strong> Algorithmische Techniken 109<br />

B.1. Die Balanced-Binary-Tree-Technik . . . . . . . . . . . . . . . . . . . . . . 109<br />

B.2. Die Doubling-Technik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110<br />

B.3. Die Divi<strong>de</strong>-and-Conquer-Technik . . . . . . . . . . . . . . . . . . . . . . 111<br />

C. Implementierungen <strong>de</strong>r <strong>Algorithmen</strong> 112<br />

C.1. Warshall-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112<br />

C.2. Hirschberg-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . 113<br />

C.3. Kruskal-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115<br />

4


C.4. Modifizierter Hirschberg-Algorithmus . . . . . . . . . . . . . . . . . . . . 117<br />

C.5. Euler-Färbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119<br />

C.6. Chinesischer Restsatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125<br />

5


Abbildungsverzeichnis<br />

2.1. Darstellung <strong>de</strong>r Nachbarschaftsbeziehung nach von Neumann . . . . . . . 19<br />

2.2. Darstellung <strong>de</strong>r Nachbarschaftsbeziehung nach Moore . . . . . . . . . . . 20<br />

2.3. [CNG + 01]: CAN network of the Sarno SCIDDICA mo<strong>de</strong>l . . . . . . . . . 22<br />

2.4. Entscheidungsbaum zur Bestimmung <strong>de</strong>r Verän<strong>de</strong>rung <strong>de</strong>r Daten . . . . 26<br />

3.1. Beispielgraph Warshall . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

3.2. Schematische Darstellung <strong>de</strong>s Warshall-Algorithmus auf <strong>de</strong>m GCA . . . . 31<br />

3.3. Zelleninitialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />

3.4. Ablauf <strong>de</strong>s Warshall-Algorithmus in <strong>de</strong>r Theorie . . . . . . . . . . . . . . 32<br />

3.5. Ablauf <strong>de</strong>s Warshall-Algorithmus auf <strong>de</strong>m GCA . . . . . . . . . . . . . . 33<br />

3.6. Beispielgraph Hirschberg . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

3.7. Hirschberg Ergebnis erster Schritt, erster Durchlauf . . . . . . . . . . . . 39<br />

3.8. Hirschberg Ergebnis vierter Schritt, erster Durchlauf . . . . . . . . . . . 39<br />

3.9. Hirschberg Ergebnis fünfter Schritt, erster Durchlauf . . . . . . . . . . . 40<br />

3.10. Hirschberg Ergebnis erster Schritt, zweiter Durchlauf . . . . . . . . . . . 40<br />

3.11. Hirschberg Ergebnis zweiter Schritt, zweiter Durchlauf . . . . . . . . . . 41<br />

3.12. Hirschberg Ergebnis vierter Schritt, zweiter Durchlauf . . . . . . . . . . . 41<br />

3.13. Hirschberg fünfter Schritt, zweiter Durchlauf . . . . . . . . . . . . . . . . 42<br />

3.14. Minimumbestimmung mit <strong>de</strong>r Doubling-Technik . . . . . . . . . . . . . . 44<br />

3.15. Balanced-Binary-Tree in <strong>de</strong>r Graphenrepräsentation . . . . . . . . . . . . 45<br />

3.16. Schematische Darstellung <strong>de</strong>s Hirschbergs auf <strong>de</strong>m GCA . . . . . . . . . 47<br />

3.17. Speicherzellenrealisierung 1 . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

3.18. Speicherzellenrealisierung 1a . . . . . . . . . . . . . . . . . . . . . . . . . 50<br />

3.19. Speicherzellenrealisierung 1b . . . . . . . . . . . . . . . . . . . . . . . . . 51<br />

3.20. Speicherzellenrealisierung 2 . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

3.21. Min-Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

3.22. In einen Heap eingelesener Vektor . . . . . . . . . . . . . . . . . . . . . . 56<br />

3.23. Min-Heap nach <strong>de</strong>m Entfernen <strong>de</strong>s ersten Elements . . . . . . . . . . . . 57<br />

3.24. Erster Schritt zur Wie<strong>de</strong>rherstellung <strong>de</strong>r Heap-Eigenschaft . . . . . . . . 57<br />

3.25. Der wie<strong>de</strong>r hergestellte Heap . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />

3.26. Darstellung <strong>de</strong>s Heaps auf <strong>de</strong>m GCA . . . . . . . . . . . . . . . . . . . . 59<br />

3.27. Beispiel <strong>de</strong>s parallel ablaufen<strong>de</strong>n Heap-Sorts, Bild1 . . . . . . . . . . . . 59<br />

3.28. Beispiel <strong>de</strong>s parallel ablaufen<strong>de</strong>n Heap-Sorts, Bild2 . . . . . . . . . . . . 60<br />

3.29. Beispiel <strong>de</strong>s parallel ablaufen<strong>de</strong>n Heap-Sorts, Bild3 . . . . . . . . . . . . 60<br />

6


3.30. Beispiel <strong>de</strong>s parallel ablaufen<strong>de</strong>n Heap-Sorts, Bild4 . . . . . . . . . . . . 61<br />

3.31. Beispiel <strong>de</strong>s parallel ablaufen<strong>de</strong>n Heap-Sorts, Bild5 . . . . . . . . . . . . 61<br />

3.32. Beispiel <strong>de</strong>s parallel ablaufen<strong>de</strong>n Heap-Sorts, Bild6 . . . . . . . . . . . . 62<br />

3.33. Beispiel <strong>de</strong>s parallel ablaufen<strong>de</strong>n Heap-Sorts, Bild7 . . . . . . . . . . . . 62<br />

3.34. Repräsentation <strong>de</strong>r Ebenen <strong>de</strong>s Baums durch die Zellen <strong>de</strong>s GCA . . . . 63<br />

3.35. Realisierung <strong>de</strong>s Kruskal-Algorithmus auf <strong>de</strong>m GCA . . . . . . . . . . . . 66<br />

3.36. Realisierung <strong>de</strong>s Kruskal-Algorithmus ohne Bus . . . . . . . . . . . . . . 67<br />

3.37. Realisierung <strong>de</strong>s Kruskal-Algorithmus mit Bus-Zelle . . . . . . . . . . . . 67<br />

3.38. Beispielgraph für <strong>de</strong>n Hirschbergalgorithmus zur Berechnung minimaler<br />

Spannbäume . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71<br />

3.39. Teilergebnisgraph nach <strong>de</strong>m ersten Schritt <strong>de</strong>s Hirschberg . . . . . . . . . 71<br />

3.40. Teilergebnis nach <strong>de</strong>m zweiten Schritt <strong>de</strong>s Hirschberg . . . . . . . . . . . 72<br />

3.41. Teilergebnis nach <strong>de</strong>m ersten Schritt <strong>de</strong>s zweiten Durchlaufs . . . . . . . 72<br />

3.42. Teilergebnis nach <strong>de</strong>m zweiten Schritt <strong>de</strong>s zweiten Durchlaufs . . . . . . 73<br />

3.43. Bipartite und nicht bipartite Graphen . . . . . . . . . . . . . . . . . . . . 77<br />

3.44. Outerplanarer Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77<br />

3.45. Ein Halin-Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78<br />

3.46. Ein Bipartiter Graph mit Euler-Zerlegung . . . . . . . . . . . . . . . . . 79<br />

3.47. Bipartiter Beispielgraph für die Euler-Zerlegung . . . . . . . . . . . . . . 82<br />

3.48. Bipartiter Beispielgraph nach <strong>de</strong>r Ersetzung <strong>de</strong>r Kanten . . . . . . . . . . 82<br />

3.49. Bipartiter Beispielgraph nach <strong>de</strong>r Kantensortierung . . . . . . . . . . . . 83<br />

3.50. Successorbestimmung für die Kanten <strong>de</strong>s bipartiten Beispielgraphen . . . 84<br />

3.51. Die Kanten-Zellen nach <strong>de</strong>r Doubling-Technik . . . . . . . . . . . . . . . 85<br />

3.52. Auswahl eines Zyklusses . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

3.53. Festlegen <strong>de</strong>r Ablaufreihenfolge . . . . . . . . . . . . . . . . . . . . . . . 86<br />

4.1. Initialisierung <strong>de</strong>s GCA um <strong>de</strong>n Chinesischen Restsatz anzuwen<strong>de</strong>n . . . 96<br />

4.2. Die Variable m wur<strong>de</strong> mit <strong>de</strong>r Doubling-Technik ausgerechnet . . . . . . 96<br />

4.3. Alle Zellen haben <strong>de</strong>n korrekten Wert in <strong>de</strong>r Variable m . . . . . . . . . . 97<br />

A.1. Die erste Dame ist auf <strong>de</strong>m Feld positioniert . . . . . . . . . . . . . . . . 103<br />

A.2. Die zweite Dame ist auf <strong>de</strong>m Feld positioniert . . . . . . . . . . . . . . . 104<br />

A.3. Die dritte Dame ist auf <strong>de</strong>m Feld positioniert . . . . . . . . . . . . . . . 104<br />

A.4. Die vierte Dame ist auf <strong>de</strong>m Feld positioniert . . . . . . . . . . . . . . . 105<br />

A.5. Die fünfte Dame ist auf <strong>de</strong>m Feld positioniert . . . . . . . . . . . . . . . 105<br />

A.6. Die fünfte Dame ist auf <strong>de</strong>m Feld repositioniert . . . . . . . . . . . . . . 105<br />

A.7. Die vierte Dame ist auf <strong>de</strong>m Feld repositioniert . . . . . . . . . . . . . . 106<br />

A.8. Startsi<strong>tu</strong>ation <strong>de</strong>s Quicksorts . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

A.9. Quicksort nach <strong>de</strong>r Vorsortierung . . . . . . . . . . . . . . . . . . . . . . 107<br />

A.10.Rekursiver Aufruf <strong>de</strong>s Quicksorts . . . . . . . . . . . . . . . . . . . . . . 108<br />

B.1. Mittelwertbestimmung mit <strong>de</strong>r Balanced-Binary-Tree-Technik . . . . . . 110<br />

B.2. Abstandsbestimmung mit <strong>de</strong>r Doubling-Technik . . . . . . . . . . . . . . 111<br />

7


Listings<br />

3.1. Der Warshall-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

3.2. Der leicht modifizierte Warshall-Algorithmus . . . . . . . . . . . . . . . . 29<br />

3.3. Der Hirschberg-Algorithmus auf <strong>de</strong>r P-RAM . . . . . . . . . . . . . . . . 37<br />

3.4. Weitere Unterteilung <strong>de</strong>s ersten Schritts <strong>de</strong>s Hirschberg-Algorithmus . . . 43<br />

3.5. Minimumbestimmung mit <strong>de</strong>r Doubling-Technik . . . . . . . . . . . . . . 43<br />

3.6. Eulerfärbung für bipartite Graphen . . . . . . . . . . . . . . . . . . . . . 80<br />

4.1. Der erweiterte euklidische Algorithmus in C . . . . . . . . . . . . . . . . 93<br />

5.1. Effizienztest für Threads in C . . . . . . . . . . . . . . . . . . . . . . . . 99<br />

B.1. Berechnung <strong>de</strong>r Summe mit <strong>de</strong>r Balanced-Binary-Tree-Technik . . . . . . 110<br />

C.1. Implementierung <strong>de</strong>s Warshall-Algorithmus . . . . . . . . . . . . . . . . . 112<br />

C.2. Implementierung <strong>de</strong>s Hirschberg-Algorithmus . . . . . . . . . . . . . . . . 113<br />

C.3. Implementierung <strong>de</strong>s Kruskal-Algorithmus . . . . . . . . . . . . . . . . . 115<br />

C.4. Implementierung <strong>de</strong>s modifizierten Hirschberg-Algorithmus . . . . . . . . 117<br />

C.5. Implementierung <strong>de</strong>r Eulerfärbung . . . . . . . . . . . . . . . . . . . . . 119<br />

C.6. Implementierung <strong>de</strong>s Chinesischen Restsatzes . . . . . . . . . . . . . . . 125<br />

8


Tabellenverzeichnis<br />

3.1. Laufzeit <strong>de</strong>r betrachteten <strong>Algorithmen</strong> . . . . . . . . . . . . . . . . . . . 89<br />

3.2. Abhängigkeiten <strong>de</strong>r Daten für die Graphen-<strong>Algorithmen</strong> . . . . . . . . . 90<br />

3.3. Abhängigkeiten <strong>de</strong>r Verbindungen für die Graphen-<strong>Algorithmen</strong> . . . . . 90<br />

4.1. Berechnungstabelle <strong>de</strong>s euklidischen Algorithmus . . . . . . . . . . . . . 92<br />

4.2. Berechnungstabelle <strong>de</strong>s erweiterten euklidischen Algorithmus . . . . . . . 92<br />

4.3. Berechnung <strong>de</strong>s Inversen von M 1 modulo m 1 . . . . . . . . . . . . . . . . 95<br />

4.4. Berechnung <strong>de</strong>s Inversen von M 2 modulo m 2 . . . . . . . . . . . . . . . . 95<br />

4.5. Berechnung <strong>de</strong>s Inversen von M 3 modulo m 3 . . . . . . . . . . . . . . . . 95<br />

4.6. Abhängigkeiten <strong>de</strong>r Daten und Verbindungen für <strong>de</strong>n Chinesischen Restsatz. 98<br />

5.1. Ausführungszeiten ohne Threads und mit Threads . . . . . . . . . . . . . 100<br />

9


1. Einlei<strong>tu</strong>ng<br />

Dieses Kapitel wird zuerst eine Motivation liefern, parallele Systeme näher zu betrachten<br />

und anschließend einen Überblick über diese Arbeit geben. Zu<strong>de</strong>m wird in diesem Kapitel<br />

<strong>de</strong>r Begriff <strong>de</strong>r Aufwandsbetrach<strong>tu</strong>ng, wie er in dieser Arbeit verwen<strong>de</strong>t wird, <strong>de</strong>finiert.<br />

1.1. Motivation<br />

Schon seit Jahren liest man oft, dass die Möglichkeiten zur Leis<strong>tu</strong>ngssteigerung <strong>de</strong>r Prozessoren<br />

durch Vermehrung <strong>de</strong>r Transistoren bald ausgereizt seien. Es wird befürchtet,<br />

dass die Struk<strong>tu</strong>ren dann nicht mehr in <strong>de</strong>m Maße weiter verkleinert wer<strong>de</strong>n können,<br />

wie es nötig wäre, um eine Leis<strong>tu</strong>ngssteigerung durch kleinere und dadurch mehr Transistoren<br />

zu erzielen. Eine Möglichkeit, die Leis<strong>tu</strong>ngsfähigkeit weiter zu steigern, bieten<br />

die Quantenrechner. Diese sind zur Zeit aber noch nicht ausreichend erforscht und produzierbar<br />

1 , um Marktreife zu erlangen.<br />

Davon abgesehen stellen Quantenrechner die Informatik vor bislang ungeahnte Probleme,<br />

müssen dann doch neue Kryptoverfahren entwickelt 2 und auf eine an<strong>de</strong>re als bislang<br />

gewohnte Weise implementiert wer<strong>de</strong>n. Es ist davon auszugehen, dass die Quantenrechner<br />

in <strong>de</strong>n nächsten Jahren noch nicht reif für die Massenproduktion sind, weshalb man<br />

sich nach an<strong>de</strong>ren Alternativen zur Leis<strong>tu</strong>ngssteigerung umgesehen hat.<br />

Der geschickte Aufbau von parallelen Architek<strong>tu</strong>ren und eine gute Programmierung für<br />

sie bieten die Möglichkeit, Laufzeitverbesserungen zu erlangen, ohne die Anzahl <strong>de</strong>r<br />

Transistoren pro Chip bzw. Prozessor zu erhöhen. Aus diesem Grund sind parallele<br />

Systeme und <strong>de</strong>r Aufbau von neuen parallelen Architek<strong>tu</strong>ren in letzter Zeit wie<strong>de</strong>r vermehrt<br />

im Interesse <strong>de</strong>r Wirtschaft. So stellten Sony, Toshiba und IBM letztens <strong>de</strong>n<br />

Cell-Prozessor [Web05] vor, einen Chip, <strong>de</strong>r über insgesamt neun Prozessoren verfügt<br />

und unter an<strong>de</strong>rem in die PS3 (Playstation 3) eingebaut wer<strong>de</strong>n soll.<br />

Mit dieser Entwicklung wer<strong>de</strong>n Parallelrechner für die breite Bevölkerung und damit<br />

auch für Nicht-Aka<strong>de</strong>miker interessant. Es wird dadurch nötig, sich Gedanken zu machen,<br />

wie man die bisherigen sequentiellen <strong>Algorithmen</strong> geschickt auf ein paralleles System<br />

übertragen kann.<br />

1 Es ist bereits gelungen, im Labor einen Quantenrechner zu bauen, <strong>de</strong>r die Zahl 15 faktorisiert. Größere<br />

Quantenrechner sind bislang noch nicht gelungen.<br />

2 Elliptische Kurven gelten bislang als sicher gegenüber Angriffe mit Quantenrechnern. Zu<strong>de</strong>m wur<strong>de</strong>n<br />

auch schon Kryptoverfahren auf Quantenrechnern entwickelt, die sicher sind. Allerdings ist auch hier<br />

die Technik noch nicht ausgereift genug, als dass Marktreife erlangt wür<strong>de</strong>.<br />

10


Natürlich kann man die <strong>Algorithmen</strong> unverän<strong>de</strong>rt ausführen und nur ein Prozessor <strong>de</strong>s<br />

Systems berechnet das Problem. Allerdings verliert man so viel Performance und es<br />

scheint angeraten, sich zu überlegen, wie man die neuen Möglichkeiten besser nutzen<br />

kann.<br />

Diese Arbeit gibt einen Überblick über einige <strong>Algorithmen</strong>, welche teilweise an die neuen<br />

Umstän<strong>de</strong> angepasst sind, teilweise schon für sie entwickelt wur<strong>de</strong>n und effizient auf parallelen<br />

Systemen arbeiten. Um die Effizienz zu bestimmen, wird eine Aufwandsbetrach<strong>tu</strong>ng<br />

<strong>de</strong>r einzelnen <strong>Algorithmen</strong> betrieben, welche auch auf die Parallelität angepasst<br />

wur<strong>de</strong>. Im Folgen<strong>de</strong>n wird eine Erklärung zur Aufwandsbetrach<strong>tu</strong>ng abgegeben, danach<br />

wird dargestellt, wie sich die Arbeit aufbaut.<br />

1.2. Aufwandsbetrach<strong>tu</strong>ng<br />

Bei Mehrprozessorsystemen ist immer zu beachten, dass nicht nur die Laufzeitkomplexität<br />

für die Komplexitätsbetrach<strong>tu</strong>ng wichtig ist. Statt<strong>de</strong>ssen ist immer auch die Anzahl<br />

<strong>de</strong>r Prozessoren sowie die benötigten Verbindungen zwischen <strong>de</strong>n Prozessoren wichtig.<br />

Ohne diese Angaben ist kein korrekter Vergleich zwischen zwei verschie<strong>de</strong>nen Realisierungen<br />

möglich. Wenn hier also eine Abschätzung <strong>de</strong>r Komplexität gegeben wird, dann<br />

ist es auch zwingend notwendig, die Anzahl <strong>de</strong>r Prozessoren und sofern möglich die<br />

Anzahl <strong>de</strong>r Verbindungen anzugeben.<br />

Die O-Notation wird hier nicht nur für die Zeitabschätzung benutzt, son<strong>de</strong>rn auch für<br />

die Anzahl <strong>de</strong>r Prozessoren verwen<strong>de</strong>t, d. h. O(1) Prozessoren be<strong>de</strong>utet eine konstante<br />

Anzahl an Prozessoren. Die gegebenen Abschätzungen sind für die maximal sinnvolle<br />

Anzahl an Prozessoren gegeben. Manchmal ließe sich das Ergebnis auch mit weniger<br />

Prozessoren erzielen, allerdings dann zu ungunsten <strong>de</strong>r Laufzeit. Damit die Abschätzung<br />

also vergleichbar bleibt, wur<strong>de</strong> hier auf Modifikationen, welche die Anzahl <strong>de</strong>r Prozessoren<br />

auf Kosten <strong>de</strong>r Laufzeit minimieren, verzichtet. Ist es jedoch möglich, die Anzahl<br />

<strong>de</strong>r Prozessoren zu verringern, ohne dabei Laufzeiteinbußen hinzunehmen, so sind diese<br />

Modifikationen o<strong>de</strong>r eine Referenz auf die entsprechen<strong>de</strong> Litera<strong>tu</strong>r angegeben.<br />

1.3. Struk<strong>tu</strong>r <strong>de</strong>r Arbeit<br />

In <strong>de</strong>r Wissenschaft wer<strong>de</strong>n parallele Systeme schon seit langem erforscht, was zu einer<br />

Vielzahl an Mo<strong>de</strong>llen geführt hat. Meist sind diese Mo<strong>de</strong>lle auf eine bestimmte Aufgabe<br />

hin mo<strong>de</strong>lliert o<strong>de</strong>r haben sich aus einem Single-Prozessor-Mo<strong>de</strong>ll heraus entwickelt. In<br />

Kapitel 2 soll eine Übersicht über einige parallele Mo<strong>de</strong>lle gegeben wer<strong>de</strong>n. Da viele<br />

Mo<strong>de</strong>lle existieren, erhebt diese Arbeit keinen Anspruch auf Vollständigkeit o<strong>de</strong>r Gewich<strong>tu</strong>ng<br />

bezüglich <strong>de</strong>r Auswahl.<br />

Im dritten Kapitel wer<strong>de</strong>n dann Graphenalgorithmen auf <strong>de</strong>m parallelen Mo<strong>de</strong>ll <strong>de</strong>s<br />

Global Cellular Automata“ (GCA) angegeben und eine Aufwandsbetrach<strong>tu</strong>ng <strong>de</strong>r einzelnen<br />

<strong>Algorithmen</strong><br />

”<br />

betrieben.<br />

11


Im vierten Kapitel wird ein kryptographischer Algorithmus auf <strong>de</strong>m GCA mo<strong>de</strong>lliert<br />

und auch hierfür eine Komplexitätsbetrach<strong>tu</strong>ng angestellt.<br />

Im fünften Kapitel wird auf die Eigenheiten <strong>de</strong>r Implementierung, wie z. B. das unterschiedliche<br />

Laufzeitverhalten eines Algorithmus bei Modifikationen <strong>de</strong>r Implementierung,<br />

eingegangen.<br />

Im sechsten Kapitel wird dann ein Fazit gezogen und aufgezeigt, wo noch Forschungsbedarf<br />

besteht.<br />

12


2. Zellularautomaten und an<strong>de</strong>re<br />

Mo<strong>de</strong>lle<br />

Innerhalb <strong>de</strong>r Automatentheorie war es schon immer so, dass mehrere gleichmächtige<br />

Mo<strong>de</strong>lle existierten. Diese wur<strong>de</strong>n teilweise zeitgleich entwickelt, und unterschie<strong>de</strong>n sich<br />

oft nur in Kleinigkeiten o<strong>de</strong>r einer verschie<strong>de</strong>nen Herangehensweise an die Problematiken.<br />

Wird ein Mo<strong>de</strong>ll entwickelt, so richtet sich <strong>de</strong>r Aufbau <strong>de</strong>s Mo<strong>de</strong>lls immer danach, was<br />

man mit <strong>de</strong>m Mo<strong>de</strong>ll erreichen möchte. Möchte man mit einem Mo<strong>de</strong>ll z. B. natürliche<br />

Vorgänge in ihrer Feinheit möglichst realitätsnah nachempfin<strong>de</strong>n, wird man es an<strong>de</strong>rs<br />

entwickeln als ein Mo<strong>de</strong>ll, das dazu dient, S<strong>tu</strong><strong>de</strong>nten die Möglichkeiten von Automaten<br />

zu ver<strong>de</strong>utlichen.<br />

Bei einem Mo<strong>de</strong>ll, das eine bestimmte Si<strong>tu</strong>ation simulieren soll, wird das Mo<strong>de</strong>ll even<strong>tu</strong>ell<br />

komplexer und unverständlicher, wenn sich dadurch die Problematik in <strong>de</strong>m Mo<strong>de</strong>ll<br />

leichter formulieren lässt. Ein Mo<strong>de</strong>ll hingegen, dass für die Lehre entwickelt wur<strong>de</strong>, wird<br />

so einfach wie möglich gehalten wer<strong>de</strong>n, damit es auch für einen Laien leicht verständlich<br />

ist. Lösungen sollen auf diesem Mo<strong>de</strong>ll dann zwar immer noch möglich sein, aber<br />

komplexere Probleme sind <strong>de</strong>utlich schwieriger auf einem solchem Mo<strong>de</strong>ll zu simulieren<br />

als auf einem speziell entwickelten Automatenmo<strong>de</strong>ll. Ein Problem bei einem einfachen<br />

Mo<strong>de</strong>ll ist stets auch, dass die Gefahr besteht, dass von <strong>de</strong>r Realität so weit abstrahiert<br />

wird, dass das resultieren<strong>de</strong> Mo<strong>de</strong>ll nicht mehr <strong>de</strong>r Realität entspricht.<br />

So wie sich verschie<strong>de</strong>ne Mo<strong>de</strong>lle in <strong>de</strong>r klassischen Automatentheorie gebil<strong>de</strong>t haben,<br />

wur<strong>de</strong>n auch mehrere Mo<strong>de</strong>lle für parallele Systeme gebil<strong>de</strong>t, teilweise davon beeinflusst,<br />

welches klassische Mo<strong>de</strong>ll zu Grun<strong>de</strong> gelegt wur<strong>de</strong>, teilweise aber auch auf an<strong>de</strong>ren I<strong>de</strong>en<br />

aufbauend. In <strong>de</strong>n anschließen<strong>de</strong>n Abschnitten sollen einige Automatenmo<strong>de</strong>lle vorgestellt<br />

wer<strong>de</strong>n. Falls vorhan<strong>de</strong>n, wird dabei zunächst auf die Single-Prozessor-Variante<br />

eingegangen, bevor die parallelen Möglichkeiten erläutert wer<strong>de</strong>n.<br />

2.1. Die <strong>Ra</strong>ndom-Access-Maschinen<br />

Ein Mo<strong>de</strong>ll, das neben <strong>de</strong>r Turing-Maschine 1 verwen<strong>de</strong>t wird, um S<strong>tu</strong><strong>de</strong>nten die Berechenbarkeit<br />

verständlicher zu machen, ist die <strong>Ra</strong>ndom-Access-Maschine (RAM). Dieses<br />

Mo<strong>de</strong>ll ist in seiner Programmiersprache ähnlich zu Assembler und eignet sich aufgrund<br />

1 Turing-Maschinen wer<strong>de</strong>n u. a. in [Sch01] eingeführt und erklärt.<br />

13


seiner Einfachheit u. a. zur Betrach<strong>tu</strong>ng <strong>de</strong>r Terminierung von Programmen. Das Prinzip<br />

<strong>de</strong>r RAM soll nun erläutert wer<strong>de</strong>n, bevor das Mo<strong>de</strong>ll dann zur parallelen RAM<br />

(P-RAM) erweitert wird.<br />

2.1.1. Prinzip <strong>de</strong>r RAM<br />

Die RAM verfügt über eine CPU mit abzählbar unendlich vielen Registern, in <strong>de</strong>nen<br />

beliebig große natürliche Zahlen gespeichert wer<strong>de</strong>n können. Auf <strong>de</strong>n Werten kann die<br />

CPU arithmetische Operationen ausführen, zusätzlich kann die CPU je<strong>de</strong>rzeit auf <strong>de</strong>n<br />

Speicher zugreifen und von dort Werte in die Register lesen o<strong>de</strong>r Werte in <strong>de</strong>n Speicher<br />

schreiben.<br />

Der Arbeitsablauf <strong>de</strong>r CPU wird von einem Programm festgelegt, welches sich im Speicher<br />

befin<strong>de</strong>t. Even<strong>tu</strong>ell benötigte Eingaben für das Programm stehen ebenfalls an <strong>de</strong>finierten<br />

Stellen im Speicher. Das Programm besteht aus arithmetischen Instruktionen<br />

sowie Speicherzugriffsbefehlen. Um <strong>de</strong>n normalen Programmfluss zu modifizieren, sind<br />

außer<strong>de</strong>m bedingte Sprünge erlaubt. Die Instruktionen <strong>de</strong>s Programms wer<strong>de</strong>n gemäß<br />

eines Takts abgearbeitet und die CPU been<strong>de</strong>t ihre Arbeit bei Programmen<strong>de</strong>.<br />

Bei <strong>de</strong>r Aufwandsbetrach<strong>tu</strong>ng eines Programms auf <strong>de</strong>r RAM müssen drei Aspekte unterschie<strong>de</strong>n<br />

wer<strong>de</strong>n:<br />

• Die Programmgröße bestimmt sich aus <strong>de</strong>r Anzahl <strong>de</strong>r Instruktionen, welche das<br />

Programm bil<strong>de</strong>n.<br />

• Die Speichergröße bestimmt sich aus <strong>de</strong>r Anzahl <strong>de</strong>r vom Programm genutzten<br />

Speicherzellen.<br />

• Der Zeitaufwand, bestimmt sich aus <strong>de</strong>r Anzahl <strong>de</strong>r Takte, welche benötigt wer<strong>de</strong>n,<br />

bis das Programm abgearbeitet ist.<br />

2.1.2. Die P-RAM<br />

Das erste Kapitel von [KKT01] liefert eine Möglichkeit zur Parallelisierung <strong>de</strong>s RAM-<br />

Mo<strong>de</strong>lls. Da alle Berechnungen von <strong>de</strong>r CPU ausgeführt wer<strong>de</strong>n, kann die RAM dadurch<br />

parallelisiert wer<strong>de</strong>n, dass es nicht mehr eine einzige CPU gibt, son<strong>de</strong>rn mehrere Prozessoren.<br />

Diese Prozessoren arbeiten synchron, besitzen also einen gemeinsamen Takt und<br />

haben Zugriff auf einen gemeinsamen Speicher.<br />

Der Datenaustausch zwischen <strong>de</strong>n Prozessoren geschieht über <strong>de</strong>n gemeinsamen Speicher.<br />

Ein Prozessor schreibt seine Werte in eine Speicherzelle und an<strong>de</strong>re Prozessoren<br />

können sie dort bei Bedarf lesen. Ein Nachteil an dieser Mo<strong>de</strong>llierung ist, dass sichergestellt<br />

wer<strong>de</strong>n muss, dass keine zwei Prozessoren zur gleichen Zeit auf eine Speicherzelle<br />

schreiben wollen.<br />

14


Aufgrund solcher Schreibkonflikte kann es zu Dateninkonsistenzen kommen. Diese Dateninkosistenzen<br />

können dadurch vermie<strong>de</strong>n wer<strong>de</strong>n, dass eine Speicherzelle, auf die schreibend<br />

zugegriffen wird, gesperrt und erst nach <strong>de</strong>m erfolgreichen Schreibvorgang wie<strong>de</strong>r<br />

freigegeben wird. Um die synchrone Arbeitsweise <strong>de</strong>r Prozessoren zu gewährleisten wird<br />

angenommen, dass alle Speicheroperationen die gleiche Zeit benötigen.<br />

Neben <strong>de</strong>m gemeinsamen Speicher gibt es noch einen privaten Speicher, auf <strong>de</strong>n nur<br />

jeweils ein Prozessor Zugriff hat. Zu<strong>de</strong>m verfügt je<strong>de</strong>r Prozessor über eine ein<strong>de</strong>utige<br />

Kennung (ID) damit die Prozessoren unterschie<strong>de</strong>n wer<strong>de</strong>n können, da verschie<strong>de</strong>ne<br />

Prozessoren verschie<strong>de</strong>ne Instruktionen ausführen können.<br />

Der gemeinsame Speicher kann in vier verschie<strong>de</strong>nen Modi betrieben wer<strong>de</strong>n: EREW,<br />

CREW, CRCW und CROW.<br />

EREW be<strong>de</strong>utet ”<br />

exclusive read, exclusive write“ und bezeichnet <strong>de</strong>n Modus, in <strong>de</strong>m<br />

pro Zeittakt jeweils nur maximal ein Prozessor lesend o<strong>de</strong>r schreibend auf die gleiche<br />

Speicherzelle zugreifen darf.<br />

CREW be<strong>de</strong>utet ”<br />

concurrent read, exclusive write“ und erlaubt <strong>de</strong>n lesen<strong>de</strong>n Zugriff<br />

mehrerer Prozessoren auf eine Speicherzelle, aber auf eine Speicherzelle schreibend zugreifen<br />

darf nur ein Prozessor pro Takt.<br />

CRCW be<strong>de</strong>utet ”<br />

concurrent read, concurrent write“ und erlaubt sowohl <strong>de</strong>n parallel lesen<strong>de</strong>n<br />

als auch <strong>de</strong>n parallel schreiben<strong>de</strong>n Zugriff auf eine Speicherzelle. In diesem Modus<br />

muss im Programm darauf geachtet wer<strong>de</strong>n, dass keine Dateninkonsistenzen entstehen.<br />

CROW be<strong>de</strong>utet ”<br />

concurrent read, owners write“. In diesem Modus wird je<strong>de</strong>r Speicherzelle<br />

genau ein Prozessor zugeordnet, welcher auf diese Speicherstelle schreibend<br />

zugreifen darf. Alle an<strong>de</strong>ren Prozessoren dürfen nur lesend zugreifen. Bei diesem Mo<strong>de</strong>ll<br />

entstehen keine Dateninkonsistenzen. Nachzulesen ist dieser Modus u. a. in [DR86].<br />

2.2. Die Pointer-Maschinen<br />

Unter <strong>de</strong>m Begriff Pointer-Maschine fin<strong>de</strong>n sich viele verschie<strong>de</strong>ne Mo<strong>de</strong>lle in <strong>de</strong>r Litera<strong>tu</strong>r.<br />

Obwohl all diese Mo<strong>de</strong>lle Gemeinsamkeiten haben, differieren sie auch an diversen<br />

Stellen. In [BA95] wur<strong>de</strong> <strong>de</strong>r Versuch unternommen, sowohl die Gemeinsamkeiten als<br />

auch die Unterschie<strong>de</strong> <strong>de</strong>r verschie<strong>de</strong>nen Pointer-Maschinen herauszuarbeiten.<br />

Ziel dieses Abschnittes ist es, die verschie<strong>de</strong>nen Mo<strong>de</strong>lle vorzustellen. Anschließend wird<br />

dann erläutert, wie sich eine Parallel-Pointer-Maschine aufbaut. Bei <strong>de</strong>r Vorstellung <strong>de</strong>r<br />

Pointer-Maschinen wird dabei Bezug auf die Unterteilung von [BA95] genommen und<br />

zwischen Maschinenmo<strong>de</strong>llen und Programmiermo<strong>de</strong>llen unterschie<strong>de</strong>n.<br />

Allen Pointer-Maschinen gemeinsam ist die Mo<strong>de</strong>llierung <strong>de</strong>s Speichers als durch Pointer<br />

verbun<strong>de</strong>ne Knoten eines Graphen. Aufgrund dieser Gemeinsamkeit kann diese Art<br />

<strong>de</strong>r Mo<strong>de</strong>llierung <strong>de</strong>s Speichers als Charakteristikum <strong>de</strong>r Pointer-Maschinen verwen<strong>de</strong>t<br />

wer<strong>de</strong>n.<br />

15


2.2.1. Maschinenmo<strong>de</strong>lle <strong>de</strong>r Pointer-Maschinen<br />

Innerhalb <strong>de</strong>r Maschinenmo<strong>de</strong>lle muss eine weitergehen<strong>de</strong> Unterscheidung getroffen wer<strong>de</strong>n.<br />

[BA95] unterschei<strong>de</strong>t beispielsweise zwischen atomistischen und High-level Mo<strong>de</strong>llen,<br />

wobei die High-level Mo<strong>de</strong>lle nicht auf Symbolen, son<strong>de</strong>rn auf Datentypen arbeiten.<br />

Unter Datentypen wird hierbei ein Konstrukt wie die Integer unter C verstan<strong>de</strong>n. Es<br />

gibt also für einen Datentypen einen <strong>de</strong>finierten Wertebereich und darauf <strong>de</strong>finierte<br />

Funktionen. High-level Mo<strong>de</strong>lle befin<strong>de</strong>n sich <strong>de</strong>mnach näher an <strong>de</strong>m Prinzip <strong>de</strong>r Programmiersprachen.<br />

Allerdings ist ein Mo<strong>de</strong>ll, das auf bestimmte Datentypen festgelegt<br />

ist, nicht mehr so allgemein vergleichbar, wie Mo<strong>de</strong>lle, welche auf Symbolen arbeiten.<br />

Hier sollen repräsentativ drei verschie<strong>de</strong>ne atomistische Maschinen vorgestellt wer<strong>de</strong>n,<br />

um zu ver<strong>de</strong>utlichen, welche Vielfalt sich hinter <strong>de</strong>m Begriff Pointer-Maschine verbringt.<br />

2.2.1.1. Kolmogorov-Uspenskii Maschinen<br />

In <strong>de</strong>r Kolmogorov-Uspenskii Maschine wird <strong>de</strong>r Speicher als ungerichteter endlicher<br />

Graph dargestellt. Zusätzlich existiert eine endliche Menge an Labeln und je<strong>de</strong> Kante hat<br />

eines dieser Label. Zwei zu <strong>de</strong>m gleichen Knoten inzi<strong>de</strong>nte Kanten müssen verschie<strong>de</strong>ne<br />

Labels haben.<br />

Innerhalb <strong>de</strong>s Graphen gibt es einen ausgezeichneten Knoten, <strong>de</strong>n aktiven Knoten, <strong>de</strong>ssen<br />

Nachbarschaft die aktive Zone beschreibt. Dabei ist mit <strong>de</strong>r Nachbarschaft aber nicht nur<br />

die direkte Nachbarschaft gemeint, son<strong>de</strong>rn alle Knoten, welche innerhalb eines festen,<br />

aber beliebigen <strong>Ra</strong>dius liegen.<br />

Innerhalb <strong>de</strong>s Mo<strong>de</strong>lls sind unbedingte Sprünge, Eingaben, Ausgaben, bedingte Sprünge<br />

und Speichermodifiktationen erlaubt. Die Speichermodifikationen ermöglichen das Erzeugen<br />

von neuen Knoten sowie das Umbiegen von Kanten.<br />

2.2.1.2. Storage Modification Maschines<br />

Die Storage Modification Maschinen, kurz SMM, <strong>de</strong>finieren <strong>de</strong>n Speicher als gerichteten<br />

endlichen Graph. Auch hier gibt es eine endliche Menge an Labels, woraus je<strong>de</strong> Kante<br />

ein Label als Kantenmarkierung erhält. Allerdings müssen bei diesem Mo<strong>de</strong>ll nur zu<br />

<strong>de</strong>m selben Knoten eingehen<strong>de</strong> Kanten verschie<strong>de</strong>ne Labels haben, aus einem Knoten<br />

ausgehen<strong>de</strong> Kanten können die gleiche Markierung haben.<br />

Eine weitere Verän<strong>de</strong>rung gegenüber <strong>de</strong>m Kolmogorov-Uspenskii Maschinen ist <strong>de</strong>r nicht<br />

<strong>de</strong>finierte <strong>Ra</strong>dius <strong>de</strong>r aktiven Zone 2 . Ansonsten erlaubt die SMM die gleichen Operationen<br />

wie die Kolmogorov-Uspenskii Maschinen. In <strong>de</strong>r Litera<strong>tu</strong>r hat sich die SMM<br />

größtenteils als Repräsentant <strong>de</strong>r atomistischen Pointer-Maschinen durchgesetzt.<br />

2 In <strong>de</strong>r Definition von Schönhage war keine For<strong>de</strong>rung zur Definition <strong>de</strong>s <strong>Ra</strong>dius enthalten.<br />

16


2.2.1.3. Knuths Linking Automat<br />

Dieser Automat ist <strong>de</strong>r SMM sehr nahe verwandt. Der einzige Unterschied besteht darin,<br />

dass in Knuths Mo<strong>de</strong>ll für je<strong>de</strong>n Knoten zusätzlich eine feste Anzahl an Wertefel<strong>de</strong>rn<br />

vorgesehen sind, in <strong>de</strong>nen Symbole eines gegebenen Alphabets stehen.<br />

2.2.1.4. Die LISP Maschinen<br />

Die Atomistic Full LISP Maschine (AFLM) <strong>de</strong>finiert, vergleichbar zur SMM, <strong>de</strong>n Speicher<br />

als gerichteten endlichen Graphen und auch sonst ist das Mo<strong>de</strong>ll <strong>de</strong>m <strong>de</strong>r SMM<br />

sehr nahe. Allerdings beinhaltet in <strong>de</strong>m AFLM Mo<strong>de</strong>ll je<strong>de</strong>r Knoten noch exakt zwei<br />

Wertefel<strong>de</strong>r, in <strong>de</strong>nen entwe<strong>de</strong>r Symbole <strong>de</strong>s Alphabets o<strong>de</strong>r Pointer stehen können.<br />

Die Atomistic Pure LISP Maschine (APLM) <strong>de</strong>finiert sich genauso wie die AFLM, mit<br />

<strong>de</strong>r Einschränkung, dass die Wertefel<strong>de</strong>r nach <strong>de</strong>r Erzeugung <strong>de</strong>s Knotens nicht mehr<br />

verän<strong>de</strong>rt wer<strong>de</strong>n dürfen. Da auch die Pointer in <strong>de</strong>n Wertefel<strong>de</strong>rn stehen, kann so kein<br />

Zyklus entstehen, weil ein Pointer bei <strong>de</strong>r Erzeugung eines neuen Knotens nur auf einen<br />

bereits vorhan<strong>de</strong>nen Knoten gesetzt wer<strong>de</strong>n kann und somit auch noch kein Pointer auf<br />

<strong>de</strong>n neuen Knoten zeigt. Mit dieser Einschränkung erreicht man, dass keine Nebeneffekte<br />

von Funktionen auftreten und dass die Semantik einfacher wird.<br />

2.2.2. Programmiermo<strong>de</strong>lle <strong>de</strong>r Pointer-Maschinen<br />

In [BA95] ist das Programmiermo<strong>de</strong>ll <strong>de</strong>r Pointer-Maschine so <strong>de</strong>finiert, dass man für eine<br />

gegebene abstrakte Datenstruk<strong>tu</strong>r eine geeignete Repräsentation als gerichteten Graphen<br />

fin<strong>de</strong>t. Auf diesem gerichteten Graphen sind die Operationen ”<br />

add no<strong>de</strong>“, ”<br />

add<br />

pointer“, ”<br />

get pointer“ und ”<br />

<strong>de</strong>lete pointer“ erlaubt. Alle gewünschten Funktionen auf<br />

<strong>de</strong>r abstrakten Datenstruk<strong>tu</strong>r müssen mit Hilfe dieser Operationen implementiert wer<strong>de</strong>n.<br />

Die Zeitkomplexität wird dann durch die Gesamtzahl <strong>de</strong>r Operationsaufrufe bestimmt,<br />

während <strong>de</strong>r Platzbedarf sich anhand <strong>de</strong>r Anzahl <strong>de</strong>r ”<br />

add no<strong>de</strong>“ bestimmt.<br />

Bei <strong>de</strong>r Implementierung <strong>de</strong>s Union-Find-Problems 3 hat sich noch eine Klasse von speziellen<br />

Pointer-<strong>Algorithmen</strong> herauskristallisiert, die ”<br />

Separable Pointer“-<strong>Algorithmen</strong>.<br />

Diese <strong>Algorithmen</strong> garantieren, dass nach je<strong>de</strong>r Operation <strong>de</strong>r Graph in disjunkte Untergraphen<br />

zerlegt wer<strong>de</strong>n kann, wobei keine Kante von einem Untergraphen zu einem<br />

an<strong>de</strong>ren führt.<br />

3 Das Union-Find-Problem startet mit Mengen, die im Verlauf eines Algorithmus vereinigt wer<strong>de</strong>n<br />

können (Union). Zu<strong>de</strong>m können Elemente in diesen Mengen gesucht wer<strong>de</strong>n (Find). Da dies die<br />

einzigen Operationen sind, die bei <strong>de</strong>r Klasse an Problemen zugelassen sind, haben sie <strong>de</strong>r Klasse<br />

ihren Namen gegeben.<br />

17


2.2.3. Parallel-Pointer Maschinen<br />

In [GK96] haben die Autoren, aufbauend auf Knuths Linking Automat, die Parallel-<br />

Pointer-Maschine (PPM) <strong>de</strong>finiert, um darauf einen Sortieralgorithmus zu mo<strong>de</strong>llieren.<br />

Das Mo<strong>de</strong>ll besitzt mehrere synchronisierte Prozessoren, welche auf einen gemeinsamen<br />

Speicher zugreifen. Der Speicher kann entwe<strong>de</strong>r nach <strong>de</strong>m EREW, <strong>de</strong>m CREW o<strong>de</strong>r <strong>de</strong>m<br />

CRCW Pinzip betrieben wer<strong>de</strong>n. Er ist als gerichteter Graph repräsentiert und erlaubt<br />

nur die Zugriffe, welche durch die Pointer-Maschinen <strong>de</strong>finiert sind.<br />

Innerhalb <strong>de</strong>s Graphen beinhaltet je<strong>de</strong>r Knoten eine konstante Anzahl an Wertefel<strong>de</strong>rn<br />

und eine konstante Anzahl an Pointern zu an<strong>de</strong>ren Knoten.<br />

Prozessoren haben eine konstante Anzahl an Registern, in <strong>de</strong>nen Pointer stehen, welche<br />

einen Zugriff auf <strong>de</strong>n Speicher ermöglichen. Der gesamte Datenaustausch <strong>de</strong>r Prozessoren<br />

geschieht über <strong>de</strong>n gemeinsamen Speicher, es existiert keine an<strong>de</strong>re Kommunikationsmöglichkeit.<br />

Die Prozessoren können <strong>de</strong>n Speicher aktiv modifizieren, in<strong>de</strong>m sie bei<br />

Bedarf in einem Schritt neue Speicherzellen erstellen.<br />

Da die gesamte Ein- und Ausgabe <strong>de</strong>r PPM aus Pointern besteht, ist es wichtig festzulegen,<br />

welche Operationen erlaubt sind und welche nicht. Erlaubte Pointer Operationen<br />

sind:<br />

• Kopieren eines Pointers in ein Pointer-Register <strong>de</strong>s Prozessors.<br />

• Schreiben eines Pointer-Registers-Inhalts <strong>de</strong>s Prozessors in eine Pointer-Speicherzelle<br />

• Vergleichen von zwei Pointer-Registerinhalten auf Äquivalenz<br />

• Lesen <strong>de</strong>s Speicherinhalts, auf <strong>de</strong>n ein Pointer zeigt.<br />

Verboten ist Pointer-Arithmetik wie z. B. in<strong>de</strong>xierte Addressierung. Zusätzlich darf ein<br />

Prozessor auf Werte (nicht auf Pointer) die normalen Arithmetik- und Vergleichsoperationen<br />

ausführen.<br />

2.3. Die Zellularautomaten<br />

Das erste Mal wur<strong>de</strong>n Zellularautomaten von John von Neumann vorgeschlagen, welcher<br />

auf <strong>de</strong>r Suche nach einer selbstreproduzieren<strong>de</strong>n Maschine war. Es gelang ihm,<br />

einen Zellularautomaten anzugeben, welcher mit 29 Zustän<strong>de</strong>n fähig war, sich selbst zu<br />

reproduzieren. Später wur<strong>de</strong> dieser Automat von Signorini auf einer SIMD-Maschine<br />

implementiert ([Sig89]).<br />

Schon von Neumann zeigte, dass man mit <strong>de</strong>m Zellularautomaten je<strong>de</strong> beliebige Turingmaschine<br />

simulieren kann. Dieser Beweis wird beispielsweise in [Sar00] und [ARS71]<br />

nachvollzogen. [Sar00] bietet zu<strong>de</strong>m eine gute Übersicht über die Entwicklung von Zellularautomaten.<br />

18


Im Folgen<strong>de</strong>n wird zuerst <strong>de</strong>r klassische Zellularautomat (CA) vorgestellt und auf einige<br />

seiner Varianten eingegangen. In <strong>de</strong>n nachfolgen<strong>de</strong>n Abschnitten wer<strong>de</strong>n dann weitergehen<strong>de</strong><br />

Variationen <strong>de</strong>s CA vorgestellt. Es wird zusätzlich erläutert, in welcher Art und<br />

Weise sie sich von <strong>de</strong>m Original unterschei<strong>de</strong>n.<br />

2.3.1. Der klassische ”<br />

Cellular Automata“ (CA)<br />

Der Zellularautomat, <strong>de</strong>n von Neumann ursprünglich vorschlug, besteht aus in einem unendlichen<br />

Gitter angeordneten Zellen (mesh-connected). Das Gitter kann eine beliebige<br />

Dimension haben, jedoch arbeiten die meisten <strong>Algorithmen</strong> auf <strong>de</strong>m ein- o<strong>de</strong>r zweidimensionalen<br />

Gitter. Je<strong>de</strong> Zelle innerhalb <strong>de</strong>s Gitters führt ein eigenes Programm aus<br />

und kann auf seine Nachbarzellen lesend, aber nicht schreibend zugreifen.<br />

Die Art und <strong>de</strong>r <strong>Ra</strong>dius <strong>de</strong>r Nachbarschaft können vom Benutzer festgelegt wer<strong>de</strong>n, allerdings<br />

ist man auf die Möglichkeiten <strong>de</strong>r Gitterstruk<strong>tu</strong>r beschränkt. Die am weitesten verbreiteten<br />

Nachbarschaften sind die von Neumann“ und die Moore“ Nachbarschaften.<br />

” ”<br />

Diese bei<strong>de</strong>n Nachbarschaftsbeziehungen unterschei<strong>de</strong>n sich ab einem zweidimensionalen<br />

Gitter darin, dass die von Neumann“ Nachbarschaft nur die horizontalen und vertikalen<br />

”<br />

Zellen als Nachbarn betrachtet (siehe Abbildung 2.1), während in <strong>de</strong>r Nachbarschaft von<br />

Moore“ auch die diagonalen Zellen als Nachbarn betrachtet wer<strong>de</strong>n (siehe Abbildung<br />

”<br />

2.2).<br />

Der CA verfügt über keinen gemeinsamen Speicher, das heißt, alle Daten, welche im<br />

Verlauf <strong>de</strong>r Berechnung gebraucht wer<strong>de</strong>n, müssen entwe<strong>de</strong>r aus <strong>de</strong>r Initialkonfiguration<br />

errechenbar o<strong>de</strong>r ein Teil <strong>de</strong>r Initialisierung sein.<br />

Abbildung 2.1.: Nachbarschaftsbeziehung <strong>de</strong>r Zellen eines CA in einem zweidimensionalen<br />

unendlichen Gitters nach von Neumann.<br />

Formal besteht <strong>de</strong>r CA aus einem Vier<strong>tu</strong>pel M=(N,d,r,δ), wobei die einzelnen Werte<br />

folgen<strong>de</strong> Be<strong>de</strong>u<strong>tu</strong>ng haben:<br />

N = die Definition, welche Nachbarschaftsbeziehung benutzt wird,<br />

d = die Dimension <strong>de</strong>s Gitters,<br />

r = <strong>de</strong>r <strong>Ra</strong>dius <strong>de</strong>r Nachbarschaft,<br />

19


Abbildung 2.2.: Nachbarschaftsbeziehung <strong>de</strong>r Zellen eines CA in einem zweidimensionalen<br />

unendlichen Gitters nach Moore.<br />

δ = die Überführungsfunktion.<br />

Bei dieser Definition wird vorausgesetzt, dass die Zellen anfangs initialisiert sind.<br />

In <strong>de</strong>r Litera<strong>tu</strong>r fin<strong>de</strong>t man oft (z. B. [Maj94], [Sar00]) eine Unterteilung <strong>de</strong>r Zellularautomaten<br />

in vier Klassen:<br />

• Die erste Klasse <strong>de</strong>r CA been<strong>de</strong>t ihre Berechnung zu einem Zeitpunkt in einem<br />

stabilen Zustand, welcher sich von da an nicht mehr än<strong>de</strong>rt.<br />

• Die zweite Klasse zeigt ein periodisches Verhalten, d. h. es gibt einen immer wie<strong>de</strong>rkehren<strong>de</strong>n<br />

Ablauf von Konfigurationen, welcher sich nach einiger Zeit einstellt<br />

und dann nicht mehr unterbrochen wird.<br />

• Die dritte Klasse zeigt ein chaotisches Verhalten. Es wird im Verlauf <strong>de</strong>r Evolution<br />

(Berechnungen) keine Konfigurationsfolge erreicht, welche sich periodisch<br />

wie<strong>de</strong>rholt.<br />

• Die vierte Klasse führt im Laufe <strong>de</strong>r Evolution zu komplexen lokalen Struk<strong>tu</strong>ren,<br />

welche manchmal über mehrere Generationen bestehen bleiben. [Maj94] stellt fest,<br />

dass alle CA, welche zu universellen Berechnungen fähig sind, in dieser Klasse<br />

liegen, auch wenn nicht alle Automaten dieser Klasse universelle Berechnungen<br />

durchführen können.<br />

Da die Zellularautomaten zu vielen Forschungen angeregt haben, wur<strong>de</strong>n bald einige<br />

Modifikationen eingeführt. Eine gute Zusammenfassung dieser Modifikationen bietet<br />

[Sar00], dort sind alle Modifikationen außer <strong>de</strong>m CA Network zusammengefasst. Nachfolgend<br />

sollen die Modifikationen erläutert wer<strong>de</strong>n.<br />

2.3.1.1. Tesselation CA<br />

Diese Spezialform <strong>de</strong>s CA benötigt keine Initialisierung, son<strong>de</strong>rn verfügt über eine Eingabe,<br />

welche beim Start an alle Zellen angelegt wird. Anschaulich kann man sich das so<br />

20


vorstellen, dass je<strong>de</strong> Zelle über eine endliche Anzahl an Regeln verfügt und die Eingabe<br />

darüber entschei<strong>de</strong>t, welche Regel angewen<strong>de</strong>t wer<strong>de</strong>n soll. Tesselation CAs wer<strong>de</strong>n auch<br />

zeitverän<strong>de</strong>rliche CA genannt.<br />

2.3.1.2. Iterative CA<br />

Bei dieser Spezialform erhält nur eine Zelle eine Eingabe. Interessant ist diese Art von<br />

CA u. a. bei S<strong>tu</strong>dien zur Spracherkennung. Die eindimensionalen iterativen CA erkennen<br />

kontextfreie Sprachen in O(n 2 ) Schritten und die nicht<strong>de</strong>terministischen zweidimensionalen<br />

iterativen CA können in linearer Zeit je<strong>de</strong>s Wort akzeptieren, welches von einer<br />

nicht<strong>de</strong>terministischen Mehrkopf-Turingmaschine akzeptiert wird. Dennoch sind die iterativen<br />

CA merklich langsamer in <strong>de</strong>r Ausführung als die konventionellen CA.<br />

2.3.1.3. Dynamische CA<br />

Bei <strong>de</strong>n dynamischen CA darf mit <strong>de</strong>r Zeit sowohl die Anzahl <strong>de</strong>r Zellen als auch <strong>de</strong>ren<br />

Verbindungen zu <strong>de</strong>n Nachbarn variieren. Es kann also geschehen, dass eine Zelle von<br />

einem Zeitpunkt zu einem an<strong>de</strong>ren ihre Nachbarschaft än<strong>de</strong>rt o<strong>de</strong>r dass eine Zelle neu<br />

erschaffen o<strong>de</strong>r zerstört wird. Eine Verallgemeinerung dieses Ansatzes fin<strong>de</strong>t sich im<br />

GCA, welcher später vorgestellt wird.<br />

2.3.1.4. CA Networks (CAN)<br />

In [CNG + 01] versuchen die Autoren mit Hilfe von Zellularautomaten Schlammlawinen<br />

und Muren (Gerölllawinen) zu simulieren. Da solche Simulationen oft komplexe Zusammenhänge<br />

mit vielen einwirken<strong>de</strong>n Eigenschaften abbil<strong>de</strong>n müssen, wer<strong>de</strong>n viele Zellularautomaten<br />

in einem Netzwerk eingesetzt. Dabei hat je<strong>de</strong>r CA <strong>de</strong>s Netzwerks eine<br />

bestimmte Aufgabe und an<strong>de</strong>re CAs können lesend auf die Ergebnisse zugreifen.<br />

Das setzt natürlich voraus, dass die CA nicht nur parallel, son<strong>de</strong>rn auch sequentiell<br />

nacheinan<strong>de</strong>r arbeiten können. Die aus [CNG + 01] entnommene Abbildung 2.3 zeigt <strong>de</strong>n<br />

von <strong>de</strong>n Autoren vorgestellten Mo<strong>de</strong>llaufbau und ver<strong>de</strong>utlicht, dass die CA in einem<br />

CAN sowohl parallel als auch seriell arbeiten können. Dabei entsprechen die mit A1 bis<br />

A5 nummerierten Knoten <strong>de</strong>n Zellularautomaten und die dunklen Knoten dienen zur<br />

Ablaufsteuerung.<br />

Mit <strong>de</strong>m Netzwerk wird neben einer Datenparallelität auch eine Aufgabenparallelität erreicht<br />

und es können auch solche Probleme berechnet wer<strong>de</strong>n, die sich aus unvereinbaren<br />

Teilaufgaben zusammensetzen.<br />

2.3.2. Der ”<br />

Struc<strong>tu</strong>rally Dynamic Cellular Automata“ (SDCA)<br />

Die <strong>de</strong>n SDCA auszeichnen<strong>de</strong> Neuerung ist, dass die Verbindungen zwischen <strong>de</strong>n Zellen<br />

<strong>de</strong>s Automaten zur Laufzeit verän<strong>de</strong>rt wer<strong>de</strong>n können. Dafür gibt es zwei Funktionen,<br />

21


Abbildung 2.3.: Das CAN-Netzwerk, welches von [CNG + 01] verwen<strong>de</strong>t wird, um das<br />

SCIDDICA-Mo<strong>de</strong>ll darzustellen.<br />

die Kopplungs- und die Entkopplungsfunktion, die entwe<strong>de</strong>r neue Verbindungen anlegen<br />

(koppeln) o<strong>de</strong>r bestehen<strong>de</strong> Verbindungen löschen (entkoppeln). Eine Einschränkung<br />

dabei ist, dass nur bereits verbun<strong>de</strong>ne Zellen entkoppelt wer<strong>de</strong>n dürfen und nur solche<br />

Zellen aneinan<strong>de</strong>r gekoppelt wer<strong>de</strong>n dürfen, die über genau einen Zwischenknoten eine<br />

Verbindung haben. Dieses Mo<strong>de</strong>ll wur<strong>de</strong> von Ilachinsky und Halpern vorgestellt, um so<br />

in <strong>de</strong>r Lage zu sein, u. a. das menschliche Gehirn besser nachbil<strong>de</strong>n zu können.<br />

In [Maj94] stellt <strong>de</strong>r Autor Majercik das von Ilachinsky und Halpern entworfene SDCA-<br />

Mo<strong>de</strong>ll vor und äußert seine Kritik. Daraufhin entwickelt er drei alternative Mo<strong>de</strong>lle,<br />

welche im Folgen<strong>de</strong>n näher erläutert wer<strong>de</strong>n. Zu<strong>de</strong>m enthält <strong>de</strong>r Text auch eine Betrach<strong>tu</strong>ng<br />

<strong>de</strong>r Laufzeitgeschwindigkeiten <strong>de</strong>r verschie<strong>de</strong>nen Mo<strong>de</strong>lle sowie eine Konstruktion,<br />

um einen beliebigen CA mit einem ULRL 4 SDCA mit Speed-Up von zwei zu simulieren.<br />

2.3.2.1. Das Relative Location Mo<strong>de</strong>ll<br />

In diesem Mo<strong>de</strong>ll kann die Zustandsübergangsfunktion nicht nur auf <strong>de</strong>n Sta<strong>tu</strong>s einer<br />

je<strong>de</strong>n Nachbarzelle zugreifen, son<strong>de</strong>rn kennt auch <strong>de</strong>ren (von <strong>de</strong>r anfragen<strong>de</strong>n Zelle aus<br />

gesehen) relative Adresse. Die Verbindungsübergangsfunktion kennt zu je<strong>de</strong>r Zelle <strong>de</strong>s<br />

Auotmaten <strong>de</strong>n Verbindungssta<strong>tu</strong>s (0 = unverbun<strong>de</strong>n, 1 = verbun<strong>de</strong>n, 2 = im nächsten<br />

Schritt verbindbar) und han<strong>de</strong>lt dann <strong>de</strong>n Regeln entsprechend, legt also im nächsten<br />

Schritt eine Verbindung an, zerstört Verbindungen im nächsten Schritt o<strong>de</strong>r lässt die<br />

Verbindung unberührt. Sowohl die Zustandsübergangs- als auch die Verbindungsübergangsfunktion<br />

müssen rekursiv berechenbar sein.<br />

Dieses Mo<strong>de</strong>ll kann variiert wer<strong>de</strong>n, in<strong>de</strong>m die Anzahl <strong>de</strong>r Verbindungen beschränkt<br />

wird (Boun<strong>de</strong>d Links Relative Location Mo<strong>de</strong>l) o<strong>de</strong>r nicht (Unboun<strong>de</strong>d Links Relative<br />

Location Mo<strong>de</strong>l).<br />

Der Nachteil dieses Mo<strong>de</strong>lls ist, dass man bei vielen Simulationen nicht voraussetzen<br />

kann, dass eine Zelle wirklich die relative Adresse ihrer Nachbarn kennt. So ist einer<br />

Zelle <strong>de</strong>s menschlichen Gehirns z. B. nicht bekannt, wo genau ihr Verbindungspartner<br />

sitzt. Sie benötigt dieses Wissen auch nicht, um ein korrektes Ergebnis zu liefern.<br />

4 ULRL = Unboun<strong>de</strong>d Links Relative Location<br />

22


2.3.2.2. Das Labeled Link Mo<strong>de</strong>ll<br />

In diesem Mo<strong>de</strong>ll kennt die Zelle die relative Adresse ihrer Nachbarn nicht. Dafür hat<br />

je<strong>de</strong> Verbindung <strong>de</strong>r Zelle ein Label, welches die Zustandsübergangsfunktion mit in die<br />

Berechnungen einbeziehen kann.<br />

In diesem Mo<strong>de</strong>ll (wie in allen an<strong>de</strong>ren hier vorgestellten SDCA Mo<strong>de</strong>llen) wer<strong>de</strong>n die<br />

Verbindungen als bidirektional angesehen. Trotz<strong>de</strong>m können zwei Nachbarzellen verschie<strong>de</strong>ne<br />

Labels für ein und dieselbe Verbindung haben. Auf diese Weise können auch<br />

unidirektionale Verbindungen mo<strong>de</strong>lliert wer<strong>de</strong>n, in<strong>de</strong>m eine Zelle das Label <strong>de</strong>r Verbindung<br />

auf ignorieren setzt.<br />

Die Verbindungsübergangsfunktion arbeitet vergleichbar zu <strong>de</strong>r <strong>de</strong>s vorhergehen<strong>de</strong>n Mo<strong>de</strong>lls.<br />

Auch hier kann wie<strong>de</strong>r die Anzahl <strong>de</strong>r Verbindungen einer Zelle beschränkt wer<strong>de</strong>n<br />

(Boun<strong>de</strong>d Links Labeled Link Mo<strong>de</strong>l) o<strong>de</strong>r nicht (Unboun<strong>de</strong>d Links Labeled Link Mo<strong>de</strong>l).<br />

Auch wenn dieses Mo<strong>de</strong>ll schon annehmbarer als das vorherige ist, so verfügt es doch<br />

noch nicht über eine endliche Übergangstabelle wie <strong>de</strong>r konventionelle CA. Um diese<br />

endliche Übergangstabelle zu erreichen, wird das Boun<strong>de</strong>d Links Labeled Link Mo<strong>de</strong>l<br />

weiter eingeschränkt, in<strong>de</strong>m die Menge <strong>de</strong>r möglichen Labels endlich ist (Finite Labels<br />

Labeled Links Mo<strong>de</strong>l). Da je<strong>de</strong> Zelle eine endliche Anzahl an Verbindungen hat und<br />

diese Verbindungen jeweils eine von endlich vielen unterschiedlichen Labels haben kann,<br />

entstehen nur endlich viele Einträge in <strong>de</strong>r Übergangstabelle.<br />

2.3.2.3. Das Symmetric Mo<strong>de</strong>ll<br />

In diesem Mo<strong>de</strong>ll weiß eine Zelle nur, welche Zustän<strong>de</strong> wie oft unter ihren Nachbarn<br />

vertreten sind, aber nicht, welcher Nachbar welchen Zustand hat. Bei Problemen wie<br />

<strong>de</strong>m ”<br />

Game of Life“ ist dies keine Einschränkung, aber es kann Probleme geben, bei<br />

<strong>de</strong>nen diese Einschränkung massiv ist.<br />

Majercik betont, dass dieses Mo<strong>de</strong>ll als Vermittler zwischen <strong>de</strong>m ursprünglichen Mo<strong>de</strong>ll<br />

von Ilachinsky und Halpern und <strong>de</strong>n eben vorgestellten Mo<strong>de</strong>llen dienen soll.<br />

Auch in diesem Mo<strong>de</strong>ll kann wie<strong>de</strong>r zwischen einer beschränkten Anzahl von Verbindungen<br />

pro Zelle (Boun<strong>de</strong>d Links Symmetric) und einer unbeschränkten Anzahl (Unboun<strong>de</strong>d<br />

Links Symmetric) unterschei<strong>de</strong>n.<br />

2.3.3. Der ”<br />

Dynamic Struc<strong>tu</strong>re Cellular Automata“ (DSCA)<br />

Der DSCA erweitert <strong>de</strong>n CA <strong>de</strong>rmaßen, dass das Mo<strong>de</strong>ll nun auch auf Eingaben von<br />

außen reagieren kann.<br />

Innerhalb <strong>de</strong>s CA können Nachrichten versen<strong>de</strong>t wer<strong>de</strong>n, die verschie<strong>de</strong>ne Eventtypen<br />

haben und die Funktion <strong>de</strong>r Zelle beeinflussen. In [MWIS02] wird dieses Prinzip anhand<br />

23


von Feuer-Simulationen erklärt, wobei <strong>de</strong>r CA die Simulation <strong>de</strong>r Feuerausbrei<strong>tu</strong>ng vornimmt.<br />

Damit auch Funkenflug in die Berechnungen eingehen kann, existiert ein Generator,<br />

welcher entsprechen<strong>de</strong> Nachrichten an <strong>de</strong>n CA sen<strong>de</strong>t und damit <strong>de</strong>ssen Zustän<strong>de</strong><br />

aktiv beeinflusst.<br />

Die Vorstellung von Nachrichten, welche an Zellen <strong>de</strong>s CA geschickt wer<strong>de</strong>n, ob über<br />

einen internen Verteiler o<strong>de</strong>r nicht, scheint zunächst einmal <strong>de</strong>m Prinzip <strong>de</strong>s CA zu<br />

wi<strong>de</strong>rsprechen. Dieses besagt, dass nur lesend auf Zellen zugegriffen wer<strong>de</strong>n kann.<br />

Allerdings kann <strong>de</strong>r interne Verteiler so organisiert sein, dass er selbst kein aktives Mitglied<br />

<strong>de</strong>s CA ist, aber alle Zellen <strong>de</strong>s CA lesend auf ihn zugreifen können. Dieser Verteiler<br />

stellt dann für je<strong>de</strong> Zelle einen Port bereit, auf <strong>de</strong>n die Zelle jeweils vor ihrem Berechnungsschritt<br />

lesend zugreift, um zu sehen, ob eine Nachricht für sie vorhan<strong>de</strong>n ist. Da <strong>de</strong>r<br />

verwen<strong>de</strong>te CA asynchron ist, muss <strong>de</strong>r Verteiler reagieren, wenn die Nachricht abgeholt<br />

wird und diese löschen, da sonst eine Nachricht mehrfach von <strong>de</strong>r Zelle abgeholt wer<strong>de</strong>n<br />

kann.<br />

2.3.4. Der ”<br />

Global Cellular Automata“ (GCA)<br />

Der GCA ([HVH03]) erlaubt es Zellen, im Laufe <strong>de</strong>r Evolution ihre Nachbarschaft zu<br />

verän<strong>de</strong>rn. Außer<strong>de</strong>m können zwei verschie<strong>de</strong>ne Zellen eines GCA verschie<strong>de</strong>ne Übergangsfunktionen<br />

und Nachbarschaften haben. Die Zellen eines GCA können also verschie<strong>de</strong>ne<br />

Aufgaben erfüllen.<br />

Mit <strong>de</strong>r Möglichkeit, die Nachbarschaft immer an die ak<strong>tu</strong>elle Aufgabenstellung anzupassen,<br />

ergibt sich ein sehr mächtiges Mo<strong>de</strong>ll, welches aber Einschränkungen unterliegt,<br />

um noch realisierbar zu sein. So muss z. B. die Gesamtheit <strong>de</strong>r im Verlauf <strong>de</strong>r Berechnungen<br />

einbezogenen Nachbarzellen schon beim Aufbau bekannt sein, damit die benötigten<br />

Verbindungen eingeplant wer<strong>de</strong>n können.<br />

Um die ak<strong>tu</strong>elle Nachbarschaft bestimmen zu können, ist es nötig, dass die Nachbarschaft<br />

entwe<strong>de</strong>r wie beim SDCA über eine Übergangsfunktion bestimmt wer<strong>de</strong>n kann o<strong>de</strong>r von<br />

Anfang an für je<strong>de</strong> Zelle für je<strong>de</strong> Generation die Nachbarschaft, z. B. in einer Tabelle,<br />

festgelegt ist.<br />

Der GCA erlaubt es, dass Zellen, welche nicht direkt zur Zelle benachbart liegen, in die<br />

Nachbarschaft <strong>de</strong>r Zelle aufgenommen wer<strong>de</strong>n. Somit verzichtet dieses Mo<strong>de</strong>ll auf eine<br />

Gitterstruk<strong>tu</strong>r wodurch sich Nachrichten schneller im Automaten ausbreiten können.<br />

Die zellulare Lichtgeschindigkeit 5 (s. u.a. [Hoc98]) wird in diesem Mo<strong>de</strong>ll überschritten.<br />

Einen Überblick über <strong>de</strong>n GCA liefert auch [Hee01], dort wird <strong>de</strong>r Begriff <strong>de</strong>r zellularen<br />

Lichtgeschwindigkeit näher erläutert.<br />

5 Im CA spricht man von <strong>de</strong>r Zellularen Lichtgeschwindigkeit, da Nachrichten bei je<strong>de</strong>r Generation nur<br />

um eine Zelle weitergereicht wer<strong>de</strong>n können. Der GCA durchbricht diese Beschränkung, da nun die<br />

Nachbarschaftsbeziehung dynamisch ist und somit mehrere Zellen bei einer Generation übersprungen<br />

wer<strong>de</strong>n können (bezüglich <strong>de</strong>r Gitterstruk<strong>tu</strong>r).<br />

24


Da <strong>de</strong>r GCA dynamisch ist, können sich seine Daten und seine Verbindungen (Pointer)<br />

zur Laufzeit abhängig von gewissen Gegebenheiten än<strong>de</strong>rn. Eine grobe Unterteilung, die<br />

auch in [Hee01] getroffen wird, ist die Unterteilung in Zeitabhängigkeit, Ortsabhängigkeit<br />

und Datenabhängigkeit. Zusätzlich wird hier noch eine Abhängigkeit von <strong>de</strong>n Verbindungen<br />

an sich eingeführt. Diese vier Punkte können (und wer<strong>de</strong>n im weiteren) noch<br />

weiter differenziert wer<strong>de</strong>n:<br />

• Die Datenabhängigkeit kann dahingehend untersucht wer<strong>de</strong>n, ob die Daten bzw.<br />

die Verbindungen <strong>de</strong>r Zelle im nächsten Schritt von <strong>de</strong>n eigenen Daten und/o<strong>de</strong>r<br />

von <strong>de</strong>n Daten ihrer Nachbarn abhängt. Im weiteren wer<strong>de</strong>n die eigenen Daten mit<br />

d und die Daten <strong>de</strong>r Nachbar-Zellen mit d* bezeichnet.<br />

• Die Zeitabhängigkeit benötigt keiner weiteren Verfeinerung. Es muss nur festgestellt<br />

wer<strong>de</strong>n, ob die Zellen zu einer bestimmten Zeit immer einer bestimmten<br />

Verän<strong>de</strong>rung unterliegen. Die Zeit wird im weiteren mit t bezeichnet.<br />

• Die Ortsabhängigkeit kann auch dahingehend unterteilt wer<strong>de</strong>n, dass geschaut<br />

wer<strong>de</strong>n muss, ob eine Verän<strong>de</strong>rung <strong>de</strong>r Daten bzw. <strong>de</strong>r Verbindungen einer Zelle<br />

davon abhängig ist, wo die eigene Zelle bzw. die Nachbarzelle angeordnet ist. Der<br />

Ort <strong>de</strong>r eigenen Zelle wird im weiteren mit l <strong>de</strong>r Ort <strong>de</strong>r Nachbar-Zelle mit l*<br />

bezeichnet.<br />

• Die Verbindungsabhängigkeit bezeichnet eine Abhängigkeit <strong>de</strong>r Daten o<strong>de</strong>r <strong>de</strong>r<br />

Verbindungen in <strong>de</strong>r nächsten Generation von <strong>de</strong>r momentanen Verbindung <strong>de</strong>r<br />

eigenen Zelle o<strong>de</strong>r <strong>de</strong>r Nachbar-Zellen. Ein Beispiel hierfür fin<strong>de</strong>t sich in Kapitel<br />

4. Die Verbindung <strong>de</strong>r Zelle wird mit p, die Verbindung <strong>de</strong>r Nachbar-Zelle mit p*<br />

bezeichnet.<br />

Die Unterteilungen sind immer für eine Zelle mit einem Nachbarn formuliert. Betrachtet<br />

man eine Zelle, die mehrere Nachbarn hat, dann bezeichnen d*,l* und p* eine geeignete<br />

Verknüpfung aller Nachbarn. Es erscheint im weiteren Verlauf sinnvoll, die Daten und<br />

die Verbindungen einer Zelle getrennt zu betrachten, da sie sich unabhängig voneinan<strong>de</strong>r<br />

än<strong>de</strong>rn können. Mit Hilfe <strong>de</strong>r Unterteilung 6 kann man die Daten (d) und die Verbindung<br />

(p) <strong>de</strong>r Zelle in <strong>de</strong>r nächsten Generation als Funktion ihrer Abhängigkeiten formulieren:<br />

d’= f(d,d*,t,l,l*,p,p*) und p’= g(d,d*,t,l,l*,p,p*).<br />

Je nach auf <strong>de</strong>m GCA realisierten Algorithmus ist die Funktion nur von einem Teil <strong>de</strong>r<br />

Parameter abhängig. In Abbildung 2.4 wur<strong>de</strong> die weitere Unterteilung <strong>de</strong>r Daten-, Ortsund<br />

Verbindungsabhängigkeit ignoriert. Mit Hilfe <strong>de</strong>s dort angegebenen Baums können<br />

die Parameter <strong>de</strong>r Verbindungs- bzw. Datenübergangsfunktion gefun<strong>de</strong>n wer<strong>de</strong>n.<br />

6 Die Unterteilung repräsentiert nur entwe<strong>de</strong>r die Abhängigkeit <strong>de</strong>r Daten o<strong>de</strong>r <strong>de</strong>r Verbindungen.<br />

Zwar kann die Unterteilung auf bei<strong>de</strong> angewen<strong>de</strong>t wer<strong>de</strong>n, aber es ist nicht möglich, bei<strong>de</strong> Abhängigkeiten<br />

mit Hilfe dieser Unterteilung zeitgleich zu bestimmen.<br />

25


Abbildung 2.4.: Innerhalb <strong>de</strong>s Baums steht in <strong>de</strong>m Kasten immer eine Abhängigkeit. Ist<br />

diese Abhängigkeit gegeben, so muss die Abzweigung nach links, sonst<br />

die nach rechts verwen<strong>de</strong>t wer<strong>de</strong>n. Wur<strong>de</strong>n alle Knoten abgelaufen, kann<br />

man ablesen, von welchen Variablen z. B. die Daten <strong>de</strong>r nächsten Generation<br />

abhängig sind.<br />

Da das Mo<strong>de</strong>ll <strong>de</strong>s GCA im Weiteren zur Mo<strong>de</strong>llierung von <strong>Algorithmen</strong> genutzt wird,<br />

wer<strong>de</strong>n dort noch Beispiele aufgezeigt, wie man die Möglichkeiten <strong>de</strong>s GCA effizient<br />

nutzen kann.<br />

26


3. Die Mo<strong>de</strong>llierung von<br />

Graphenalgorithmen auf <strong>de</strong>m GCA<br />

Graphenalgorithmen haben auch heute noch eine große Be<strong>de</strong>u<strong>tu</strong>ng in <strong>de</strong>r Informatik, da<br />

man mit ihrer Hilfe viele Probleme sehr anschaulich und effizient lösen kann. Graphen<br />

wer<strong>de</strong>n <strong>de</strong>finiert durch zwei Mengen: die Menge V <strong>de</strong>r Knoten und die Menge E <strong>de</strong>r<br />

Kanten. Dabei verbin<strong>de</strong>t eine Kante aus E immer zwei Knoten aus V. Kanten können<br />

gerichtet (die Kante kann nur in eine Rich<strong>tu</strong>ng durchlaufen wer<strong>de</strong>n) o<strong>de</strong>r ungerichtet<br />

(die Kante kann in bei<strong>de</strong>n Rich<strong>tu</strong>ngen durchlaufen wer<strong>de</strong>n) sein.<br />

Ein Graph selber kann in vielen Varianten im Rechner repräsentiert sein, wobei eine <strong>de</strong>r<br />

häufigeren Darstellungen die Matrix ist. Zur Veranschaulichung dieser Matrixrepräsentation<br />

soll hier <strong>de</strong>r Graph aus Abbildung 3.1 dienen, welcher durch die folgen<strong>de</strong> Matrix<br />

A repräsentiert wer<strong>de</strong>n kann.<br />

Abbildung 3.1.: Ein gerichteter Beispielgraph, welcher sowohl einen Zyklus enthält als<br />

auch einen Knoten, <strong>de</strong>r keine ausgehen<strong>de</strong>n Kanten hat.<br />

⎛<br />

A =<br />

⎜<br />

⎝<br />

0 1 0 1 0<br />

0 0 1 0 0<br />

1 0 0 0 0<br />

0 0 0 0 1<br />

0 0 0 0 0<br />

Der Vorteil dieser Darstellung ist, dass die i-te Zeile <strong>de</strong>r Matrix die ausgehen<strong>de</strong>n Verbindungen<br />

und die i-te Spalte die eingehen<strong>de</strong>n Verbindungen <strong>de</strong>s i-ten Knotens repräsentiert.<br />

Allerdings muss man alle Zeileneinträge <strong>de</strong>r i-ten Zeile abgehen, um genau festzu-<br />

⎞<br />

⎟<br />

⎠<br />

27


stellen, welche Nachfolger <strong>de</strong>r Knoten i hat. Möchte man aber testen, ob eine Verbindung<br />

zwischen <strong>de</strong>m i-ten und <strong>de</strong>m j-ten Knoten existiert, so reicht eine Anfrage.<br />

Einer <strong>de</strong>r größten Nachteile dieser Repräsentation ist allerdings, dass man auf einem<br />

klassischen Einprozessor-System minimal O(n 2 ) Schritte benötigt, wenn ein Problem<br />

mit <strong>de</strong>r Matrizendarstellung arbeitet und einen Großteil <strong>de</strong>s Graphen betrachten muss.<br />

Im Laufe dieser Arbeit wird <strong>de</strong>utlich wer<strong>de</strong>n, dass es nicht möglich ist, diesen Aufwand zu<br />

minimieren. Allerdings ist es möglich, Teile <strong>de</strong>s Aufwands auf die Hardware zu verlagern<br />

und so einen Speed-Up zu erzielen.<br />

Diese Aussage wird am Beispiel <strong>de</strong>s Warshall-Algorithmus noch <strong>de</strong>utlicher wer<strong>de</strong>n, wobei<br />

klar sein sollte, dass ein Aufwand von O(n) be<strong>de</strong>utet, dass <strong>de</strong>r Algorithmus eine Laufzeit<br />

von O(n) hat, falls nicht explizit etwas an<strong>de</strong>res erwähnt wird.<br />

3.1. Erkennung von zusammenhängen<strong>de</strong>n Komponenten<br />

eines Graphen<br />

In vielen Aufgabenstellungen aus <strong>de</strong>r Praxis ist es wichtig, zu erkennen, ob Komponenten<br />

eines Graphen zusammenhängen. So möchte man z. B. effizient testen, ob alle Computer<br />

eines Netzwerks verbun<strong>de</strong>n sind.<br />

Auf einem Rechner mit einem Prozessor kann man hier u. a. <strong>de</strong>n Warshall-Algorithmus<br />

anwen<strong>de</strong>n, welcher die transitive Hülle berechnet und somit die Erreichbarkeitsmatrix<br />

liefert. Dabei steht eine 1 in Zeile A und Spalte B <strong>de</strong>r Matrix immer dafür, dass es einen<br />

Weg zwischen <strong>de</strong>m Punkt A und <strong>de</strong>m Punkt B gibt, eine 0 <strong>de</strong>mentsprechend dafür, dass<br />

kein Weg existiert. Der Algorithmus von Warshall benötigt auf einem Einprozessor-<br />

System eine Laufzeit von O(n 3 ). Im folgen<strong>de</strong>n Abschnitt wird schrittweise hergeleitet,<br />

wie man <strong>de</strong>n Warshall-Algorithmus auf <strong>de</strong>m GCA mo<strong>de</strong>llieren kann und wie es möglich<br />

ist, eine Laufzeit von O(n) zu erreichen.<br />

3.1.1. Der Warshall-Algorithmus<br />

Der Warshall-Algorithmus ist eine Spezialisierung <strong>de</strong>s Kleene-Algorithmus und je nach<br />

Litera<strong>tu</strong>r berechnet er die reflexive-transitive-Hülle 1 o<strong>de</strong>r nur die transitive Hülle 2 . Ein<br />

Vorteil <strong>de</strong>r Berechnung <strong>de</strong>r reinen transitiven Hülle ist, dass man sehr leicht mit <strong>de</strong>m<br />

Warshall-Algorithmus testen kann, ob ein Graph Zyklen enthält.<br />

Da es für die Erkennung von zusammenhängen<strong>de</strong>n Komponenten irrelevant ist, ob die<br />

Hülle reflexiv ist o<strong>de</strong>r nicht, wird im weiteren <strong>de</strong>r Warshall-Algorithmus verwen<strong>de</strong>t,<br />

1 Bei <strong>de</strong>r reflexiven Hülle existiert von je<strong>de</strong>m Knoten eine Verbindung zu sich selber. Auf <strong>de</strong>r Diagonalen<br />

stehen also nur Einsen. Die reflexive-transitive Hülle erfüllt die Bedingung <strong>de</strong>r reflexiven Hülle<br />

zusätzlich zu <strong>de</strong>n Bedingungen <strong>de</strong>r transitiven Hülle.<br />

2 Hier steht auf <strong>de</strong>r Diagonale nur dann eine 1, wenn es von <strong>de</strong>m Knoten aus möglich ist, wie<strong>de</strong>r zu<br />

<strong>de</strong>m Knoten zu kommen<br />

28


welcher rein die transitive Hülle berechnet. Nachzulesen sind <strong>de</strong>r Warshall-Algorithmus<br />

und <strong>de</strong>r im nächsten Kapitel folgen<strong>de</strong> Floyd-Warshall-Algorithmus u.a. in [CLRS01] auf<br />

Seite 632-634.<br />

3.1.1.1. Der Warshall auf einem Einprozessor-System<br />

Eine Eigenschaft, die alle <strong>Algorithmen</strong> haben, welche vom Kleene-Algorithmus abstammen,<br />

ist die Nutzung <strong>de</strong>s Transitknotenkonzepts. Transitknoten be<strong>de</strong>utet hierbei, dass<br />

je<strong>de</strong>r Knoten i nur dann neue Verbindungen aufnehmen darf, wenn für ihn eine Verbindung<br />

zum Transitknoten besteht. Alle an<strong>de</strong>ren Verbindungen <strong>de</strong>s Knotens wer<strong>de</strong>n für<br />

diesen Schritt ignoriert.<br />

Besteht die Verbindung zum Transitknoten, so kann <strong>de</strong>r Knoten i zu je<strong>de</strong>m Knoten j, mit<br />

<strong>de</strong>m <strong>de</strong>r Transitknoten verbun<strong>de</strong>n ist, seinerseits eine Verbindung aufbauen. Es wer<strong>de</strong>n<br />

also Wege <strong>de</strong>r Länge zwei erlaubt, welche über <strong>de</strong>n Transitknoten gehen müssen.<br />

Programmiert man <strong>de</strong>n Warshall-Algorithmus, so gestaltet er sich einfach: er besteht<br />

aus drei ineinan<strong>de</strong>r geschachtetelten Schleifen und <strong>de</strong>r Formulierung <strong>de</strong>s Transitknotenprinzips<br />

(Zeile 4 im nachfolgen<strong>de</strong>n Algorithmus). In C formuliert sieht <strong>de</strong>r Algorithmus<br />

dann folgen<strong>de</strong>rmaßen aus:<br />

Listing 3.1: Der Warshall-Algorithmus<br />

1 for (k=0; k < AnzahlKnoten ; k++) {<br />

2 for ( i =0; i < AnzahlKnoten ; i++) {<br />

3 for ( j =0; j < AnzahlKnoten ; j++) {<br />

4 A[ i ] [ j ]= A[ i ] [ j ] | | (A[ i ] [ k ] && A[ k ] [ j ] )<br />

5 }<br />

6 }<br />

7 }<br />

Man kann <strong>de</strong>n Algorithmus noch ein wenig verbessern, in<strong>de</strong>m man die Abfrage, ob eine<br />

Verbindung von i nach k besteht (die Abfrage A[i][k] in Zeile 4), vor die innere Schleife<br />

zieht. In <strong>de</strong>r Mo<strong>de</strong>llierung auf <strong>de</strong>m GCA wird <strong>de</strong>r modifiziert Warshall-Algorithmus<br />

verwen<strong>de</strong>t, welche auch noch ausnutzt, dass eine Überprüfung keine Än<strong>de</strong>rung ergibt,<br />

wenn <strong>de</strong>r zu modifizieren<strong>de</strong> Knoten <strong>de</strong>r Transitknoten ist:<br />

Listing 3.2: Der leicht modifizierte Warshall-Algorithmus<br />

1 for (k=0; k < AnzahlKnoten ; k++) {<br />

2 for ( i =0; i < AnzahlKnoten ; i++) {<br />

3 if (k!= i && A[ i ] [ k]==1) {<br />

4 for ( j =0; j < AnzahlKnoten ; j++) {<br />

5 A[ i ] [ j ]= A[ i ] [ j ] | | A[ k ] [ j ] )<br />

6 }<br />

7 }<br />

8 }<br />

9 }<br />

29


Trotz dieser Verbesserung spart man im worst-case nur einen Durchlauf <strong>de</strong>r inneren<br />

Schleife und somit ist <strong>de</strong>r Aufwand auf O(n 3 ) abzuschätzen. Da <strong>de</strong>r best-case (die innere<br />

Schleife wird so selten wie möglich durchlaufen, das be<strong>de</strong>utet, nur auf <strong>de</strong>r Diagonalen<br />

stehen Einser) sehr unwahrscheinlich ist, wäre nur noch <strong>de</strong>r average-case interessant. In<br />

<strong>de</strong>r Regel wird man eine Zeile ca. zur Hälte mit Einsen befüllt haben. Daraus folgt, dass<br />

man ca. die Hälfte <strong>de</strong>r inneren Schleifendurchläufe einsparen kann. Dies ergibt dann<br />

eine Laufzeit von O(n 2 × n 2 ), was aber in <strong>de</strong>r O-Notation wie<strong>de</strong>r auf O(n3 ) abgeschätzt<br />

wird. Diese Modifikation bringt also einen Laufzeitgewinn, aber die Laufzeitkomplexität<br />

än<strong>de</strong>rt sich dadurch nicht.<br />

3.1.1.2. Der Warshall-Algorithmus auf <strong>de</strong>m GCA<br />

Um einen Algorithmus auf einem parallelen Mo<strong>de</strong>ll wie <strong>de</strong>m GCA effizient zu mo<strong>de</strong>llieren,<br />

muss zunächst hergeleitet wer<strong>de</strong>n, welche Schritte parallelisierbar sind:<br />

Bei <strong>de</strong>m Warshall-Algorithmus ist ersichtlich, dass die Herlei<strong>tu</strong>ng <strong>de</strong>r Verbindungen <strong>de</strong>r<br />

Länge 2 jeweils nur von <strong>de</strong>m Startknoten und <strong>de</strong>m Transitknoten abhängig sind. Alle<br />

an<strong>de</strong>ren Knoten wer<strong>de</strong>n an dieser Stelle ignoriert. Also kann man alle Knoten parallel<br />

als Startknoten betrachten und die Verbindungsmöglichkeiten testen. Hierfür benötigt<br />

man O(n) Prozessoren, um wirklich alle Knoten parallel abzuarbeiten.<br />

Eine weitere Möglichkeit zur Parallelisierung bietet sich für je<strong>de</strong>n Knoten bei <strong>de</strong>r Herstellung<br />

<strong>de</strong>r Verbindungen an sich. Im Normalfall wird zuerst überprüft, ob <strong>de</strong>r Startknoten<br />

eine Verbindung zum Transitknoten hat, und falls dies <strong>de</strong>r Fall ist, wird für je<strong>de</strong>n an<strong>de</strong>ren<br />

Knoten geprüft, ob es nun eine Verbindung zu ihm gibt o<strong>de</strong>r schon früher gab. Parallelisiert<br />

man diesen Vorgang, so wer<strong>de</strong>n die Verbindungen zu allen Knoten ausgehend von<br />

einem festem Startknoten und einem festen Transitknoten gleichzeitig gesucht.<br />

Mo<strong>de</strong>lliert man nun diese Parallelisierungsvorschläge auf <strong>de</strong>m GCA, so äußert sich das<br />

folgen<strong>de</strong>rmaßen:<br />

• Um bei einem gegebenem Transitknoten alle Knoten parallel als Startknoten abzuarbeiten,<br />

muss dafür gesorgt wer<strong>de</strong>n, dass je<strong>de</strong> Zeile <strong>de</strong>r Matrix durch eine Zelle<br />

repräsentiert wird [3.2]. Die Zellen können alle parallel arbeiten und garantieren<br />

so die gewünschte Parallelität. Da nur ein lesen<strong>de</strong>r Zugriff auf die Zelle, welche<br />

<strong>de</strong>n Transitknoten repräsentiert, nötig ist, ist diese Art <strong>de</strong>r Mo<strong>de</strong>llierung mit <strong>de</strong>n<br />

Bedingungen <strong>de</strong>s GCA konform.<br />

• Um bei einem gegebenem Transitknoten und einem gegebenem Startknoten alle<br />

Verbindungen parallel zu prüfen und zu modifizieren, gibt es zwei Möglichkeiten:<br />

Bei <strong>de</strong>r ersten Möglichkeit wird je<strong>de</strong>r einzelne Eintrag einer Zeile durch eine Zelle<br />

repräsentiert. Es wer<strong>de</strong>n also n 2 Zellen benötigt. Eines <strong>de</strong>r dabei auftreten<strong>de</strong>n<br />

Probleme ist, dass die Zellen feststellen müssen, mit welchen an<strong>de</strong>ren Zellen sie zu<br />

kommunizieren haben. Da eine Zeile durch mehrere Zellen repräsentiert wird, ist<br />

einerseits auf die Zelle <strong>de</strong>r eigenen Zeile lesend zuzugreifen, welche die Verbindung<br />

30


zum Transitknoten repräsentiert, und an<strong>de</strong>rerseits ist die entsprechen<strong>de</strong> Zelle <strong>de</strong>s<br />

Transitknotens zu lesen. Dies führt einerseits zu einem hohen Kommunikationsbedarf<br />

3 und an<strong>de</strong>rerseits zu <strong>de</strong>r For<strong>de</strong>rung einer Logik, die erkennt, welche Zelle<br />

angesprochen wer<strong>de</strong>n muss.<br />

Die zweite Möglichkeit besteht darin, die Zeilen <strong>de</strong>r Matrix jeweils wie<strong>de</strong>r durch<br />

eine Zelle repräsentieren zu lassen. Um dabei einen parallelen Abgleich <strong>de</strong>r Zeileneinträge<br />

zu garantieren, wird ein Bussystem verwen<strong>de</strong>t. Je<strong>de</strong>r Knoten weiß, welcher<br />

Knoten ak<strong>tu</strong>ell <strong>de</strong>r Transitknoten ist, also auch <strong>de</strong>r Transitknoten an sich. Beim<br />

Start <strong>de</strong>s Taktes legt <strong>de</strong>r Transitknoten seine Zeile auf <strong>de</strong>n Bus und je<strong>de</strong>r Knoten,<br />

welcher eine Verbindung zu <strong>de</strong>m Transitknoten hat, kann sich <strong>de</strong>ssen Werte<br />

dann vom Bus holen. Zellen, welche keine Verbindung zum Transitknoten haben,<br />

ignorieren <strong>de</strong>n Bus bis zum nächsten Taktzyklus. Bei dieser Realisierung wer<strong>de</strong>n<br />

O(n) Verbindungen benötigt, allerdings ist es möglich, gleich eine Logik einzubauen,<br />

welche <strong>de</strong>n i-ten Zeileneintrag mit <strong>de</strong>m i-ten Eintrag auf <strong>de</strong>m Bus vero<strong>de</strong>rt.<br />

Die zugrun<strong>de</strong>liegen<strong>de</strong> Logik ist also sehr einfach und ebenso effizient wie <strong>de</strong>r erste<br />

Lösungsansatz.<br />

Bei<strong>de</strong> Lösungsansätze erfüllen die Anfor<strong>de</strong>rung. Der erste Lösungsansatz benötigt<br />

insgesamt O(n 2 ) Prozessoren und O(1) Lei<strong>tu</strong>ngen. Der zweite Ansatz kommt mit<br />

O(n) Prozessoren aus, benötigt dafür aber auch ein Verbindungssystem mit O(n)<br />

Lei<strong>tu</strong>ngen.<br />

Abbildung 3.2.: Je<strong>de</strong> Zelle kennt eine Zeile <strong>de</strong>r Matrix und ihre ID.<br />

Abbildung 3.3.: Die 5 Zellen <strong>de</strong>s GCA dieses Beispiels wer<strong>de</strong>n wie abgebil<strong>de</strong>t initialisiert.<br />

Beispiel Wenn <strong>de</strong>r Graph aus Abbildung 3.1 auf <strong>de</strong>n GCA abgebil<strong>de</strong>t wer<strong>de</strong>n soll,<br />

dann wird <strong>de</strong>r GCA wie in 3.3 initialisiert. Der Ablauf <strong>de</strong>s Warshall-Algorithmus, <strong>de</strong>r<br />

in 3.4 anhand <strong>de</strong>s Graphen veranschaulicht wird, geschieht auf <strong>de</strong>m GCA dann wie in<br />

Abbildung 3.5.<br />

3 Die i-te Zelle einer Zeile muss sowohl eine Verbindung zu allen an<strong>de</strong>ren Zellen ihrer eigenen Zeile als<br />

auch eine Verbindung zu <strong>de</strong>r i-ten Zelle aller an<strong>de</strong>ren Zeilen haben.<br />

31


Abbildung 3.4.: Der Ablauf <strong>de</strong>s Warshall-Algorithmus in <strong>de</strong>r Graphen-Darstellung. Der<br />

Transitknoten ist grau hinterlegt, neu hinzugefügte Kanten gestrichelt<br />

dargestellt.<br />

32


In Abbildung 3.4 entspricht <strong>de</strong>r Graph a) <strong>de</strong>m ersten Schleifendurchlauf <strong>de</strong>r Schleife<br />

aus Zeile 1. Knoten v 0 wird als Transitknoten gewählt und alle Kanten eingefügt, die<br />

durch Wege über diesen Transitknoten möglich wer<strong>de</strong>n. In diesem Fall hat nur v 2 eine<br />

Kante zu v 0 und <strong>de</strong>mentsprechend kann auch nur v 2 neue Kanten erhalten. Diese neuen<br />

Kanten von v 2 nach v 1 und v 3 wer<strong>de</strong>n in Abbildung 3.4 gestrichelt dargestellt. Nach<br />

<strong>de</strong>m gleichen Prinzip sind auch die restlichen Schritte <strong>de</strong>s Warshall-Algorithmus in <strong>de</strong>n<br />

Abbildungsteilen b) - e) dargestellt.<br />

Abbildung 3.5.: Der Ablauf <strong>de</strong>s Warshall-Algorithmus auf <strong>de</strong>m GCA. Da die Zell-ID hier<br />

nicht benötigt wird, wur<strong>de</strong>n sie in <strong>de</strong>r Abbildung zugunsten <strong>de</strong>r Übersichtlichkeit<br />

weggelassen.<br />

In Abbildung 3.5 sind die einzelnen Generationen <strong>de</strong>s GCA dargestellt. Dabei sind für<br />

je<strong>de</strong> Generation die Zellen mit Zellinhalt und Verbindung angegeben. In <strong>de</strong>r ersten Generation<br />

testen alle Zellen, ob sie eine Verbindung zur ersten Zelle haben und fragen gegebenenfalls<br />

<strong>de</strong>ren Zeile ab. In <strong>de</strong>r nächsten Generation ist dann die Zeile schon mit <strong>de</strong>r<br />

abgefragten Zeile vero<strong>de</strong>rt und es wird auf die nächste Zelle zugegriffen. Dies geschieht<br />

bis zur n-ten Generation, in <strong>de</strong>r auf die n-te Zelle lesend zugegriffen und somit <strong>de</strong>r letzte<br />

Schritt <strong>de</strong>s Warshall-Algorithmus ausgeführt wird. Am En<strong>de</strong> dieses n-ten Schritts repräsentieren<br />

die Zellen <strong>de</strong>s GCA die einzelnen Zeilen <strong>de</strong>r Matrix, welche die transitive<br />

Hülle <strong>de</strong>r Ausgangsmatrix ist.<br />

Es kann das in Kapitel 2.3.4 vorgestellte Schema angewen<strong>de</strong>t wer<strong>de</strong>n, um die Abhängigkeit<br />

<strong>de</strong>r Daten und <strong>de</strong>r Verbindungen für die nächste Generation festzustellen. Dabei<br />

33


wird die Realisierung betrachtet, die keinen Bus verwen<strong>de</strong>t. Statt<strong>de</strong>ssen wer<strong>de</strong>n Verbindungen<br />

zwischen <strong>de</strong>n Zellen aufgebaut, mit <strong>de</strong>ren Hilfe <strong>de</strong>r Zellinhalt ausgelesen wird.<br />

Es bedarf einer getrennten Betrach<strong>tu</strong>ng <strong>de</strong>r Abängigkeit für die Daten und für die Verbindungen:<br />

• Die Daten sind nicht abhängig von <strong>de</strong>r Zeit. Es kann nicht festgestellt wer<strong>de</strong>n,<br />

dass sich die Daten zu einem Zeitpunkt auf einen bestimmten Wert setzen. Eine<br />

Abhängigkeit von <strong>de</strong>r Zeit wür<strong>de</strong> z. B. bestehen, wenn die Daten <strong>de</strong>r Zelle in<br />

regelmässigen Abstän<strong>de</strong>n neu initialisiert wür<strong>de</strong>n.<br />

Die Daten einer Zelle än<strong>de</strong>rn sich auch nicht in Abhängigkeit von <strong>de</strong>m Ort, an<br />

<strong>de</strong>m die Zelle sich im GCA befin<strong>de</strong>t.<br />

Allerdings sind die Daten <strong>de</strong>r nächsten Generation abhängig von <strong>de</strong>n Daten <strong>de</strong>r<br />

momentanen Generation. Betrachtet man diese Abhängigkeit genauer, so stellt<br />

man fest, dass die Daten auch von <strong>de</strong>n Daten <strong>de</strong>r Nachbar-Zelle abhängig sind.<br />

Eine Abhängigkeit von <strong>de</strong>r Verbindung liegt nicht vor, die Daten än<strong>de</strong>rn sich nicht<br />

unter zur Hilfenahme <strong>de</strong>r Verbindung an sich, son<strong>de</strong>rn nur <strong>de</strong>r Daten auf die die<br />

Verbindung zeigt.<br />

Es ergibt sich die Verän<strong>de</strong>rungsfunktion: d’ = f(d,d*).<br />

• Da in <strong>de</strong>r i-ten Generation auf die i-te Zelle zugegriffen wird, sind die Verbindungen<br />

abhängig von <strong>de</strong>r Zeit.<br />

Die Verbindungen <strong>de</strong>r Zelle in <strong>de</strong>r nächsten Generation sind nicht abhängig von<br />

<strong>de</strong>m Ort o<strong>de</strong>r <strong>de</strong>r Verbindung <strong>de</strong>r Zelle.<br />

Eine Abhängigkeit <strong>de</strong>r Verbindung <strong>de</strong>r Zelle in <strong>de</strong>r nächsten Generation von <strong>de</strong>n<br />

Daten <strong>de</strong>r Zelle liegt in <strong>de</strong>r konkreten Simulation vor. Bei <strong>de</strong>m Warshall-Algorithmus<br />

repräsentiert eine Zelle immer einen Knoten. Man kann nun immer eine Verbindung<br />

aufbauen, egal ob von <strong>de</strong>m Knoten eine Verbindung zu <strong>de</strong>m i-ten Knoten<br />

existiert o<strong>de</strong>r nicht. In <strong>de</strong>m Falle sind die Verbindungen nur abhängig von <strong>de</strong>r<br />

Zeit. Allerdings kann man <strong>de</strong>n Verbindungsaufbau auch davon abhängig machen,<br />

ob in <strong>de</strong>m Graphen eine Verbindung zu <strong>de</strong>m i-ten Knoten existiert. In <strong>de</strong>m Falle<br />

ist dann die Verbindung <strong>de</strong>r nächsten Generation sowohl von <strong>de</strong>r Zeit als auch<br />

von <strong>de</strong>n eigenen Daten abhängig. Bei dieser Betrach<strong>tu</strong>ng wird davon ausgegangen,<br />

dass nur dann eine Verbindung aufgebaut wird, wenn auch im Ursprungsgraphen<br />

eine Verbindung existierte.<br />

Daraus folgt die Funktion: p’ = g(t,d).<br />

3.1.2. Der Floyd-Warshall-Algorithmus<br />

Die Ergebnismatrix <strong>de</strong>s Floyd-Warshall-Algorithmus erlaubt nicht nur die Einsicht, ob<br />

ein beliebiger Punkt A mit einem beliebigem Punkt B verbun<strong>de</strong>n ist, son<strong>de</strong>rn auch wie<br />

groß die Summe <strong>de</strong>r Kantenwer<strong>tu</strong>ngen <strong>de</strong>r kürzesten Verbindung zwischen ihnen ist.<br />

34


Da sowohl Warshall-Algorithmus als auch Floyd-Warshall-Algorithmus Spezialisierungen<br />

<strong>de</strong>s Kleene-Algorithmus sind, unterschei<strong>de</strong>n sie sich lediglich in <strong>de</strong>n Funktionen, die sie<br />

verwen<strong>de</strong>n, um die einzelnen Matrix-Einträge zu verknüpfen. Während <strong>de</strong>r Warshall-<br />

Algorithmus die logischen Funktionen und und o<strong>de</strong>r benutzt, verwen<strong>de</strong>t <strong>de</strong>r Floyd-<br />

Warshall-Algorithmus die Funktionen min und plus.<br />

Der Floyd-Warshall-Algorithmus wird immer dann benutzt, wenn nicht nur wichtig ist,<br />

ob es eine Verbindung gibt, son<strong>de</strong>rn es auch nötig ist, zu wissen, wie teuer diese Verbindung<br />

ist. Eine mögliche Anwendung wäre beispielsweise <strong>de</strong>r Aufbau eines Mehrprozessorsystems<br />

mit verschie<strong>de</strong>nen Verbindungen. Dann ist es einerseits wichtig zu wissen,<br />

dass kein Prozessor isoliert ist (also entwe<strong>de</strong>r keine an<strong>de</strong>ren Prozessoren ansprechen o<strong>de</strong>r<br />

von keinem an<strong>de</strong>ren Prozessor angesprochen wer<strong>de</strong>n kann). An<strong>de</strong>rerseits ist es wichtig<br />

zu wissen, welche Kommunikationszeiten zwischen <strong>de</strong>n Prozessoren benötigt wer<strong>de</strong>n.<br />

Der globale Takt ist dann min<strong>de</strong>stens auf <strong>de</strong>n höchsten Wert <strong>de</strong>r Ergebnismatrix <strong>de</strong>s<br />

Floyd-Warshall-Algorithmus zu setzen.<br />

Die Realisierung auf <strong>de</strong>m GCA gestaltet sich ebenso wie die Realisierung <strong>de</strong>s Warshall-<br />

Algorithmus: je<strong>de</strong> Zeile <strong>de</strong>r Matrix wird durch eine Zelle <strong>de</strong>s GCA repräsentiert. Allerdings<br />

muss beachtet wer<strong>de</strong>n, dass <strong>de</strong>r Floyd-Warshall-Algorithmus nicht auf einer<br />

binären Matrix arbeitet, son<strong>de</strong>rn auf einer Matrix von Integern.<br />

Eine Reduktion <strong>de</strong>s Zeitaufwands auf O(n) mittels O(n) Lei<strong>tu</strong>ngen ist hier zwar auch<br />

möglich, allerdings kann <strong>de</strong>r Zeileninhalt nicht einfach auf einen Bus gelegt wer<strong>de</strong>n.<br />

Möchte man alle Inhalte einer Zeile übertragen, so hat man die Möglichkeit, entwe<strong>de</strong>r<br />

jeweils <strong>de</strong>n i-ten Zeileneintrag komplett o<strong>de</strong>r das jeweils i-te Bit aller Zeileneinträge<br />

gleichzeitig zu übertragen. Der Aufwand mag bei bei<strong>de</strong>n Möglichkeiten gleich erscheinen,<br />

allerdings benötigt man beim Übertragen <strong>de</strong>s jeweils i-ten Elements O(n) Schritte,<br />

während man beim Übertragen <strong>de</strong>r Bits einer Zeile nur O(1) Schritte benötigt, da die<br />

Größe eines Integers nicht von <strong>de</strong>r Eingabegröße abhängig ist. Für kleine Graphen ist<br />

also die erste Lösung zu präferieren, während bei zunehmen<strong>de</strong>r Knotenanzahl die zweite<br />

Lösung ein besseres Verhalten an <strong>de</strong>n Tag legt.<br />

Auch hier besteht die Möglichkeit, insgesamt O(n 2 ) Prozessoren zu benutzen und je<strong>de</strong>n<br />

Eintrag <strong>de</strong>r Matrix durch eine Zelle darzustellen. Diese Lösung bedarf einer sinnvollen<br />

Verbindungslogik, welche zeitgleich die Verbindung <strong>de</strong>s eigenen Knotens zum Transitknoten<br />

und die Verbindung vom Transitknoten zum gewünschten Zielknoten abfragt.<br />

Bevor eine Addition stattfin<strong>de</strong>n darf, muss getestet wer<strong>de</strong>n, ob <strong>de</strong>r Verbindungswert<br />

zum Transitknoten gleich 0 ist. Ist <strong>de</strong>r Wert gleich 0, so ist <strong>de</strong>r alte Wert unverän<strong>de</strong>rt<br />

zurückzuschreiben und die Berechnung ist been<strong>de</strong>t. Ist er ungleich 0, müssen die bei<strong>de</strong>n<br />

erhaltenen Werte addiert wer<strong>de</strong>n und das Minimum zwischen <strong>de</strong>m Ergebnis aus <strong>de</strong>r<br />

Addition und <strong>de</strong>m alten Wert ist nun in <strong>de</strong>n Zellspeicher zu schreiben.<br />

Der Floyd-Warshall-Algorithmus funktioniert auf <strong>de</strong>m GCA entsprechend <strong>de</strong>m Warshall-<br />

Algorithmus, es wird lediglich die Berechnungsfunktion geän<strong>de</strong>rt. Diese geht aber nicht<br />

in das Schema aus Kapitel 2.3.4 ein, weswegen sich die selben Abhängigkeiten wie beim<br />

Warshall-Algorithmus ergeben:<br />

d’ = f(d,d*) und p’ = g(t,d).<br />

35


3.1.3. Der Algorithmus von Hirschberg et al.<br />

Auf einem Multiprozessor-System kann man entwe<strong>de</strong>r <strong>de</strong>n modifizierten Warshall verwen<strong>de</strong>n,<br />

um zusammenhängen<strong>de</strong> Komponenten zu erkennen, o<strong>de</strong>r man nutzt <strong>de</strong>n Algorithmus<br />

von Hirschberg et al., welcher bereits so mo<strong>de</strong>lliert wur<strong>de</strong>, dass er die Möglichkeiten<br />

eines Multiprozessor-Systems ausnutzt.<br />

Der Algorithmus von Hirschberg et al. ist ursprünglich in [GR98] als ein Algorithmus auf<br />

<strong>de</strong>r P-RAM vorgestellt wor<strong>de</strong>n. Bei diesem Algorithmus wird versucht, unter Ausnutzung<br />

<strong>de</strong>r nebenläufigen 4 Fähigkeiten eines Mo<strong>de</strong>lls wie <strong>de</strong>r P-RAM, die zusammenhängen<strong>de</strong>n<br />

Komponenten eines Graphen zu bestimmen. Auch hier wird <strong>de</strong>r Algorithmus zunächst<br />

schematisch im Original vorgestellt und dann wird dargestellt, wie dieser Algorithmus<br />

auf <strong>de</strong>m GCA mo<strong>de</strong>lliert wer<strong>de</strong>n kann.<br />

3.1.3.1. Ablauf auf <strong>de</strong>r P-RAM<br />

Der Algorithmus von Hirschberg et al. durchläuft insgesamt O(log(n))-mal eine Schleife.<br />

Nach je<strong>de</strong>m Schleifendurchlauf hat er eine Menge von zusammenhängen<strong>de</strong>n Komponenten.<br />

Diese Komponenten wer<strong>de</strong>n in <strong>de</strong>m n-dimensionalen Vektor C gespeichert, wobei<br />

an <strong>de</strong>r Stelle i genau dann <strong>de</strong>r Wert j steht, wenn <strong>de</strong>r Knoten i zu <strong>de</strong>r Komponente<br />

j gehört. Innerhalb einer Komponente gilt immer, dass die kleinste Nummer aller zugehörigen<br />

Knoten die Nummer <strong>de</strong>r Komponente bestimmt. So wird die Ein<strong>de</strong>utigkeit<br />

<strong>de</strong>r Benennung gewährleistet. Der Algorithmus bestimmt diese Komponenten in zwei<br />

Phasen nun folgen<strong>de</strong>rmaßen:<br />

• Die erste Phase bestimmt eine Verbindung von je<strong>de</strong>r Komponente zu <strong>de</strong>r erreichbaren<br />

Komponente mit <strong>de</strong>r niedrigsten Nummer, die in <strong>de</strong>m n-dimensionalen Vektor<br />

T gespeichert wird.<br />

Zu diesem Zweck muss erst von je<strong>de</strong>m <strong>de</strong>r Komponente angehörigen Knoten die<br />

Verbindung zu <strong>de</strong>m kleinsten Knoten, <strong>de</strong>r nicht <strong>de</strong>r Komponente angehört, gesucht<br />

wer<strong>de</strong>n. Der Wert von <strong>de</strong>ssen Komponente muss anschließend gespeichert wer<strong>de</strong>n.<br />

Danach wird von all diesen Verbindungen diejenige ausgesucht, die zu <strong>de</strong>r niedrigsten<br />

Komponente ungleich <strong>de</strong>r eigenen führt. Diese Verbindung wird gleichzeitig<br />

<strong>de</strong>m Repräsentanten <strong>de</strong>r eigenen Komponente zugeordnet.<br />

Diese Phase setzt sich aus zwei Teilschritten zusammen.<br />

• Die zweite Phase sorgt dafür, dass alle Komponenten, die eine Verbindung zueinan<strong>de</strong>r<br />

haben, zusammengelegt wer<strong>de</strong>n und alle in ihnen enthaltenen Knoten auf<br />

<strong>de</strong>n Knoten mit <strong>de</strong>r niedrigsten Nummer verweisen.<br />

4 Man spricht davon, dass zwei Befehle parallel abarbeitbar sind, wenn man die Befehle zeitgleich<br />

ausführen kann. Nebenläufig be<strong>de</strong>utet hingegen, dass es egal ist, in welcher Reihenfolge die Befehle<br />

ausgeführt wer<strong>de</strong>n. Die Befehle können also parallel abgearbeitet wer<strong>de</strong>n o<strong>de</strong>r aber in je<strong>de</strong>r<br />

Kombination sequentiell hintereina<strong>de</strong>r ohne dass es zu Konflikten kommt.<br />

36


Um diese Aufgabe zu erfüllen, muss zuerst eine Kopie <strong>de</strong>s Vektors T angelegt<br />

wer<strong>de</strong>n. Diese Kopie wird in <strong>de</strong>m Vektor B abgespeichert.<br />

Danach wird O(logn)-mal <strong>de</strong>r Schritt <strong>de</strong>r Doubling-Technik 5 angewen<strong>de</strong>t, in <strong>de</strong>r<br />

Hoffnung, somit auf <strong>de</strong>n niedrigsten Knoten <strong>de</strong>r Gesamtkomponente zu verweisen.<br />

Innerhalb <strong>de</strong>r Komponente kann es dazu kommen, dass ein Zyklus <strong>de</strong>r Länge zwei<br />

entsteht, <strong>de</strong>r dafür sorgt, dass bei <strong>de</strong>r Doubling-Technik die Komponente wie<strong>de</strong>r<br />

in zwei zerfällt. Aus diesem Grund wird an die Doubling-Technik anschließend das<br />

Minimum <strong>de</strong>s Ergebnisses <strong>de</strong>r Doubling-Technik mit <strong>de</strong>m Nachfolger <strong>de</strong>s ursprünglichen<br />

Werts gebil<strong>de</strong>t. Dieser Wert wird dann wie<strong>de</strong>r an die entsprechen<strong>de</strong> Stelle<br />

<strong>de</strong>s C-Vektors geschrieben.<br />

Somit stehen dann die neu gefun<strong>de</strong>nen Komponenten wie<strong>de</strong>r im Vektor C und <strong>de</strong>r<br />

nächste Durchlauf <strong>de</strong>s Algoritmus kann beginnen. Die zweite Phase besteht aus<br />

drei Teilschritten.<br />

Nach<strong>de</strong>m die Funktionsweise im Groben beschrieben wur<strong>de</strong>, soll nun <strong>de</strong>r gesamte Algorithmus<br />

in Pseudoco<strong>de</strong> beschrieben wer<strong>de</strong>n. Dabei wird die gleiche Terminologie wie<br />

in [GR98] benutzt. Im Pseudoco<strong>de</strong> wird eine parallele Abarbei<strong>tu</strong>ng <strong>de</strong>r Aufgabe immer<br />

durch ein ”<br />

for all ... in parallel“gefor<strong>de</strong>rt. Ansonsten ist <strong>de</strong>r Pseudoco<strong>de</strong> ähnlich<br />

<strong>de</strong>r gewohnten Lesart von C, wobei min j be<strong>de</strong>utet, dass das Minimum bezüglich j gesucht<br />

wird. Der angegebene Pseudoco<strong>de</strong> muss log(n)-mal wie<strong>de</strong>rholt wer<strong>de</strong>n, damit das<br />

korrekte Ergebnis erreicht wird.<br />

Listing 3.3: Der Hirschberg-Algorithmus auf <strong>de</strong>r P-RAM<br />

1 for a l l vertices i in parallel do<br />

T( i )


⎛<br />

B =<br />

⎜<br />

⎝<br />

0 0 0 1 0 0 0 0<br />

0 0 1 1 0 0 1 0<br />

0 1 0 0 1 1 0 0<br />

1 1 0 0 0 0 0 1<br />

0 0 1 0 0 0 0 0<br />

0 0 1 0 0 0 0 0<br />

0 1 0 0 0 0 0 0<br />

0 0 0 1 0 0 0 0<br />

⎞<br />

⎟<br />

⎠<br />

Abbildung 3.6.: Beispielgraph für <strong>de</strong>n Algorithmus von Hirschberg<br />

Im ersten Teilschritt wird von je<strong>de</strong>m Knoten ausgehend die Kante zum Nachbarn mit<br />

<strong>de</strong>r kleinsten Nummer gesucht, <strong>de</strong>r noch nicht in <strong>de</strong>r gleichen Komponente enthalten ist.<br />

Dieser Nachbar wird in <strong>de</strong>n Vektor T gespeichert, welchen man wie<strong>de</strong>r als Grundlage<br />

für einen gerichteten Graphen verwen<strong>de</strong>n kann (<strong>de</strong>r Wert j an <strong>de</strong>r Stelle i impliziert eine<br />

gerichtete Verbindung von i nach j). Interpretiert man <strong>de</strong>n Vektor T in dieser Weise, so<br />

entsteht am En<strong>de</strong> <strong>de</strong>s ersten Schritts <strong>de</strong>r Graph aus Abbildung 3.7.<br />

Im ersten Durchlauf än<strong>de</strong>rt <strong>de</strong>r zweite Teilschritt nichts am Vektor T, da bislang 8<br />

Komponenten existieren, in <strong>de</strong>nen jeweils nur ein Knoten, <strong>de</strong>r Repräsentant, enthalten<br />

ist. Ein Minimum über alle Elemente <strong>de</strong>r Komponente wird also nichts än<strong>de</strong>rn und<br />

Abbildung 3.7 repräsentiert somit auch das Ergebnis <strong>de</strong>s zweiten Teilschritts <strong>de</strong>s ersten<br />

Durchlaufs.<br />

Im dritten Teilschritt wird nunmehr eine Kopie angelegt, welche für <strong>de</strong>n fünften und<br />

letzten Teilschritt benötigt wird.<br />

Im vierten Teilschritt wird nun log(n) mal die Doubling-Technik angewen<strong>de</strong>t. Dabei setzt<br />

je<strong>de</strong>r Knoten seinen Nachfolger auf <strong>de</strong>n Nachfolger seines ursprünglichen Nachfolgers.<br />

Diese Aktion wird im Algorithmus log(n)-wie<strong>de</strong>rholt, bis schließlich als Ergebnis <strong>de</strong>r<br />

Graph aus Abbildung 3.8 entsteht. Der Vektor T wird dafür wie<strong>de</strong>r wie vorne beschrieben<br />

interpretiert.<br />

Auffällig am Ergebnis nach <strong>de</strong>m vierten Schritt ist, dass nun vier und nicht mehr, wie<br />

nach <strong>de</strong>m zweiten Schritt, zwei Komponenten vorhan<strong>de</strong>n sind. Solches Auseinan<strong>de</strong>rreißen<br />

von Komponenten ist allerdings unerwünscht, weshalb <strong>de</strong>r fünfte Schritt wie<strong>de</strong>r<br />

38


Abbildung 3.7.: Ergebnis nach <strong>de</strong>m ersten und zweiten Teilschritt <strong>de</strong>s ersten Durchlaufs.<br />

Abbildung 3.8.: Ergebnis nach <strong>de</strong>m vierten Teilschritt <strong>de</strong>s ersten Durchlaufs.<br />

dafür sorgt, dass Komponenten, die im zweiten Schritt zusammenhängend waren, auch<br />

nach <strong>de</strong>m fünften Schritt wie<strong>de</strong>r zusammenhängend sind. Da das Auseinan<strong>de</strong>rgehen einer<br />

Komponente nur durch die Doubling-Technik auftreten konnte, muss dieser Schritt<br />

im Nachhinein noch einmal kritisch nachgeprüft wer<strong>de</strong>n. Dies kann man erreichen in<strong>de</strong>m<br />

man das Minimum bil<strong>de</strong>t, da immer <strong>de</strong>r Knoten mit <strong>de</strong>r kleinsten Nummer als<br />

Repräsentant <strong>de</strong>r Komponente <strong>de</strong>finiert ist.<br />

Das Minimum wird über zwei Werte gebil<strong>de</strong>t. Der erste Wert ist <strong>de</strong>r direkte Nachfolger<br />

<strong>de</strong>s Knotens i nach <strong>de</strong>m vierten Teilschritt. Der zweite Wert berechnet sich mit Hilfe <strong>de</strong>r<br />

Kopie aus Teilschritt drei. Zunächst wird in <strong>de</strong>r Kopie nachgesehen, welchen Nachfolger<br />

<strong>de</strong>r Knoten i nach <strong>de</strong>m zweiten Teilschritt hatte. Dann wird für diesen Nachfolger <strong>de</strong>r<br />

Nachfolger nach <strong>de</strong>m vierten Teilschritt gesucht. Dieser Nachfolger schließlich ist die<br />

zweite Eingabe für das Minimum. Das Ergebnis <strong>de</strong>s Minimums ist <strong>de</strong>r neue Nachfolger<br />

<strong>de</strong>s betrachteten Knotens. Nach<strong>de</strong>m dieses Verfahren für je<strong>de</strong>n Knoten angewen<strong>de</strong>t<br />

wur<strong>de</strong>, erhält man <strong>de</strong>n Graphen aus 3.9.<br />

Die zwei neu entstan<strong>de</strong>nen Komponenten versucht man im zweiten Durchlauf zusammenzufassen.<br />

Gäbe es in diesem Durchlauf noch mehr als zwei Komponenten, so wäre<br />

mit einer Verringerung <strong>de</strong>r Komponentenanzahl von min<strong>de</strong>stens <strong>de</strong>r Hälfte zu rechnen,<br />

außer die Komponenten hängen nicht zusammen 6 .<br />

6 Da nicht zusammenhängen<strong>de</strong> Komponenten nicht zusammengefasst wer<strong>de</strong>n können, kommt es bei<br />

einem nicht zusammenhängen<strong>de</strong>n Graphen dazu, dass sich die Anzahl <strong>de</strong>r zusammenhängen<strong>de</strong>n<br />

Komponenten in einem Durchlauf even<strong>tu</strong>ell nicht halbiert.<br />

39


Abbildung 3.9.: Ergebnis nach <strong>de</strong>m fünften Teilschritt <strong>de</strong>s ersten Durchlaufs.<br />

Abbildung 3.10.: Ergebnis nach <strong>de</strong>m ersten Teilschritt <strong>de</strong>s zweiten Durchlaufs.<br />

Im ersten Teilschritt <strong>de</strong>s zweiten Durchlaufs wird wie<strong>de</strong>r in je<strong>de</strong>m Knoten gesucht, ob<br />

eine Verbindung zu einem Knoten aus einer an<strong>de</strong>ren Komponente möglich ist und falls<br />

ja wird die günstigste genommen. In diesem Beispiel ist es nur <strong>de</strong>n Knoten 4 und 2<br />

möglich, eine Verbindung zu einer an<strong>de</strong>ren Komponente herzustellen. Das Ergebnis am<br />

En<strong>de</strong> dieses Teilschritts ist in Abbildung 3.10 zu sehen.<br />

Im zweiten Teilschritt wird nun für je<strong>de</strong>n Knoten i überprüft, ob es einen Knoten j gibt,<br />

welcher am Anfang <strong>de</strong>s Durchlaufs auf <strong>de</strong>n Knoten i gezeigt hat (C(j)=i) und nun nicht<br />

mehr auf i zeigt (T(j)!=i). Existiert min<strong>de</strong>stens ein solcher Knoten, so wird <strong>de</strong>r kleinste<br />

Knoten j gewählt und <strong>de</strong>r Nachfolger von i auf <strong>de</strong>n Nachfolger von j gesetzt (T(i)=T(j)).<br />

Solch eine Anweisung wird nur für die Knoten ausgeführt, die vorher Repräsentanten<br />

ihrer Gruppe waren, da nur solche noch am Anfang im Vektor C auftauchen. In diesem<br />

Beispiel trifft das auf <strong>de</strong>n Knoten 1 zu; dieser war vorher Repräsentant seiner Komponente<br />

und nun gibt es einen Knoten (Knoten 4), welcher nicht mehr auf <strong>de</strong>n Knoten 1<br />

zeigt. Also zeigt Knoten 1 nun auch nicht mehr auf sich selber, son<strong>de</strong>rn auf Knoten 2,<br />

<strong>de</strong>n Nachfolger von Knoten 4. Der einzige an<strong>de</strong>re Knoten, <strong>de</strong>r für eine Verän<strong>de</strong>rung in<br />

Frage käme, ist Knoten 2. Da dieser aber in seiner Komponente <strong>de</strong>r Einzige ist, welcher<br />

eine Verbindung in die an<strong>de</strong>re Komponente fin<strong>de</strong>t, muss <strong>de</strong>r Nachfolger nicht mehr umgesetzt<br />

wer<strong>de</strong>n. Ist ein Knoten nicht <strong>de</strong>r Repräsentant seiner Komponente, so wird er<br />

wie<strong>de</strong>r auf seinen Ausgangswert gesetzt (T(i)=C(i)), da die Verbindung zu <strong>de</strong>r an<strong>de</strong>ren<br />

Komponente nur einfach benötigt wird und diese vom Repräsentanten ausgeht. Das Ergebnis<br />

nach Teilschritt zwei <strong>de</strong>s zweiten Durchlaufs sieht dann wie in Abbildung 3.11<br />

40


Abbildung 3.11.: Ergebnis nach <strong>de</strong>m zweiten Teilschritt <strong>de</strong>s zweiten Durchlaufs. Um Verwirrung<br />

aufgrund von Kantenüberschneidungen zu vermei<strong>de</strong>n, wur<strong>de</strong>n<br />

die Positionen <strong>de</strong>r Knoten teilweise variiert.<br />

aus.<br />

Teilschritt drei legt wie<strong>de</strong>r eine Kopie <strong>de</strong>s Vektors T für <strong>de</strong>n letzten Schritt an. Im vierten<br />

Teilschritt wird nun wie<strong>de</strong>r die Doubling-Technik angewen<strong>de</strong>t, was zu Abbildung 3.12<br />

führt.<br />

Abbildung 3.12.: Ergebnis nach <strong>de</strong>m vierten Teilschritt <strong>de</strong>s zweiten Durchlaufs.<br />

Auch hier sind wie<strong>de</strong>r mehrere Komponenten entstan<strong>de</strong>n, obwohl eigentlich die Komponenten<br />

zusammengefasst wer<strong>de</strong>n sollten. Das liegt daran, dass im Graphen ein Zyklus<br />

enthalten war und somit je<strong>de</strong>r <strong>de</strong>r beteiligten Knoten zwangsläufig früher o<strong>de</strong>r später auf<br />

sich selber zeigt. Dieser Missstand wird im fünften Teilschritt bereinigt. Da <strong>de</strong>r Knoten<br />

1 die im gesamten Graphen kleinste Nummer hat, wird sich sein Wert bei <strong>de</strong>r Minimumbildung<br />

nicht än<strong>de</strong>rn, gleiches gilt für alle an<strong>de</strong>ren Knoten, welche auf Knoten 1 zeigen.<br />

Knoten 2 zeigt nach <strong>de</strong>m vierten Teilschritt auf sich selber, aber in <strong>de</strong>r Kopie (B) zeigt<br />

er auf Knoten 1, welcher nach <strong>de</strong>m vierten Teilschritt auch auf sich selber zeigt. Das<br />

Minimum von 2 und 1 ist 1, Knoten 2 zeigt also ab jetzt auf Knoten 1. Das gleiche<br />

Prinzip wird nun noch auf Knoten 4 und 8 angewen<strong>de</strong>t. Bei<strong>de</strong> zeigen in <strong>de</strong>r Kopie auf 1<br />

und wer<strong>de</strong>n somit im fünften Teilschritt wie<strong>de</strong>r auf <strong>de</strong>n Knoten 1 umgebogen.<br />

41


Da nun alle Knoten auf Knoten 1 zeigen, kann man erkennen, dass es eine große zusammenhängen<strong>de</strong><br />

Komponente im Graphen gibt, mit an<strong>de</strong>rn Worten, <strong>de</strong>r Graph ist<br />

zusammenhängend. Der Algorithmus wird die fünf Teilschritte noch einmal durchlaufen,<br />

da log 2 (8) = 3. Es wird sich jedoch nichts mehr än<strong>de</strong>rn, da keine Verbindung zu<br />

einer an<strong>de</strong>ren Komponente mehr gefun<strong>de</strong>n wer<strong>de</strong>n kann. Das Beispiel kann also hier als<br />

abgeschlossen betrachtet wer<strong>de</strong>n. Das En<strong>de</strong>rgebnis wird in Abbildung 3.13 graphisch<br />

dargestellt. Bei einem nicht zusammenhängen<strong>de</strong>n Graphen wür<strong>de</strong>n nach Terminierung<br />

<strong>de</strong>s Algorithmus im Vektor C min<strong>de</strong>stens zwei unterschiedliche Repräsentanten stehen.<br />

Man kann direkt aus <strong>de</strong>m Vektor auslesen, wie groß die Komponenten sind und welche<br />

Knoten zu ihnen gehören.<br />

Abbildung 3.13.: Ergebnis nach <strong>de</strong>m fünften Schritt <strong>de</strong>s zweiten Durchlaufs. Um Verwirrung<br />

aufgrund von Kantenüberschneidungen zu vermei<strong>de</strong>n, wur<strong>de</strong>n<br />

auch hier die Position <strong>de</strong>r Knoten teilweise variiert.<br />

3.1.3.2. Komplexitätsbetrach<strong>tu</strong>ng auf <strong>de</strong>r P-RAM<br />

Der Algorithmus benötigt, so wie er vorgestellt wur<strong>de</strong>, eine Laufzeit von<br />

O(log 2 (n)) bei <strong>de</strong>r Benutzung von O( n2 ) Prozessoren. In diesem Abschnitt soll ver<strong>de</strong>utlicht<br />

wer<strong>de</strong>n, dass diese Abschätzung gerechtfertigt<br />

log(n)<br />

ist.<br />

Da die fünf Teilschritte (im Folgen<strong>de</strong>n nur noch Schritte genannt) <strong>de</strong>s Algorithmus<br />

log(n)-mal wie<strong>de</strong>rholt wer<strong>de</strong>n, darf je<strong>de</strong>r Schritt nicht mehr als eine Laufzeit von<br />

O(log(n)) haben, damit die Gesamtlaufzeit von O(log 2 (n)) erfüllt ist. Die Aufgabe dieses<br />

Abschnitts ist es also, für je<strong>de</strong>n Schritt darzulegen, dass er wirklich in O(log(n)) Zeit<br />

ablaufen kann.<br />

Für <strong>de</strong>n ersten Schritt ist nicht offensichtlich, dass er in O(log(n)) ausführbar ist. Immerhin<br />

müssen potentiell n Werte bestimmt und daraus das Minimum gebil<strong>de</strong>t wer<strong>de</strong>n.<br />

Um diesen Schritt in <strong>de</strong>r gewünschten Zeit ausführen zu können, muss man geschickt<br />

zusätzliche Prozessoren einsetzen. In [GR98] wird <strong>de</strong>r erste Schritt in drei Teilschritte<br />

42


1(a) bis 1(c) aufgeteilt und für je<strong>de</strong>n <strong>de</strong>r Teilschritte die Komplexität bestimmt. Die<br />

Teilschritte gestalten sich wie folgt (∞ aus <strong>de</strong>m Original wur<strong>de</strong> hier durch 2n ersetzt):<br />

Listing 3.4: Weitere Unterteilung <strong>de</strong>s ersten Schritts <strong>de</strong>s Hirschberg-Algorithmus<br />

1 1(a) : for a l l i , j , 1


Abbildung 3.14.: Eine Möglichkeit, aus n Werten in log(n) Schritten das Minimum zu<br />

fin<strong>de</strong>n. Die Prozessoren vergleichen mit ihrem Nachbarn und speichern<br />

das Minimum ab. Danach wird <strong>de</strong>r Nachbar umgesetzt. Das Minimum<br />

steht nach <strong>de</strong>r Ausführung immer an erster Stelle.<br />

Wen<strong>de</strong>t man statt <strong>de</strong>r Doubling-Technik die modifizierte Balanced-Binary-Tree-Technik 7<br />

an, dann reichen<br />

n Prozessoren pro Spalte und dabei wird eine Laufzeit von O(log(n))<br />

log(n)<br />

garantiert. Insgesamt benötigt <strong>de</strong>r erste Schritt dann eine Laufzeit von O(log(n)) und<br />

O( n2 ) Prozessoren. Im Folgen<strong>de</strong>n soll zuerst die Balanced-Binary-Tree-Technik vorgestellt<br />

wer<strong>de</strong>n und dann wird dargestellt, wie man die Anzahl <strong>de</strong>r Prozessoren dahinge-<br />

log(n)<br />

n<br />

hend verän<strong>de</strong>rn kann, dass man mit O( ) Prozessoren die gleiche Laufzeitkomplexität<br />

log(n)<br />

erreicht.<br />

Bei <strong>de</strong>r Balanced-Binary-Tree-Technik [Abbildung 3.15] hält je<strong>de</strong> Zelle zwei Werte <strong>de</strong>r<br />

Matrixspalte aus 1(a) und bil<strong>de</strong>t hieraus das Minimum.<br />

Es berechnen also n−1 Prozessoren parallel das Minimum ihrer Werte. Je<strong>de</strong> Zelle hat in<br />

diesem Mo<strong>de</strong>ll eine Vaterzelle und je<strong>de</strong> Vaterzelle hat zwei Kindzellen, was dazu führt,<br />

dass die Balanced-Binary-Tree-Technik immer n = 2 m Werte erwartet (ist dies nicht<br />

<strong>de</strong>r Fall, müssen Dummy-Einträge ergänzt wer<strong>de</strong>n, bis die Bedingung erfüllt ist). Die<br />

Vaterzelle bestimmt nun das Minimum <strong>de</strong>r Kindzellen und liefert diesen Wert an seine<br />

Vaterzelle weiter. Insgesamt halbiert sich die Anzahl <strong>de</strong>r Zellen auf je<strong>de</strong>r S<strong>tu</strong>fe, bis in<br />

<strong>de</strong>r Spitze nur noch eine Zelle existiert, die Wurzel. Nach<strong>de</strong>m diese Wurzelzelle ihrerseits<br />

das Minimum gebil<strong>de</strong>t hat, kann <strong>de</strong>r korrekte Wert aus <strong>de</strong>r Wurzel ausgelesen wer<strong>de</strong>n.<br />

Die Laufzeitkomplexität entspricht nun <strong>de</strong>r maximalen Weglänge von <strong>de</strong>r Wurzel zu<br />

einem Blatt, was in einem vollständigen Binärbaum log(n) ist. Wie bei <strong>de</strong>r Doubling-<br />

Technik wer<strong>de</strong>n hier jedoch O(n) Prozessoren o<strong>de</strong>r Zellen pro Spalte benötigt, was zu<br />

einer Gesamtzahl von O(n 2 ) benötigten Zellen führt.<br />

7 Ein Beispiel für die Balanced-Binary-Tree-Technik wird gegeben. Eine genaue Beschreibung fin<strong>de</strong>t<br />

sich im Anhang.<br />

44


Abbildung 3.15.: Die Balanced-Binary-Tree Technik mit <strong>de</strong>r maximalen Anzahl <strong>de</strong>r Prozessoren.<br />

Bei <strong>de</strong>r weniger prozessoraufwendigen Variante wer<strong>de</strong>n die<br />

Aufgaben <strong>de</strong>r Prozessoren <strong>de</strong>r oberen Schichten noch von <strong>de</strong>n Prozessoren<br />

<strong>de</strong>r untersten Schicht mit erledigt.<br />

Die Anzahl <strong>de</strong>r Zellen lässt sich leicht minimieren, in<strong>de</strong>m man Zellen erlaubt, ihre eigene<br />

Vaterzelle zu sein um somit mehrfach benutzt zu wer<strong>de</strong>n. Durch diese Maßnahme lässt<br />

sich die Anzahl <strong>de</strong>r benötigten Zellen zwar auf n reduzieren, jedoch entspricht dies in <strong>de</strong>r<br />

2<br />

O-Notation immer noch einer Anzahl von O(n) Prozessoren pro Spalte. Um die Anzahl<br />

n<br />

<strong>de</strong>r Prozessoren also auf O( ) zu reduzieren, muss man also die Anzahl <strong>de</strong>r in <strong>de</strong>r<br />

log(n)<br />

untersten Reihe benötigten Prozessoren minimieren.<br />

Um die Anzahl <strong>de</strong>r Prozessoren nun weiter zu minimieren, teilt man die n Werte, aus<br />

<strong>de</strong>nen das Minimum gesucht wer<strong>de</strong>n soll, in p Gruppen. Je<strong>de</strong> dieser p Gruppen hat ⌊ n⌋<br />

p<br />

Elemente, außer einer, welche n−(p−1)⌊ n ⌋ Elemente hat. Es wer<strong>de</strong>n nun p Prozessoren<br />

p<br />

gebraucht, welche jeweils in einer Gruppe sequentiell das Minimum suchen. Da je<strong>de</strong><br />

Gruppe maximal ⌊ n⌋ Elemente hat, wer<strong>de</strong>n maximal p ⌊n ⌋ -1 Schritte gebraucht, um<br />

p<br />

n<br />

das Minimum <strong>de</strong>r Gruppe zu bestimmen. Setzt man p auf , so wer<strong>de</strong>n O( n<br />

)<br />

log(n) log(n)<br />

Prozessoren für die Gesamtlösung benötigt und die Laufzeitkomplexität ist weiterhin<br />

O(log(n)).<br />

Um die Laufzeitkomplexität zu ver<strong>de</strong>utlichen, wer<strong>de</strong>n die Arbeitsschritte noch einmal<br />

aufgeführt: Es wer<strong>de</strong>n insgesamt log(n)−1 Minimumbestimmungen aus zwei Elementen<br />

benötigt (<strong>de</strong>r obere Teil <strong>de</strong>s Baums), die in O(1) möglich sind und eine Minimumbestimmung<br />

aus einer Gruppe mit ⌈ n ⌉ Elementen. Dieses Minimum <strong>de</strong>r Gruppe benötigt<br />

p<br />

⌈ n n<br />

⌉ − 1 Schritte, wobei p als gewählt wur<strong>de</strong>. Die Gruppenminimumsbestimmung<br />

p log(n)<br />

benötigt <strong>de</strong>mnach log(n) − 1 Schritte, was insgesamt 2log(n) − 2 Schritte ergibt, also<br />

O(log(n)). Es wur<strong>de</strong> also eine Metho<strong>de</strong> gefun<strong>de</strong>n, sowohl die Zeitschranke als auch die<br />

gewünschte Schranke <strong>de</strong>r Prozessoren einzuhalten.<br />

Der zweite Schritt kann genauso wie <strong>de</strong>r erste Schritt in drei Teilschritte zerlegt wer<strong>de</strong>n,<br />

die sich dann ähnlich wie die drei Teilschritte <strong>de</strong>s ersten Schritts in O(log(n)) Laufzeit<br />

und mit O( n2 ) Prozessoren realisiern lassen.<br />

log(n)<br />

Schritt drei ist offensichtlich in O(1) realisierbar. Das Kopieren von n Werten kann mit n<br />

45


Prozessoren in zwei Schritten erledigt wer<strong>de</strong>n (ein Schritt <strong>de</strong>n Wert lesen und ein Schritt<br />

<strong>de</strong>n Wert in die neue Variable schreiben).<br />

Die Zuweisung <strong>de</strong>s vierten Schritts ist mit n Prozessoren in O(1) möglich, allerdings<br />

muss die Anweisung log(n) mal wie<strong>de</strong>rholt wer<strong>de</strong>n, was sich auch mit mehr Prozessoren<br />

nicht vermei<strong>de</strong>n lässt. Der Zeitaufwand <strong>de</strong>s vierten Schritts ist also O(log(n)) und kann<br />

nicht optimiert wer<strong>de</strong>n. Das be<strong>de</strong>utet, dass auch die Laufzeit <strong>de</strong>s Gesamtalgorithmus<br />

nicht mehr verbessert wer<strong>de</strong>n kann.<br />

Der fünfte Schritt ist wie<strong>de</strong>r mit n Prozessoren in O(1) realisierbar.<br />

Da alle Schritte in maximal O(log(n)) ausführbar sind und <strong>de</strong>r vierte Schritt minimal<br />

O(log(n)) Schritte benötigt, ist anschaulich dargelegt, weshalb <strong>de</strong>r Algorithmus eine<br />

Laufzeitkomplexität von O(log(n) 2 ) hat. Die Anzahl <strong>de</strong>r Prozessoren lässt sich noch auf<br />

O( n2<br />

log 2 n)<br />

) reduzieren, wie in [CLC82] dargestellt ist.<br />

3.1.3.3. Mo<strong>de</strong>llierung auf <strong>de</strong>m GCA<br />

Da <strong>de</strong>r Algorithmus von Hirschberg schon so konstruiert ist, dass er die parallelen<br />

Möglichkeiten <strong>de</strong>r P-RAM ausnutzt, ist es nicht mehr nötig, zu überprüfen, an welchen<br />

Stellen er sich parallelisieren lässt. Die eigentliche Aufgabe bei diesem Algorithmus<br />

liegt nunmehr darin, zu überprüfen, ob alle parallelen Möglichkeiten <strong>de</strong>r P-RAM, die hier<br />

genutzt wer<strong>de</strong>n, sich auch auf <strong>de</strong>m GCA nutzen lassen. Allerdings wird hier zunächst<br />

einmal ein Ansatz <strong>de</strong>r Mo<strong>de</strong>llierung verfolgt, <strong>de</strong>r verständlich ist, auch wenn dadurch die<br />

Laufzeitkomplexität schlechter wird. Nach<strong>de</strong>m die Mo<strong>de</strong>llierung veranschaulicht wur<strong>de</strong>,<br />

wird darauf eingegangen, wie die Laufzeit optimiert wer<strong>de</strong>n kann.<br />

Auf die erste Abweichung trifft man bei <strong>de</strong>n Vektoren T,B und C. Diese wer<strong>de</strong>n im Algorithmus<br />

von Hirschberg als globale Variablen genutzt, auf die alle Prozessoren sowohl<br />

lesend als auch schreibend zugreifen dürfen. Da <strong>de</strong>r GCA über keinen globalen Speicher<br />

verfügt und auch auf die Zellinhalte frem<strong>de</strong>r Zellen nicht schreibend zugegriffen wer<strong>de</strong>n<br />

darf, ist das ein Problem.<br />

Diese Problematik lässt sich mit <strong>de</strong>m GCA jedoch elegant umgehen, in<strong>de</strong>m man die<br />

Vektoren auf die Prozessoren aufteilt. Mit diesem Lösungsansatz hält dann je<strong>de</strong> Zelle i<br />

die Werte T[i],B[i] und C[i]. Da je<strong>de</strong> Zelle i nur auf <strong>de</strong>n i-ten Wert schreibend zugreift,<br />

ist damit bereits das Problem <strong>de</strong>s Schreibens auf Zellinhalte frem<strong>de</strong>r Zellen gelöst.<br />

Ein Nachteil dieses Lösungansatzes ist allerdings, dass <strong>de</strong>r Kommunikationsaufwand<br />

recht hoch wird. Allein im vierten Schritt ist es sehr wahrscheinlich, dass auf <strong>de</strong>n Zellinhalt<br />

an<strong>de</strong>rer Zellen zugegriffen wer<strong>de</strong>n muss. Der Kommunikationsaufwand ist dabei<br />

weniger das Problem, als vielmehr die Tatsache, dass viele Verbindungen am Anfang<br />

hergestellt wer<strong>de</strong>n müssen, die im weiteren Verlauf even<strong>tu</strong>ell ungenutzt bleiben.<br />

Um auch diese Problematik zu umgehen, wird eine Zelle als Speicherzelle ausgezeichnet,<br />

auf die alle Zellen lesend zugreifen. Damit diese Zelle aber immer die korrekten<br />

Variableninhalte bereit hält, muss sie am En<strong>de</strong> je<strong>de</strong>s Schritts bei je<strong>de</strong>r Zelle <strong>de</strong>n ak<strong>tu</strong>ellen<br />

Stand <strong>de</strong>r entsprechen<strong>de</strong>n Variable abfragen. Das heißt, die Speicherzelle fragt nach<br />

46


Abbildung 3.16.: Schematischer Aufbau <strong>de</strong>s GCA, welcher <strong>de</strong>n Hirschberg-Algorithmus<br />

bearbeitet. Es existieren sowohl von <strong>de</strong>r Speicherzelle“ zu je<strong>de</strong>r Rechenzelle“<br />

Verbindungen als auch von je<strong>de</strong>r Rechenzelle“ zu <strong>de</strong>r<br />

”<br />

” ”<br />

” Speicherzelle“. Je<strong>de</strong> Rechenzelle“ beinhaltet neben <strong>de</strong>n Variablen T,<br />

”<br />

B und C auch noch eine Zeile <strong>de</strong>r Matrix.<br />

Schritt eins bei allen Zellen an, welchen Wert sie in ihrer Variable T halten (da nur T<br />

geän<strong>de</strong>rt wur<strong>de</strong>, ist es nicht nötig, die an<strong>de</strong>ren Variablen abzufragen). Die Antwort <strong>de</strong>r<br />

Zelle i wird dann an die i-te Stelle <strong>de</strong>s Vektors T <strong>de</strong>r Speicherzelle geschrieben. In je<strong>de</strong>m<br />

Schritt <strong>de</strong>s Algorithmus sind also k+1 Takte nötig; k Takte um die Aufgabe <strong>de</strong>s Schritts<br />

zu lösen und ein Takt, um die Variablen in die Speicherzelle zu übertragen. Zusätzlich<br />

muss je<strong>de</strong> Zelle, außer <strong>de</strong>r Speicherzelle, noch eine Zeile <strong>de</strong>r Matrix halten, so dass Zelle<br />

i die i-te Zeile <strong>de</strong>r Matrix bearbeitet. Der Aufbau wird in Abbildung 3.16 noch einmal<br />

veranschaulicht.<br />

Nach<strong>de</strong>m <strong>de</strong>r Aufbau <strong>de</strong>r Zellen festgelegt wur<strong>de</strong>, muss überlegt wer<strong>de</strong>n, wie die einzelnen<br />

Schritte <strong>de</strong>s Algorithmus damit abgearbeitet wer<strong>de</strong>n können. Hier wird o. B. d.<br />

A. davon ausgegangen, dass es kein Problem ist, <strong>de</strong>n Ablauf log(n)-mal zu wie<strong>de</strong>rholen,<br />

d.h. wenn es gelingt, die einzelnen Schritte auf <strong>de</strong>m GCA lauffähig zu mo<strong>de</strong>llieren, wird<br />

<strong>de</strong>r gesamte Algorithmus auf <strong>de</strong>m GCA lauffähig sein.<br />

• Erster Schritt: Im ersten Schritt muss für je<strong>de</strong> Zelle jeweils <strong>de</strong>r Nachbar mit<br />

<strong>de</strong>m kleinsten In<strong>de</strong>x gesucht wer<strong>de</strong>n. Dies geschieht einfach, in<strong>de</strong>m je<strong>de</strong> Zelle die<br />

Matrixzeile, die sie beinhaltet, von vorne durchläuft und bei <strong>de</strong>r ersten gefun<strong>de</strong>nen<br />

Eins stoppt. Nun muss noch gesucht wer<strong>de</strong>n, ob dieser Nachbar j gewählt wer<strong>de</strong>n<br />

darf. Dazu wird eine Anfrage an die Speicherzelle gestellt, wobei <strong>de</strong>r Eintrag C(j)<br />

angefragt wird. Daraufhin testet die Zelle, ob <strong>de</strong>r zurückgelieferte Wert gleich <strong>de</strong>m<br />

Wert in <strong>de</strong>r eigenen C Variable ist. Ist dies <strong>de</strong>r Fall, so muss die Zeile so lange<br />

weiter durchlaufen wer<strong>de</strong>n, bis ein j gefun<strong>de</strong>n wird, das bei<strong>de</strong> Bedingungen erfüllt.<br />

Kommt die Zelle an das En<strong>de</strong> <strong>de</strong>r Zeile ohne ein passen<strong>de</strong>s j gefun<strong>de</strong>n zu haben,<br />

wird <strong>de</strong>r Wert <strong>de</strong>r C Variable in T gespeichert.<br />

47


Da alle Zellen parallel arbeiten, ist <strong>de</strong>r erste Schritt hier eigentlich been<strong>de</strong>t, allerdings<br />

muss in dieser Mo<strong>de</strong>llierung noch dafür gesorgt wer<strong>de</strong>n, dass die Speicherzelle<br />

wie<strong>de</strong>r ak<strong>tu</strong>alisiert wird. Um dieses Ziel zu erreichen fragt die Speicherzelle nun<br />

parallel bei allen Zellen an und speichert <strong>de</strong>ren Wert T in ihrem Vektor T ab.<br />

• Zweiter Schritt: Im zweiten Schritt sind immer die Paare T(j) und C(j) interessant.<br />

Natürlich kann man je<strong>de</strong> Zelle bei <strong>de</strong>r Speicherzelle nachfragen lassen,<br />

allerdings wollen ja alle Zellen alle T(j) und C(j) wissen, weshalb es geschickter ist,<br />

die Speicherzelle die Koordination übernehmen zu lassen. Innerhalb von n Takten<br />

stellt sie jeweils das Paar (T(j), C(j)) bereit und je<strong>de</strong> Zelle, die noch auf <strong>de</strong>r Suche<br />

nach ihrem Wert ist, kann sich dieses Paar abholen. Die Zellen selbst übernehmen<br />

dann intern die Überprüfung, ob das Tupel die Bedingung erfüllt o<strong>de</strong>r nicht.<br />

Da nur T(j) abgespeichert wer<strong>de</strong>n muss, kann dieses Verfahren noch dahingehend<br />

optimiert wer<strong>de</strong>n, dass die Speicherzelle nur solche Paare bereitstellt, die ungleich<br />

sind. Allerdings wird dann ein Abbruch<strong>tu</strong>pel benötigt, damit die Zellen wissen,<br />

dass sie ihren Wert C in <strong>de</strong>r Variablen T speichern sollen.<br />

Anschließend muss wie<strong>de</strong>r die Speicherzelle von allen Zellen die T-Werte abfragen<br />

und an <strong>de</strong>r korrekten Stelle ihres Vektors T speichern.<br />

• Dritter Schritt: Da je<strong>de</strong> Zelle i ihren ak<strong>tu</strong>ellen Wert T(i) kennt, kann das Kopieren<br />

<strong>de</strong>s Vektors T in <strong>de</strong>r Speicherzelle und <strong>de</strong>r Werte T(i) in <strong>de</strong>n Zellen gleichzeitig<br />

geschehen.<br />

• Vierter Schritt: Im vierten Schritt wird log(n)-mal <strong>de</strong>r folgen<strong>de</strong> Ablauf durchgeführt:<br />

Je<strong>de</strong> Zelle fragt bei <strong>de</strong>r Speicherzelle <strong>de</strong>n Wert an, <strong>de</strong>r im Vektor T an <strong>de</strong>r Stelle<br />

T(i) steht (<strong>de</strong>n Wert T(i) kennt die Zelle ja bereits, da er in ihrer eigenen Variable<br />

T steht). Die Antwort <strong>de</strong>r Speicherzelle wird in <strong>de</strong>r Variablen T <strong>de</strong>r anfragen<strong>de</strong>n<br />

Zelle gespeichert.<br />

Nachfolgend muss wie<strong>de</strong>r die Speicherzelle ihren Vektor T ak<strong>tu</strong>alisieren und erst<br />

dann darf die Schleife erneut durchlaufen wer<strong>de</strong>n.<br />

• Fünfter Schritt: In diesem Schritt muss je<strong>de</strong> Zelle genau einmal bei <strong>de</strong>r Speicherzelle<br />

anfragen. Gefragt wird in diesem Fall nach <strong>de</strong>m Wert <strong>de</strong>s Vektors B an<br />

<strong>de</strong>r Stelle T(i). Daraufhin bil<strong>de</strong>t je<strong>de</strong> Zelle für sich das Minimum und speichert<br />

<strong>de</strong>n Wert in C. Nach<strong>de</strong>m je<strong>de</strong> Zelle mit ihrer Berechnung fertig ist, fragt die Speicherzelle<br />

wie<strong>de</strong>r alle Werte C <strong>de</strong>r Zellen ab und ak<strong>tu</strong>alisiert so ihren Vektor C.<br />

Die Simulation auf <strong>de</strong>m GCA ist also möglich. Aufgrund <strong>de</strong>r Speicherzelle hat man zwar<br />

mehr Ak<strong>tu</strong>alisierungsaufwand, allerdings müssen die Zellen nicht mehr untereinan<strong>de</strong>r<br />

kommunizieren. Dadurch können viele Verbindungen eingespart wer<strong>de</strong>n. Diese Mo<strong>de</strong>llierung<br />

ist nicht laufzeitoptimal. Im nächsten Abschnitt wer<strong>de</strong>n Möglichkeiten untersucht,<br />

die Speicherzelle effizient zu mo<strong>de</strong>llieren und die Laufzeitkomplexität zu verbessern.<br />

48


Abbildung 3.17.: Erster Ansatz zur Realisierung <strong>de</strong>r Speicherzelle. Je<strong>de</strong> Zelle hat n Verbindungen<br />

zur Speicherzelle und die Speicherzelle hat eine Verbindung<br />

zu je<strong>de</strong>r Zelle.<br />

3.1.3.4. Komplexitätsbetrach<strong>tu</strong>ng und Verbesserungen auf <strong>de</strong>m GCA<br />

Gegenüber <strong>de</strong>r Mo<strong>de</strong>llierung auf <strong>de</strong>r P-RAM sind bei <strong>de</strong>r vorgestellten Mo<strong>de</strong>llierungsvariante<br />

erhebliche Geschwindigkeitseinbußen hinzunehmen. Während <strong>de</strong>r Originalalgorithmus<br />

mit O(log(n) 2 ) auskommt, benötigt schon <strong>de</strong>r erste Schritt in <strong>de</strong>r eben erläuterten<br />

Realisierung im worst-case O(n) Schritte. Eine Laufzeitkomplexität besser als<br />

O(nlog(n)) ist hiermit also nicht zu realisieren. Dennoch wird zunächst auf die Realisierungsmöglichkeiten<br />

<strong>de</strong>r Speicherzelle eingegangen, bevor erläutert wird, wie die Laufzeitkomplexität<br />

verringert wer<strong>de</strong>n kann.<br />

3.1.3.4.1. Speicherzellenmo<strong>de</strong>llierung Die Grün<strong>de</strong>, die für die Einführung einer Speicherzelle<br />

in <strong>de</strong>r Mo<strong>de</strong>llierung sprachen, waren einerseits die bessere Verständlichkeit und<br />

an<strong>de</strong>rerseits die Reduktion <strong>de</strong>r benötigten Lei<strong>tu</strong>ngen und ein geringerer Kommunikationsaufwand.<br />

Ohne Speicherzelle muss je<strong>de</strong> Zelle auf je<strong>de</strong> an<strong>de</strong>re Zelle lesend zugreifen<br />

können, d.h. bei n Zellen wer<strong>de</strong>n O(n 2 ) Lei<strong>tu</strong>ngen benötigt, um eine korrekte Kommunikation<br />

zu ermöglichen.<br />

Ein erster Ansatz ist es, <strong>de</strong>r Speicherzelle n Ports zur Verfügung zu stellen, an <strong>de</strong>nen sie<br />

dann immer die passen<strong>de</strong> Variable anlegt [Abbildung 3.17]. Im ersten Schritt wür<strong>de</strong> beispielsweise<br />

an <strong>de</strong>n Port i <strong>de</strong>r Wert C(i) angelegt. Damit die Zellen sich <strong>de</strong>n gewünschten<br />

Wert abholen können, muss je<strong>de</strong> Zelle über eine Verbindung zu je<strong>de</strong>m einzelnen Port <strong>de</strong>r<br />

Speicherzelle verfügen. Da je<strong>de</strong> Zelle einen Teil <strong>de</strong>s ak<strong>tu</strong>ellen Werts <strong>de</strong>r gera<strong>de</strong> verän<strong>de</strong>rten<br />

Variable hält, muss die Speicherzelle auch eine Verbindung zu je<strong>de</strong>r Zelle haben.<br />

Insgesamt benötigt man also n 2 + n Verbindungen (n Verbindungen pro Zelle zuzüglich<br />

49


<strong>de</strong>n n Verbindungen <strong>de</strong>r Speicherzelle). Ein Vorteil ist, dass die Kommunikation in zwei<br />

Takten (erster Takt: Speicherzelle stellt <strong>de</strong>n Wert bereit, zweiter Takt: Zelle liest <strong>de</strong>n<br />

Wert) abläuft und gleich alle interessanten Variableninhalte abgefragt wer<strong>de</strong>n können.<br />

Diesen ersten Ansatz kann man noch überschaubarer gestalten, in<strong>de</strong>m man einen Bus<br />

einführt, welcher die Variablen bereitstellt [Abbildung 3.18].<br />

Abbildung 3.18.: Modifizierter Ansatz <strong>de</strong>r Speicherzellenrealisierung. Je<strong>de</strong> Zelle sowie<br />

die Speicherzelle haben Zugriff auf je<strong>de</strong>n Wert <strong>de</strong>s Busses.<br />

Wenn die Zellen einen Wert von <strong>de</strong>r Speicherzelle erfragen wollen, schreibt die Speicherzelle<br />

<strong>de</strong>n gesamten Vektor <strong>de</strong>r Variablen auf <strong>de</strong>n Bus. Die Zellen können dann vom Bus<br />

die Werte lesen, welche sie interessieren. In <strong>de</strong>r Ak<strong>tu</strong>alisierungsphase <strong>de</strong>r Speicherzelle<br />

schreiben dann die Zellen ihren Variablenwert, welcher ihrer Zell-ID entspricht, auf die<br />

Lei<strong>tu</strong>ng <strong>de</strong>s Busses. Da je<strong>de</strong> Zelle eine einzigartige Zell-ID hat, kommt es zu keinerlei<br />

Schreibkollisionen.<br />

Diese Realisierung erscheint zwar struk<strong>tu</strong>rierter, allerdings wer<strong>de</strong>n noch mehr Verbindungen<br />

benötigt: zuzüglich zu <strong>de</strong>n n 2 + n Verbindung <strong>de</strong>r ersten Realisierung sind auch<br />

noch die n Verbindungen <strong>de</strong>s Busses nötig. Außer<strong>de</strong>m gilt es zu beachten, dass in <strong>de</strong>r<br />

ersten Realsierung nur unidirektionale Verbindungen gebraucht wur<strong>de</strong>n, in dieser Modifikation<br />

allerdings die n Verbindungen <strong>de</strong>r Speicherzelle sowie min<strong>de</strong>stens eine Verbindung<br />

pro Zelle bidirektional sind. Der Zugriff ist weiterhin in zwei Takten möglich und somit<br />

ist diese Variante zumin<strong>de</strong>st dahingehend interessant, dass sie <strong>de</strong>n Denkansatz für <strong>de</strong>n<br />

nächsten Ansatz liefert.<br />

In <strong>de</strong>r zweiten Modifikation <strong>de</strong>s ersten Ansatzes wird auf die Speicherzelle gänzlich verzichtet<br />

[Abbildung 3.19]. In <strong>de</strong>r letzten Realisierungsvariante wur<strong>de</strong> ersichtlich, dass <strong>de</strong>r<br />

50


Abbildung 3.19.: Realisierung durch einen Datenbus ohne Speicherzell. Je<strong>de</strong> Zelle kann<br />

auf eine Stelle <strong>de</strong>s Busses schreibend und auf alle lesend zugreifen.<br />

Bus die Kommunikation min<strong>de</strong>stens genauso gut koordinieren kann wie die Speicherzelle.<br />

Der Vorteil <strong>de</strong>r Speicherzelle, dass sie Daten zwischenspeichern kann, wird hier<br />

nicht effektiv genutzt und <strong>de</strong>mentsprechend kann auf die Speicherzelle auch verzichtet<br />

wer<strong>de</strong>n. Wenn Variablen abgefragt wer<strong>de</strong>n sollen, schreibt je<strong>de</strong> Zelle i die entsprechen<strong>de</strong><br />

Variable auf die i-te Stelle <strong>de</strong>s Busses. Da dies alle Zellen parallel machen, kann sofort im<br />

nächsten Takt darauf gelesen wer<strong>de</strong>n. Für einen Variablenaustausch sind also weiterhin<br />

nur zwei Takte nötig. Allerdings kann eine Zelle (die Speicherzelle) eingespart wer<strong>de</strong>n<br />

und die Anzahl <strong>de</strong>r benötigten Verbindungen sinkt wie<strong>de</strong>r auf n 2 + 2n unidirektionale<br />

Verbindungen. Erlaubt man bidirektionale Verbindungen, so wer<strong>de</strong>n nur noch n 2 + n<br />

Verbindungen benötigt, wovon n Verbindungen bidirektional sind.<br />

Ein völlig an<strong>de</strong>rer Ansatz wird in <strong>de</strong>r in Abbildung 3.20 dargestellten zweiten Realisierung<br />

verfolgt. Hier wur<strong>de</strong> die Anzahl <strong>de</strong>r Verbindungen auf 2n reduziert. Die Speicherzelle<br />

stellt je<strong>de</strong>r Zelle einen Port zur Verfügung, aus <strong>de</strong>m sie die für sie interessanten Werte<br />

auslesen kann. Zusätzlich stellt je<strong>de</strong> Zelle <strong>de</strong>r Speicherzelle einen Port zur Verfügung, an<br />

<strong>de</strong>m die Speicherzelle ihrerseits Werte auslesen kann.<br />

Der Verbindungsaufwand ist <strong>de</strong>utlich geringer als bei <strong>de</strong>n ersten Realisierungen, allerdings<br />

ist mehr Logik und Kommunikation notwendig, damit je<strong>de</strong> Zelle auch wirklich <strong>de</strong>n<br />

Wert erhält, <strong>de</strong>n sie benötigt. Voraussetzung für diese Realisierung ist, dass alle Zellen<br />

zeitgleich funktionieren, also immer <strong>de</strong>n gleichen Schritt <strong>de</strong>s Algorithmus bearbeiten und<br />

die Speicherzelle zu<strong>de</strong>m weiß, welcher Schritt gera<strong>de</strong> ak<strong>tu</strong>ell ist.<br />

Je<strong>de</strong> Zelle, die eine Anfrage an die Speicherzelle stellen möchte, schreibt <strong>de</strong>n benötigten<br />

In<strong>de</strong>x in ihren Port, ansonsten wird ein als ”<br />

none“ <strong>de</strong>finierter Wert in die Variable <strong>de</strong>s<br />

Ports geschrieben. Im nächsten Schritt liest die Speicherzelle parallel die Portvariablen<br />

aller Zellen aus und bearbeitet die Werte in einem Takt. Wie<strong>de</strong>r einen Schritt später<br />

schreibt sie die angefragten Werte an die entsprechen<strong>de</strong>n Ports, wo sich die Zellen im<br />

nächsten Takt die Werte abholen können. Der Ablauf benötigt also fünf Takte.<br />

Einer <strong>de</strong>r Vorteile dieser Realisierung sind allerdings die ”<br />

none“ Werte, die möglich sind,<br />

was im Weiteren noch genauer erläutert wird. In vielen Schritten ist nicht klar, wie<br />

lange ein Prozessor wirklich braucht, bis er ein Ergebnis gefun<strong>de</strong>n hat. So muss z. B. im<br />

ersten Schritt einerseits die Zeile durchlaufen und dann noch <strong>de</strong>r entsprechen<strong>de</strong> Wert C<br />

51


Abbildung 3.20.: Je<strong>de</strong> Zelle hat einen Port in <strong>de</strong>r Speicherzelle, auf <strong>de</strong>n sie lesend zugreift<br />

und zusätzlich einen eigenen Port, auf <strong>de</strong>n die Speicherzelle lesend<br />

zugreifen kann.<br />

abgefragt wer<strong>de</strong>n. Wenn nicht im ersten Anlauf ein passen<strong>de</strong>r Wert gefun<strong>de</strong>n wird, muss<br />

<strong>de</strong>r Ablauf wie<strong>de</strong>rholt wer<strong>de</strong>n. Wur<strong>de</strong> <strong>de</strong>r Wert gefun<strong>de</strong>n, wird die Zelle <strong>de</strong>n none“ Wert ”<br />

in die Portvariable schreiben und so anzeigen, dass sie fertig ist. Haben alle Zellen einen<br />

none“ Wert in <strong>de</strong>r Portvariable, so kann die Speicherzelle schließen, dass <strong>de</strong>r Schritt<br />

”<br />

erfolgreich abgearbeitet wur<strong>de</strong> und dies <strong>de</strong>n Zellen mitteilen, in<strong>de</strong>m sie einen <strong>de</strong>finierten<br />

Wert an ihre Portvariablen anlegt. Dies setzt natürlich voraus, dass die Zellen auch dann<br />

lesend auf die Portvariable zugreifen, wenn sie selber keinen Wert angefor<strong>de</strong>rt haben.<br />

Die Laufzeitkomplexität wird durch diese Modifikation zwar nicht verringert, aber <strong>de</strong>r<br />

Algorithmus wird <strong>de</strong>nnoch beschleunigt.<br />

Wählt man <strong>de</strong>n zweiten Mo<strong>de</strong>llierungsansatz, so benötigt man O(n) Prozessoren mit<br />

O(n) Verbindungen und einer Laufzeit von O(nlog(n)), wobei n die Anzahl <strong>de</strong>r Knoten<br />

ist.<br />

Mit diesem zweiten Ansatz kann man nun wie<strong>de</strong>r versuchen, <strong>de</strong>n Algorithmus in das<br />

in Kapitel 2.3.4 vorgestellte Schema einzuordnen. Auch hier ist wie<strong>de</strong>r eine getrennte<br />

Betrach<strong>tu</strong>ng <strong>de</strong>r Abhängigkeiten <strong>de</strong>r Daten und <strong>de</strong>r Verbindungen <strong>de</strong>r nächsten Generation<br />

notwendig. Da <strong>de</strong>r Algorithmus von Hirschberg allerdings aus fünf Schritten<br />

besteht, wird nachfolgend die Betrach<strong>tu</strong>ng nach diesen Schritten geglie<strong>de</strong>rt:<br />

• Im ersten Schritt wird abhängig von <strong>de</strong>n eigenen Daten <strong>de</strong>r Zelle eine Anfrage<br />

an die Speicherzelle gestellt. Abhängig von <strong>de</strong>ren Antwort än<strong>de</strong>rn sich die Daten<br />

<strong>de</strong>r Zelle. Daraus ergibt sich eine Abhängigkeit <strong>de</strong>r Daten von <strong>de</strong>n eigenen Daten<br />

und <strong>de</strong>n Daten <strong>de</strong>r Nachbar-Zelle (in diesem Fall die Speicherzelle). Von <strong>de</strong>r Zeit,<br />

<strong>de</strong>r genauen Position <strong>de</strong>r Zelle o<strong>de</strong>r <strong>de</strong>r Verbindung an sich sind die Daten nicht<br />

52


abhängig. Die einzige Verbindung <strong>de</strong>r Zelle in diesem Schritt ist die Verbindung<br />

zur Speicherzelle und diese wird nicht geän<strong>de</strong>rt. Aus diesem Grund ergeben sich<br />

in diesem Schritt keine Abhängigkeiten <strong>de</strong>r Verbindungen.<br />

• Im zweiten Schritt wer<strong>de</strong>n Tupel von <strong>de</strong>r Speicherzelle bereitgestellt und von <strong>de</strong>n<br />

an<strong>de</strong>ren Zellen gelesen. Abhängig von <strong>de</strong>n eigenen Daten wer<strong>de</strong>n die Daten <strong>de</strong>r<br />

Speicherzelle weiter verwen<strong>de</strong>t o<strong>de</strong>r nicht. Somit ergibt sich wie<strong>de</strong>r eine Abhängigkeit<br />

<strong>de</strong>r Daten von <strong>de</strong>n eigenen Daten sowie <strong>de</strong>n Daten <strong>de</strong>r Nachbar-Zelle. Auch<br />

hier verän<strong>de</strong>rt sich die Verbindung nicht, weshalb hier wie<strong>de</strong>r keine Abhängigkeiten<br />

für diese festgestellt wer<strong>de</strong>n können.<br />

• Der dritte Schritt umfasst das Kopieren <strong>de</strong>s Wertes (bzw. Vektors für die Speicherzelle)<br />

T in <strong>de</strong>n Wert (Vektor) B. Das heißt, dass sich die Daten nur abhängig von<br />

<strong>de</strong>n eigenen Daten än<strong>de</strong>rn. Es wer<strong>de</strong>n keine Verbindungen benutzt o<strong>de</strong>r verän<strong>de</strong>rt,<br />

weshalb wie<strong>de</strong>r keine Abhängigkeiten für die Verbingungen bestehen.<br />

• Im vierten Schritt wird wie<strong>de</strong>r von <strong>de</strong>r Zelle ein Wert <strong>de</strong>r Speicherzelle gelesen und<br />

abhängig davon <strong>de</strong>r eigene Wert geän<strong>de</strong>rt. Da <strong>de</strong>r aus <strong>de</strong>r Speicherzelle gelesene<br />

Wert abhängig von <strong>de</strong>n eigenen Daten ist, ergibt sich wie<strong>de</strong>r die Abhängigkeit <strong>de</strong>r<br />

Daten von <strong>de</strong>n eigenen Daten und <strong>de</strong>n Daten <strong>de</strong>r Nachbar-Zelle. Es wer<strong>de</strong>n auch<br />

hier keine Verbindungen verän<strong>de</strong>rt.<br />

• Im fünften Schritt wird wie<strong>de</strong>r ein Wert aus <strong>de</strong>r Speicherzelle ausgelesen. Abhängig<br />

von diesem gelesenen Wert und <strong>de</strong>m eigenen Wert wird dann <strong>de</strong>r neue Wert bestimmt.<br />

Es ergibt sich also eine Abhängigkeit von <strong>de</strong>n eigenen Daten und <strong>de</strong>n<br />

Daten <strong>de</strong>r Nachbar-Zelle. Auch hier wer<strong>de</strong>n keine Verbindungen verän<strong>de</strong>rt, es ergeben<br />

sich dafür also keine Abhängigkeiten.<br />

Mit Hilfe <strong>de</strong>s Speicherzellen-Konzepts ist es gelungen, dass <strong>de</strong>r Algorithmus im Laufe<br />

<strong>de</strong>r Zeit keine Än<strong>de</strong>rungen an <strong>de</strong>n Verbindungen vornehmen muss. Auch sind die Daten<br />

immer nur abhängig von <strong>de</strong>n eigenen Daten und <strong>de</strong>n Daten <strong>de</strong>r Speicherzelle. Die Speicherzelle<br />

bil<strong>de</strong>t keine Ausnahme bei <strong>de</strong>n Zellen. Sie hängt implizit von <strong>de</strong>n eigenen Daten<br />

ab, da die Zellen ihren Wert abfragen und mit <strong>de</strong>ren Hilfe ihren neuen Wert berechnen.<br />

Es ergeben sich also die Funktionen: d’ = f(d,d*) und p’ = g().<br />

3.1.3.4.2. Laufzeitkomplexitätsverbesserung Alle Techniken, welche in <strong>de</strong>r Komplexitätsbetrach<strong>tu</strong>ng<br />

<strong>de</strong>s Algorithmus auf <strong>de</strong>r P-RAM angewen<strong>de</strong>t wur<strong>de</strong>n, sind auch auf<br />

<strong>de</strong>m GCA anwendbar. Mit Hilfe <strong>de</strong>r Speicherzelle verhält sich <strong>de</strong>r GCA fast ebenso wie<br />

die P-RAM und die regelmäßige Auffrischung <strong>de</strong>s Speichers <strong>de</strong>r Speicherzelle ist aufgrund<br />

<strong>de</strong>r hohen Nebenläufigkeit in O(1) möglich und wirkt sich somit nicht negativ auf<br />

die Komplexitätsbetrach<strong>tu</strong>ng aus.<br />

53


3.2. Minimal aufspannen<strong>de</strong> Bäume<br />

Bäume sind zusammenhängen<strong>de</strong>, azyklische Graphen. Das be<strong>de</strong>utet in einem ungerichteten<br />

Graphen, dass es von je<strong>de</strong>m Knoten zu je<strong>de</strong>m an<strong>de</strong>ren Knoten genau einen Weg gibt.<br />

Ein aufspannen<strong>de</strong>r Baum ist ein Baum, welcher aus einem Graphen durch das Entfernen<br />

von Kanten entstan<strong>de</strong>n ist. Ein minimal aufspannen<strong>de</strong>r Baum ist ein aufspannen<strong>de</strong>r<br />

Baum, <strong>de</strong>ssen Summe an Kantenwer<strong>tu</strong>ng minimal ist. Solche minimal aufspannen<strong>de</strong>n<br />

Bäume wer<strong>de</strong>n z. B. in Netzwerken verwen<strong>de</strong>t, um aus einer Menge von möglichen<br />

Verbindungen diejenigen auszuwählen, welche die geringsten Kosten verursachen und<br />

trotz<strong>de</strong>m eine Verbindung zwischen allen Komponenten garantieren.<br />

In <strong>de</strong>r sequentiellen Programmierung gibt es viele <strong>Algorithmen</strong> zur Bestimmung von<br />

minimal aufspannen<strong>de</strong>n Bäumen. Die <strong>Algorithmen</strong>, welche im Grunds<strong>tu</strong>dium <strong>de</strong>r Informatik<br />

gelehrt wer<strong>de</strong>n, sind <strong>de</strong>r Algorithmus von Kruskal und <strong>de</strong>r Algorithmus von<br />

Prim. Bei<strong>de</strong> liefern einen minimal aufspannen<strong>de</strong>n Baum, allerdings stecken vollkommen<br />

unterschiedliche Ansätze hinter <strong>de</strong>n <strong>Algorithmen</strong>. Aus diesem Grund wer<strong>de</strong>n die bei<strong>de</strong>n<br />

<strong>Algorithmen</strong> auch meist verwen<strong>de</strong>t, um zu illustrieren, dass verschie<strong>de</strong>ne Ansätze zu<br />

richtigen Ergebnissen führen können.<br />

Der Algorithmus von Kruskal startet mit einer sortierten Menge an Kanten und <strong>de</strong>n<br />

Knoten <strong>de</strong>s Baums, die als eigenständige Wäl<strong>de</strong>r 8 betrachtet wer<strong>de</strong>n. Im Verlauf <strong>de</strong>s<br />

Algorithmus wird dann die kleinste Kante <strong>de</strong>s Graphen genommen und überprüft, ob<br />

durch das Aufnehmen dieser Kante zwei Wäl<strong>de</strong>r verbun<strong>de</strong>n wer<strong>de</strong>n o<strong>de</strong>r nicht. Wenn<br />

keine zwei Wäl<strong>de</strong>r verbun<strong>de</strong>n wer<strong>de</strong>n, entstün<strong>de</strong> mit <strong>de</strong>m Aufnehmen <strong>de</strong>r Kante ein<br />

Zyklus, weshalb die Kante verworfen wird. Ansonsten wird die Kante aufgenommen und<br />

die bei<strong>de</strong>n Wäl<strong>de</strong>r wer<strong>de</strong>n zu einem Wald zusammengefasst.<br />

Der Algorithmus von Prim hat einen Startknoten, welcher anfangs die Menge <strong>de</strong>r aufgenommenen<br />

Knoten repräsentiert. Im Verlauf <strong>de</strong>s Algorithmus wird immer die Kante<br />

gesucht, die einen Knoten aus dieser Menge mit einem Knoten außerhalb <strong>de</strong>r Menge<br />

verbin<strong>de</strong>t und dabei die minimale Kantenwer<strong>tu</strong>ng hat. Diese Kante wird dann in <strong>de</strong>n<br />

minimalen Spannbaum aufgenommen und die Menge wird um <strong>de</strong>n Knoten erweitert, <strong>de</strong>r<br />

nicht in <strong>de</strong>r Menge war und nun durch die Kante verbun<strong>de</strong>n wur<strong>de</strong>. Da keine Kanten<br />

betrachtet wer<strong>de</strong>n, die nicht genau einen Knoten in <strong>de</strong>r Menge als Start o<strong>de</strong>r Ziel haben,<br />

können keine Zyklen entstehen.<br />

3.2.1. Der Kruskal-Algorithmus<br />

Auch beim Algorithmus von Kruskal 9 muss betrachtet wer<strong>de</strong>n, inwiefern <strong>de</strong>r Algorithmus<br />

parallelisierbar ist. Betrachtet man <strong>de</strong>n Algorithmus kritisch, so muss man feststellen,<br />

8 Ein Baum ist ein zusammenhängen<strong>de</strong>r, azyklischer Graph. Ein Wald besteht aus mehreren Bäumen,<br />

die nicht unbedingt zusammenhängen müssen.<br />

9 Eine Erklärung zu Kruskal fin<strong>de</strong>t sich in [Gü92] auf Seite 170-173. In diesem Buch fin<strong>de</strong>n sich auch<br />

an<strong>de</strong>re Graphenalgorithmen, u. a. <strong>de</strong>r Warshall-Algorithmus.<br />

54


dass die Kanten nicht parallel überprüft wer<strong>de</strong>n können, weil nicht parallel getestet wer<strong>de</strong>n<br />

kann, ob ein Zyklus ensteht. Da <strong>de</strong>r Kruskal allerdings eine Sortierung <strong>de</strong>r Kanten<br />

for<strong>de</strong>rt, bietet sich dort eine Möglichkeit zur Parallelisierung 10 . Da <strong>de</strong>r Kruskal in je<strong>de</strong>m<br />

Schritt nur die kleinste Kante for<strong>de</strong>rt und nicht nötigerweise von Anfang an eine sortierte<br />

Liste benötigt, kann die Sortierung parallel zur Zyklenüberprüfung stattfin<strong>de</strong>n. Auf <strong>de</strong>m<br />

GCA existieren viele Sortieralgorithmen, u. a. das bitonische Mischen (s. [Hee01]), das<br />

sich hier aber nicht so gut eignet. Ein Sortier-Algorithmus, welcher sich sowohl parallelisieren<br />

lässt als auch in je<strong>de</strong>m Schritt die kleinste Kante zur Verfügung stellen kann, ist<br />

<strong>de</strong>r Heap-Sort. Dieser soll in <strong>de</strong>m folgen<strong>de</strong>n Abschnitt an einem Beispiel näher erklärt<br />

wer<strong>de</strong>n, bevor dann <strong>de</strong>r Kruskal mit <strong>de</strong>m Heap-Sort auf <strong>de</strong>m GCA mo<strong>de</strong>lliert wird.<br />

3.2.1.1. Der Heap-Sort<br />

Da hier immer die kleinste Kantenwer<strong>tu</strong>ng gesucht wird, kann in diesem Falle ein Min-<br />

Heap verwen<strong>de</strong>t wer<strong>de</strong>n, d. h. <strong>de</strong>r Vaterknoten enthält immer einen kleineren Wert als<br />

seine Söhne. Damit steht dann immer <strong>de</strong>r kleinste Wert <strong>de</strong>s Baums in <strong>de</strong>r Wurzel.<br />

Abbildung 3.21.: Ein Min-Heap: In <strong>de</strong>r Wurzel steht immer ein kleinerer Wert als in<br />

<strong>de</strong>n Söhnen. Außer<strong>de</strong>m ist <strong>de</strong>r Baum fast vollständig.<br />

Um <strong>de</strong>n Heap aufzubauen und am En<strong>de</strong> alle Bedingung wie in Abbildung 3.21 zu erfüllen,<br />

wird die zu sortieren<strong>de</strong> Menge als Vektor eingelesen. In diesem Fall sollen Kanten sortiert<br />

wer<strong>de</strong>n, es wird also ein Baum <strong>de</strong>r Höhe log(|E|) bei <strong>de</strong>r Wurzel beginnend schrittweise<br />

gefüllt. Dabei kommt das erste Element in die Wurzel, das zweite in <strong>de</strong>n linken Sohn<br />

<strong>de</strong>r Wurzel, das dritte in <strong>de</strong>n rechten und dann wird die nächste Ebene von links nach<br />

rechts aufgefüllt. Im weiteren wird <strong>de</strong>r Heap allgemein für n Werte beschrieben.<br />

Ein Baum, in <strong>de</strong>m je<strong>de</strong> Ebene bis auf die letzte vollständig gefüllt und <strong>de</strong>ssen letzte<br />

Ebene von links beginnend ohne Lücken gefüllt ist, wird fast vollständig genannt. Wird<br />

<strong>de</strong>r Baum wie beschrieben aufgebaut, so ist er fast vollständig. Der Baum dient dabei<br />

lediglich <strong>de</strong>r Visualisierung <strong>de</strong>r Vorgänge beim Ablauf <strong>de</strong>s Algorithmus, <strong>de</strong>nn <strong>de</strong>r<br />

Algorithmus selbst arbeitet auf <strong>de</strong>m Vektor. Dieser Vektor kann genau dann als fast<br />

vollständiger Baum interpretiert wer<strong>de</strong>n, wenn es keine freien Einträge im Vektor gibt.<br />

10 Dieser Vorschlag wird auch in [Kru05] gemacht. Dort wird allerdings eine an<strong>de</strong>re Möglichkeit vorgeschlagen,<br />

<strong>de</strong>n Heap zu parallelisieren.<br />

55


Im Folgen<strong>de</strong>n wird <strong>de</strong>r Algorithmus weiterhin am Baum erklärt, da dies anschaulicher<br />

ist.<br />

Ist <strong>de</strong>r Baum wie oben beschrieben gefüllt wor<strong>de</strong>n, so entspricht er noch nicht <strong>de</strong>r Bedingung,<br />

dass die Wurzel immer kleiner als ihre Söhne sein muss. Dazu wer<strong>de</strong>n die Knoten<br />

<strong>de</strong>r vorletzten Ebene überprüft, ob sie Söhne haben, <strong>de</strong>ren Wert kleiner ist als ihr eigener.<br />

Ist dies <strong>de</strong>r Fall, so wird mit <strong>de</strong>m Sohn getauscht, <strong>de</strong>r <strong>de</strong>n kleineren Wert enthält.<br />

Als Beispiel wird <strong>de</strong>r Baum betrachtet, <strong>de</strong>r durch <strong>de</strong>n Vektor (7, 5, 10, 1, 3, 4, 9, 6)<br />

beschrieben wird. Eine graphische Darstellung <strong>de</strong>s Baums fin<strong>de</strong>t sich in Abbildung 3.22.<br />

Abbildung 3.22.: Nach <strong>de</strong>m Einlesen <strong>de</strong>s Vektors in <strong>de</strong>n Baum ist zwar ein fast<br />

vollständiger Baum entstan<strong>de</strong>n, er erfüllt allerdings noch nicht die<br />

Heap-Eigenschaft.<br />

Um die Heapeigenschaft herzustellen wird zuerst <strong>de</strong>r Wert 1 überprüft, ob er kleiner<br />

als sein Sohn ist. Dies ist <strong>de</strong>r Fall, also wird als nächstes <strong>de</strong>r Wert 10 überprüft, ob er<br />

kleiner als seine Söhne mit <strong>de</strong>n Werten 4 und 9 ist. Da hier die Bedingung nicht erfüllt<br />

ist, wird <strong>de</strong>r Wert 10 mit <strong>de</strong>m Wert 4 getauscht und dieser Teilbaum erfüllt nun für<br />

sich die Heap-Bedingung. Anschließend wird <strong>de</strong>r Wert 5 und <strong>de</strong>ssen zwei Söhne mit <strong>de</strong>n<br />

Werten 1 und 3 verglichen. Nun muss <strong>de</strong>r Wert 1 mit <strong>de</strong>m Wert 5 getauscht wer<strong>de</strong>n, was<br />

dazu führt, dass auch nachgeprüft wer<strong>de</strong>n muss, ob <strong>de</strong>r Wert 5 kleiner als <strong>de</strong>r Wert 6 ist,<br />

<strong>de</strong>r nun sein Sohn ist. Dies ist <strong>de</strong>r Fall, <strong>de</strong>shalb ist die Vertauschung damit abgeschlossen<br />

und die Wurzel kann nun geprüft wer<strong>de</strong>n. Da 7 aber größer als die 1 ist, muss auch hier<br />

wie<strong>de</strong>r getauscht wer<strong>de</strong>n. In diesem Fall ist die 7 aber auch größer als ihre neuen Söhne 5<br />

und 3 und muss <strong>de</strong>shalb mit 3 getauscht wer<strong>de</strong>n. Damit ist die initiale Heap-Herstellung<br />

abgeschlossen, <strong>de</strong>r Heap aus Abbildung 3.21 wur<strong>de</strong> hergestellt und die erste sortierte<br />

Kante kann entnommen wer<strong>de</strong>n.<br />

Das Entnehmen eines Werts in einem Heap gestaltet sich <strong>de</strong>rmaßen, dass er mit <strong>de</strong>m Wert<br />

getauscht wird, <strong>de</strong>r im Heap in <strong>de</strong>r untersten Ebene an <strong>de</strong>r rechtesten Stelle steht. Diese<br />

Stelle wird dann in allen nachfolgen<strong>de</strong>n Schritten ignoriert. Durch die Vertauschung hat<br />

<strong>de</strong>r Baum nun ein zu betrachten<strong>de</strong>s Element weniger. Der hieraus resultieren<strong>de</strong> Baum<br />

ist in 3.23 dargestellt.<br />

Die Wie<strong>de</strong>rherstellung <strong>de</strong>r Heap-Eigenschaft gestaltet sich immer gleich und soll hier nur<br />

exemplarisch erklärt wer<strong>de</strong>n. Da vor <strong>de</strong>m Vertauschen die Heap-Eigenschaft erfüllt war,<br />

müssen maximal log(n) Vertauschungen durchgeführt wer<strong>de</strong>n, bis die Heap-Eigenschaft<br />

56


Abbildung 3.23.: Der kleinste Wert wur<strong>de</strong> aus <strong>de</strong>m Heap entfernt und die Heap-<br />

Eigenschaft ist verletzt.<br />

Abbildung 3.24.: Der Wert <strong>de</strong>r Wurzel wur<strong>de</strong> mit <strong>de</strong>m Wert <strong>de</strong>s linken Sohns vertauscht.<br />

Die Heap-Eigenschaft muss für <strong>de</strong>n linken Sohn wie<strong>de</strong>r hergestellt wer<strong>de</strong>n.<br />

wie<strong>de</strong>r hergestellt ist. Zuerst wird die Wurzel mit <strong>de</strong>m kleineren Wert ihrer Söhne vertauscht,<br />

in diesem Fall ist dies die 3 aus <strong>de</strong>m linken Teilbaum. Damit muss am rechten<br />

Teilbaum nichts mehr geän<strong>de</strong>rt und nur noch <strong>de</strong>r linke Teilbaum betrachtet wer<strong>de</strong>n. Der<br />

Baum, <strong>de</strong>r nach <strong>de</strong>r ersten Vertauschung entsteht, ist in Abbildung 3.24 zu sehen.<br />

Im linken Teilbaum wer<strong>de</strong>n wie<strong>de</strong>r <strong>de</strong>r Wert <strong>de</strong>r Wurzel mit <strong>de</strong>n Werten <strong>de</strong>r Söhne<br />

verglichen und anschließend wird die 6 mit <strong>de</strong>r 5 ausgetauscht. Die Wie<strong>de</strong>rherstellung <strong>de</strong>r<br />

Heap-Eigenschaft ist somit abgeschlossen und Abbildung 3.25 zeigt <strong>de</strong>n resultieren<strong>de</strong>n<br />

Heap.<br />

Abbildung 3.25.: Nach <strong>de</strong>r zweiten Vertauschung ist die Heap-Eigenschaft wie<strong>de</strong>r hergestellt.<br />

Nun folgt wie<strong>de</strong>r das Entfernen eines Wertes, die Wie<strong>de</strong>rherstellung usw. bis <strong>de</strong>r Heap<br />

nur noch aus einem Element besteht. Im sequentiellen Fall läuft <strong>de</strong>r Heap mit einer<br />

Laufzeit von O(nlog(n)), da n mal die Heap-Eigenschaft wie<strong>de</strong>r hergestellt wer<strong>de</strong>n muss<br />

und das Wie<strong>de</strong>rherstellen maximal log(n) Schritte benötigt.<br />

57


Der Heap-Sort eignet sich zur Parallelisierung, da je<strong>de</strong>r Wert innerhalb <strong>de</strong>s Heaps durch<br />

einen Prozessor repräsentiert wer<strong>de</strong>n kann. Die Möglichkeiten zur Parallelisierungen und<br />

wieviele Prozessoren wirklich benötigt wer<strong>de</strong>n, sollen im nächsten Abschnitt erläutert<br />

wer<strong>de</strong>n.<br />

3.2.1.2. Der Heap-Sort auf <strong>de</strong>m GCA<br />

Der Heap-Sort kann auf <strong>de</strong>m GCA dadurch realisiert wer<strong>de</strong>n, dass je<strong>de</strong>r zu sortieren<strong>de</strong><br />

Wert durch eine Zelle repräsentiert wird. Durch diese Realisierung wer<strong>de</strong>n O(n) Zellen<br />

benötigt. Je<strong>de</strong> Zelle muss wissen, ob sie einen Blattknoten o<strong>de</strong>r einen Wurzelknoten<br />

repräsentiert. Der Knoten, <strong>de</strong>r die Wurzel <strong>de</strong>s gesamten Baums repräsentiert, muss dies<br />

auch wissen, da er eine Son<strong>de</strong>rstellung hat.<br />

In regelmässigen Abstän<strong>de</strong>n muss ein Wurzelknoten abfragen, ob er einen größeren Wert<br />

besitzt, als seine Söhne. Da beim GCA nicht schreibend auf an<strong>de</strong>re Zellen zugegriffen<br />

wer<strong>de</strong>n darf, müssen auch die Söhne überprüfen, ob sie einerseits einen kleineren Wert<br />

als ihr Vater haben und an<strong>de</strong>rerseits, ob ihr Wert kleiner ist als <strong>de</strong>r Wert <strong>de</strong>s an<strong>de</strong>ren<br />

Sohns <strong>de</strong>r Wurzel. Ist einer <strong>de</strong>r bei<strong>de</strong>n Werte kleiner als <strong>de</strong>r eigene, so muss ein<br />

Sohnknoten nichts machen, ein Wurzelknoten hingegen erkennt dadurch, dass <strong>de</strong>r Wert<br />

getauscht wer<strong>de</strong>n muss. Ein Sohnknoten erkennt einen nötigen Tausch dadurch, dass er<br />

bei allen Vergleichen <strong>de</strong>n kleinsten Wert hat. Ein Son<strong>de</strong>rfall muss hier noch betrachtet<br />

wer<strong>de</strong>n: Sind gleiche Werte innerhalb <strong>de</strong>s Heaps erlaubt, so wer<strong>de</strong>n Zell-IDs eingeführt,<br />

um festzustellen, welches <strong>de</strong>r linke und welches <strong>de</strong>r rechte Sohn einer Zelle ist. Zu<strong>de</strong>m<br />

kann dann eine Zelle feststellen, ob sie ein linker o<strong>de</strong>r ein rechter Sohn ist. Hat nun eine<br />

Sohnzelle <strong>de</strong>n gleichen Wert wie die Wurzel, so wird <strong>de</strong>swegen nicht getauscht. Haben<br />

bei<strong>de</strong> Söhne <strong>de</strong>n gleichen Wert und es muss getauscht wer<strong>de</strong>n, so wird immer <strong>de</strong>r Wert<br />

<strong>de</strong>s linken Sohns genommen. In an<strong>de</strong>ren Worten be<strong>de</strong>utet das, dass eine Zelle, wenn sie<br />

bei einem Vergleich feststellt, dass sie <strong>de</strong>n gleichen Wert hat, nachprüft, ob <strong>de</strong>r gleiche<br />

Wert in einer Zelle mit niedriegerer ID steht. Ist dies <strong>de</strong>r Fall, so ist <strong>de</strong>r gleiche Wert<br />

entwe<strong>de</strong>r in <strong>de</strong>r Wurzel o<strong>de</strong>r im linken Sohn und es muss nichts getan wer<strong>de</strong>n. Ansonsten<br />

wird wie gewohnt verglichen, ob ein Tausch notwendig ist.<br />

Für <strong>de</strong>n Schritt <strong>de</strong>s Vertauschens ist es wichtig, dass <strong>de</strong>r GCA synchron arbeitet, <strong>de</strong>nn<br />

um die Werte zu tauschen, muss einerseits die Sohnzelle auf die Wurzelzelle und die<br />

Wurzelzelle ihrerseits auf die Sohnzelle lesend zugreifen. Wenn bei diesem Schritt <strong>de</strong>r<br />

Automat nicht synchron arbeitet, ist einer <strong>de</strong>r Werte bereits überschrieben, bevor die<br />

an<strong>de</strong>re Zelle ihn gelesen hat. Nach <strong>de</strong>m Vertauschen muss die Sohnzelle überprüfen,<br />

ob sie ein Blatt ist o<strong>de</strong>r ihrerseits wie<strong>de</strong>r Söhne hat. Der Wert <strong>de</strong>r Vaterzelle ist zu<br />

diesem Zeitpunkt schon wie<strong>de</strong>r abgreifbar. Eine Zelle muss sowohl Verbindungen zu<br />

ihren Söhnen als auch zu ihrem Vater haben. Die Möglichkeiten <strong>de</strong>n Heap auf <strong>de</strong>m GCA<br />

darzustellen ist in Abbildung 3.26 zu sehen. Auffallend ist hierbei, dass die Wurzel-Zelle<br />

<strong>de</strong>s Heaps lesen<strong>de</strong>n Zugriff auf alle Zellen hat, um die Vertauschungen durchzuführen,<br />

die jeweils notwendig sind, um die Liste zu sortieren. Da <strong>de</strong>r Heap hier in einer Form<br />

verwen<strong>de</strong>t wird, bei <strong>de</strong>r es egal ist, dass die eingegebene Liste im Verlauf <strong>de</strong>s Algorithmus<br />

zerstört wird (wichtig ist nur, dass immer zum richtigen Zeitpunkt <strong>de</strong>r jeweils kleinste<br />

58


Abbildung 3.26.: Die Heap-Zellen <strong>de</strong>s GCA kennen sowohl ihren Vater als auch ihre<br />

Söhne, sowie ihre Nachbarn. Die Wurzel-Zelle <strong>de</strong>s Heaps hat eine Verbindung<br />

zu je<strong>de</strong>r Zelle, damit sie die Werte tauschen kann. Je<strong>de</strong> Zelle<br />

weiß zu<strong>de</strong>m, ob sie ein Blatt darstellt o<strong>de</strong>r nicht.<br />

Abbildung 3.27.: Der in <strong>de</strong>n Heap eingelesene Vektor.<br />

Wert in <strong>de</strong>r Wurzel steht), reicht es, wenn die Wurzel die Werte liest und die Blattknoten<br />

sich <strong>de</strong>aktivieren.<br />

Die Synchronisation <strong>de</strong>r Deaktivierungen kann wie<strong>de</strong>r durch die Zell-ID erfolgen, in<strong>de</strong>m<br />

die Zellen aufsteigend in <strong>de</strong>r Reihenfolge durchnummeriert wer<strong>de</strong>n, in <strong>de</strong>r sie auch befüllt<br />

wer<strong>de</strong>n. Zu<strong>de</strong>m erhält je<strong>de</strong> Zelle das Wissen, wieviele Zellen es insgesamt im Heap gibt<br />

und das Wissen, nach wievielen Takten ein Blatt <strong>de</strong>aktiviert wird. Die Zellen testen alle<br />

parallel zur gleichen Zeit, ob sie die zu <strong>de</strong>aktivieren<strong>de</strong> Zelle sind und <strong>de</strong>krementieren dann<br />

die maximale Anzahl an Zellen. Die Zelle, die festgestellt hat, dass sie <strong>de</strong>aktiviert wer<strong>de</strong>n<br />

muss, <strong>tu</strong>t dies dann auch. Eine Zelle, die vor <strong>de</strong>m <strong>de</strong>aktivieren ihre Söhne überprüfen<br />

musste, kann leicht feststellen, ob dies auch nach <strong>de</strong>r Deaktivierung notwendig ist, in<strong>de</strong>m<br />

sie die Nummer ihres Sohns mit <strong>de</strong>r noch nicht <strong>de</strong>krementierten Anzahl an maximalen<br />

Zellen vergleicht. Ist <strong>de</strong>r einzige Sohn die zu <strong>de</strong>aktivieren<strong>de</strong> Zelle, so muss kein Vergleich<br />

mehr durchgeführt wer<strong>de</strong>n, ansonsten läuft <strong>de</strong>r Vergleich wie bereits beschrieben ab.<br />

Der Ablauf <strong>de</strong>s Heap-Algorithmus wird parallelisiert und im Folgen<strong>de</strong>n anhand von<br />

Graphiken erklärt. Damit das Beispiel aussagekräftig wird, muss ein größeres Beispiel<br />

gewählt wer<strong>de</strong>n, da das vorherige Beispiel nicht <strong>de</strong>utlich machen wür<strong>de</strong>, dass <strong>de</strong>r Algorithmus<br />

wirklich so funktioniert. Das Beispiel baut auf <strong>de</strong>m Vektor (7, 5, 10, 1, 3, 4, 9,<br />

6, 12, 15, 2, 20, 22, 14) auf und wird in Abbildung 3.27 als Baum dargestellt.<br />

59


Abbildung 3.28.: Der Heap nach Herstellung <strong>de</strong>r Heap-Eigenschaft. Der Schritt <strong>de</strong>r Herstellung<br />

muss noch sequentiell erfolgen, mehrere Prozessoren können<br />

dazu zwar verwen<strong>de</strong>t wer<strong>de</strong>n, ermöglichen aber keine Laufzeitverbesserung.<br />

Abbildung 3.29.: Der Eintrag <strong>de</strong>r Wurzel wird mit <strong>de</strong>m letzten Eintrag getauscht. Der<br />

letzte Eintrag wird für <strong>de</strong>n weiteren Ablauf <strong>de</strong>s Heap-Algorithmus ignoriert.<br />

Die initiale Herstellung <strong>de</strong>r Heap-Eigenschaft kann zwar parallel geschehen, aber eine<br />

Verbesserung <strong>de</strong>r Laufzeit ist nicht zu erwarten. Nach <strong>de</strong>r Herstellung <strong>de</strong>r Heap-<br />

Eigenschaft sieht <strong>de</strong>r Baum wie in Abbildung 3.28 aus.<br />

Im nächsten Schritt wird nun <strong>de</strong>r oberste Eintrag mit <strong>de</strong>m Eintrag <strong>de</strong>r untersten Ebene,<br />

welcher am weitesten rechts steht, vertauscht. In diesem Fall wird also die 1 mit <strong>de</strong>r 14<br />

getauscht. Dies wird in Abbildung 3.29 ver<strong>de</strong>utlicht.<br />

Im nächsten Schritt muss die Wurzel ihren Wert mit <strong>de</strong>n Werten ihrer Söhne vergleichen.<br />

Da dies <strong>de</strong>r Anfang <strong>de</strong>s Algorithmus ist, müssen nur die Wurzel und ihre bei<strong>de</strong>n Söhne<br />

arbeiten, die restlichen Zellen sind inaktiv. Nach <strong>de</strong>m Vertauschen sieht <strong>de</strong>r Baum wie in<br />

Abbildung 3.30 aus. Nach <strong>de</strong>r Vertauschung steht <strong>de</strong>r kleinste Wert wie<strong>de</strong>r in <strong>de</strong>r Wurzel<br />

<strong>de</strong>s Baums, die Wurzel kann also wie<strong>de</strong>r mit <strong>de</strong>m letzten Wert getauscht wer<strong>de</strong>n.<br />

Im nächsten Schritt tauscht die Wurzel wie<strong>de</strong>r ihren Wert mit <strong>de</strong>m letzten Wert und zeitgleich<br />

führt <strong>de</strong>r linke Sohn die Vertauschung aus, die nötig ist, um die Heap-Eigenschaft<br />

wie<strong>de</strong>r herstellen zu können. Der linke Sohn hat nach dieser Vertauschung <strong>de</strong>n kleinsten<br />

Wert <strong>de</strong>s linken Teilbaums und kann im nächsten Schritt <strong>de</strong>r Wurzel wie<strong>de</strong>r zum<br />

60


Abbildung 3.30.: Die Wurzel wur<strong>de</strong> vertauscht und <strong>de</strong>r Vergleich mit <strong>de</strong>n Söhnen fand<br />

statt. In <strong>de</strong>r Wurzel steht nun wie<strong>de</strong>r <strong>de</strong>r kleinste Wert <strong>de</strong>s Baums.<br />

Abbildung 3.31.: Die Wurzel wur<strong>de</strong> erneut vertauscht. Zeitgleich hat <strong>de</strong>r linke Sohn seine<br />

Ausgleichsfunktion ausgeführt und beinhaltet wie<strong>de</strong>r <strong>de</strong>n kleinsten Wert<br />

dieses Teilbaums.<br />

Vergleich dienen. In diesem Schritt sind also die Wurzel, ihr linker Sohn, sowie <strong>de</strong>ssen<br />

bei<strong>de</strong>n Söhne beschäftigt. Ver<strong>de</strong>utlicht wird dieser Vorgang in Abbildung 3.31.<br />

Nun muss sowohl die Wurzel wie<strong>de</strong>r mit ihren Söhnen <strong>de</strong>n Vergleich ausführen. Genauso<br />

muss auch <strong>de</strong>r zweite Knoten <strong>de</strong>r dritten Ebene einen Vergleich mit seinen Söhnen<br />

ausführen. Die Vergleiche können zeitgleich stattfin<strong>de</strong>n und somit wer<strong>de</strong>n zwei Vertauschungen<br />

in diesem Schritt ausgeführt. Nach <strong>de</strong>m Vertauschen hat die Wurzel wie<strong>de</strong>r<br />

<strong>de</strong>n kleinsten Wert. Der resultieren<strong>de</strong> Baum ist in Abbildung 3.32 zu sehen.<br />

Der Algorithmus arbeitet nach <strong>de</strong>m selben Prinzip weiter. Ein letztes Beispiel soll Abbildung<br />

3.33 liefern. In einem Schritt tauscht die Wurzel ihren Eintrag mit <strong>de</strong>m letzten Wert<br />

und im nächsten wird dann die Vertauschung mit <strong>de</strong>n Söhnen ausgeführt. Die an<strong>de</strong>ren<br />

Knoten verhalten sich in ihrer Ebene immer gleich. Die Ebenen mit gera<strong>de</strong>r Nummer<br />

prüfen im zweiten Takt, ob sie mit ihrem Vater tauschen müssen, und im dritten, ob ein<br />

Tausch mit <strong>de</strong>n Söhnen nötig ist. Ebenen mit einer ungera<strong>de</strong>n Nummer testen bei <strong>de</strong>n<br />

ungera<strong>de</strong>n Takten, ob ein Tausch mit <strong>de</strong>m Vater nötig ist, und in <strong>de</strong>n gera<strong>de</strong>n Takten<br />

vergleichen sie ihren Wert mit <strong>de</strong>m ihrer Söhne.<br />

Nach <strong>de</strong>r Initialisierungs- und Herstellungsphase ermöglicht <strong>de</strong>r parallele Heap-Sort nach<br />

je<strong>de</strong>m zweiten Schritt das Entnehmen <strong>de</strong>s kleinsten Werts. Der Heap-Sort muss n mal<br />

61


Abbildung 3.32.: Die Wurzel hat wie<strong>de</strong>r mit <strong>de</strong>m kleineren Wert ihrer Söhne getauscht.<br />

Zeitgleich hat <strong>de</strong>r zweite Knoten <strong>de</strong>r dritte Ebene <strong>de</strong>n Vergleich und<br />

die Vertauschung mit seinen Söhnen durchgeführt.<br />

Abbildung 3.33.: Die Wurzel wur<strong>de</strong> wie<strong>de</strong>r mit <strong>de</strong>m letzten Element vertauscht, zeitgleich<br />

hat <strong>de</strong>r linke Sohn <strong>de</strong>n Wert mit seinem linken Sohn getauscht.<br />

<strong>de</strong>n niedrigsten Wert liefern, bevor die gesamte Liste sortiert ist. Damit hat <strong>de</strong>r Heap-<br />

Sort eine Komplexität von O(n). Innerhalb dieser Zeitgrenze können auch die Initialisierung<br />

sowie die anfängliche Herstellung <strong>de</strong>r Heapeigenschaft erledigt wer<strong>de</strong>n.<br />

Die Zelle, <strong>de</strong>ren Wert in die Wurzel gelesen wird, nach<strong>de</strong>m <strong>de</strong>r minimale Wert aus <strong>de</strong>r<br />

Wurzel ausgewertet wur<strong>de</strong>, muss sich nach <strong>de</strong>m Lesen <strong>de</strong>aktivieren. Wie dies ermöglicht<br />

wer<strong>de</strong>n kann, wur<strong>de</strong> bereits beschrieben. Zu<strong>de</strong>m muss diese Zelle bei ungera<strong>de</strong>n Takten<br />

aus <strong>de</strong>m Vergleich ausgeklammert wer<strong>de</strong>n, da sonst ein Element even<strong>tu</strong>ell doppelt im<br />

Baum vorkommt und ein Element fälschlicherweise gelöscht wird. Durch die Parallelisierung<br />

kann die Heap-Eigenschaft nicht mehr garantiert wer<strong>de</strong>n und <strong>de</strong>r Algorithmus<br />

funktioniert nur <strong>de</strong>shalb, weil die Heap-Eigenschaft anfangs hergestellt ist und die Wie<strong>de</strong>rherstellung<br />

von oben beginnt und somit <strong>de</strong>r Wurzel recht schnell <strong>de</strong>r nächste minimale<br />

Wert zur Verfügung gestellt wer<strong>de</strong>n kann.<br />

Diese Art <strong>de</strong>r Realisierung benötigt unnötig viele Zellen, da die meisten Zellen einer<br />

Ebene inaktiv sind und <strong>de</strong>r Heap zu<strong>de</strong>m verhältnismäßig schnell abgebaut wird.<br />

Um Zellen zu sparen, können die Zellen einer Ebene zusammengefasst wer<strong>de</strong>n. Je<strong>de</strong><br />

Ebenen-Zelle beinhaltet dann eine Liste, welche die Werte beinhaltet, die auf einer Ebene<br />

sind. Die einzige Zelle, welche even<strong>tu</strong>ell zwei Aktionen in einem Takt durchführen müsste,<br />

62


Abbildung 3.34.: Je<strong>de</strong> Zelle repräsentiert eine Ebene <strong>de</strong>s Baums aus Abbildung 3.21. Die<br />

Wurzel muss lesend auf alle Zellen zugreifen können.<br />

ist die Zelle, welche die letzte Ebene repräsentiert, da diese zeitgleich in einen Vergleich<br />

nach oben und eine Austauschaktion von <strong>de</strong>r Wurzel verstrickt sein kann. Aber auch<br />

wenn man für je<strong>de</strong> Vertauschungsaktion zwei Takte ansetzt, damit kein Konflikt entsteht,<br />

verschlechtert sich die Komplexitätsklasse nicht.<br />

Die Zelle, welche die letzte Ebene repräsentiert, muss dies wissen, damit sie sich entsprechend<br />

verhält. Unbelegte Plätze wer<strong>de</strong>n in dieser Zelle durch ein x gekennzeichnet und<br />

wenn die Wurzel sich <strong>de</strong>n letzten Wert geholt hat, muss dieser aus <strong>de</strong>r Liste gelöscht<br />

wer<strong>de</strong>n. Sind alle Werte <strong>de</strong>r Zelle auf x, so geht sie in <strong>de</strong>n inaktiven Modus. Die Zelle <strong>de</strong>r<br />

darüberliegen<strong>de</strong>n Ebene erkennt nun, dass sie die letzte Ebene repräsentiert und verhält<br />

sich <strong>de</strong>mentsprechend. Wenn die Wurzel die letzte Ebene ist, dann ist <strong>de</strong>r Algorithmus<br />

been<strong>de</strong>t. Eine Möglichkeit, die Zellen die Ebenen <strong>de</strong>s Baums darstellen zu lassen, ist in<br />

Abbildung 3.34 gegeben.<br />

Zu klären ist noch, wie <strong>de</strong>r Nachbar, <strong>de</strong>r Vater und die Söhne eines Werts <strong>de</strong>r Zelle<br />

i<strong>de</strong>ntifiziert wer<strong>de</strong>n können. Rechnet man nach, welchen Wert <strong>de</strong>r j-te Wert <strong>de</strong>r i-ten<br />

Ebene repräsentiert, so stellt man fest, dass er genau <strong>de</strong>n (2 i−1 + (j − 1))-ten Wert<br />

im gesamten Baum repräsentiert. Innerhalb eines Baums, <strong>de</strong>ssen Werte von 1 bis n<br />

nummeriert wer<strong>de</strong>n, hat <strong>de</strong>r i-te Wert seine Söhne an <strong>de</strong>r (2 ∗ i)-ten und <strong>de</strong>r (2 ∗ i + 1)-<br />

ten Stelle. Die Zelle muss zum Ausrechnen <strong>de</strong>r Stelle <strong>de</strong>s Vaters eines Werts nur die Stelle<br />

<strong>de</strong>s zu vergleichen<strong>de</strong>n Werts durch zwei teilen und abrun<strong>de</strong>n und hat somit die Position<br />

<strong>de</strong>s Vaters bestimmt. Die Stellen <strong>de</strong>r Söhne bestimmen sich dadurch, dass die Stelle mal<br />

zwei für <strong>de</strong>n einen Sohn und mal zwei und plus eins für <strong>de</strong>n zweiten Sohn genommen<br />

wird. Natürlich benötigt die Zelle auch das Wissen darüber, ob <strong>de</strong>r Wert überhaupt<br />

einen Vater bzw. Söhne hat. Dazu dient die Information über die Ebene und die Blatt-<br />

Einträge, die für je<strong>de</strong>n Wert angeben, ob er ein Blatt repräsentiert. Der Nachbar wird<br />

abhängig davon, ob <strong>de</strong>r Wert selber an einer gera<strong>de</strong>n o<strong>de</strong>r einer ungera<strong>de</strong>n Stelle steht,<br />

gefun<strong>de</strong>n: Der Nachbar eines Werts an einer ungera<strong>de</strong>n Stelle steht rechts vom Wert,<br />

ansonsten links.<br />

Soll <strong>de</strong>r Heap am En<strong>de</strong> eine sortierte Liste liefern und nicht wie hier nur regelmäßig<br />

die kleinste Kante zur Abfrage bereitstellen, benötigt je<strong>de</strong> Zelle zu<strong>de</strong>m einen Zähler,<br />

welcher ihrer Werte für <strong>de</strong>n Heap noch betrachtet wer<strong>de</strong>n. Die Zelle <strong>de</strong>r letzten Ebene<br />

63


muss dann auch dafür sorgen, dass ihr letzter Wert nicht nur gelesen wird, son<strong>de</strong>rn auch<br />

<strong>de</strong>r Wert <strong>de</strong>r Wurzel an diese Stelle geschrieben und <strong>de</strong>r Zähler <strong>de</strong>r zu beachten<strong>de</strong>n<br />

Werte um eins erniedrigt wird. Da je<strong>de</strong>s Da<strong>tu</strong>m einmal zurückgeliefert wer<strong>de</strong>n muss,<br />

kann die Laufzeit nicht besser wer<strong>de</strong>n. Allerdings wird das Wie<strong>de</strong>rherstellen <strong>de</strong>s Heaps<br />

parallel zu <strong>de</strong>r Ausgabe <strong>de</strong>r Kanten ausgeführt und kann somit für <strong>de</strong>n Algorithmus<br />

als konstant angesehen wer<strong>de</strong>n. Der Heap-Sort benötigt <strong>de</strong>mnach auf <strong>de</strong>m GCA eine<br />

Laufzeit von O(n).<br />

Möchte man nun <strong>de</strong>n Heap-Sort wie<strong>de</strong>r in das in Kapitel 2.3.4 vorgestellte Schema<br />

einordnen, so sind vorher ein paar Design-Entscheidungen zu treffen:<br />

Es wird davon ausgegangen, dass eine Sohnzelle ihren Sta<strong>tu</strong>s (letzte Ebene o<strong>de</strong>r nicht)<br />

daran feststellen kann, dass die Verbindung zu ihrem Sohn inaktiv ist. Die Daten sind<br />

also abhängig von <strong>de</strong>r eigenen Verbindung.<br />

Innerhalb <strong>de</strong>r Zelle <strong>de</strong>r letzten Ebene wird abhängig von <strong>de</strong>r Zeit jeweils ein an<strong>de</strong>res<br />

Da<strong>tu</strong>m von <strong>de</strong>r obersten Ebene ausgelesen. Dieses Da<strong>tu</strong>m wird anschließend von <strong>de</strong>r<br />

Zelle auf x gesetzt und somit ignoriert. Die Daten sind also abhängig von <strong>de</strong>r Zeit.<br />

Die Vertauschungen innerhalb <strong>de</strong>s Heaps sind abhängig von <strong>de</strong>n Daten <strong>de</strong>r eigenen und<br />

<strong>de</strong>r Vater- bzw. Sohnzelle. Da eine Vertauschung eine Verän<strong>de</strong>rung <strong>de</strong>r Daten be<strong>de</strong>utet,<br />

ergibt sich hier eine Abhängigkeit <strong>de</strong>r Daten von <strong>de</strong>n eigenen Daten sowie <strong>de</strong>r Daten <strong>de</strong>r<br />

Nachbar-Zellen.<br />

Der Sta<strong>tu</strong>s ist implizit abhängig von <strong>de</strong>m Ort <strong>de</strong>r Zelle. Allerdings wird <strong>de</strong>r Sta<strong>tu</strong>s<br />

geschickter durch die Verbindung getestet, dann kann die Zelle sich an einem beliebigem<br />

Ort innerhalb <strong>de</strong>s GCA befin<strong>de</strong>n. Aus diesem Grund sind die Daten nicht ortsabhängig.<br />

Die Verbindung <strong>de</strong>r eigenen Zelle sind von <strong>de</strong>n Verbindungen <strong>de</strong>r Nachbar-Zellen abhängig.<br />

So setzt eine Zelle ihre eigene Sohn-Verbindung auf inaktiv, wenn <strong>de</strong>r Sohn seine Verbindung<br />

zum Vater auf inaktiv gesetzt hat. Die Verbindung ist also abhängig von <strong>de</strong>r<br />

Verbindung <strong>de</strong>s Sohns, allerdings nicht von <strong>de</strong>r eigenen Verbindung.<br />

Wur<strong>de</strong> das letzte Da<strong>tu</strong>m aus <strong>de</strong>r Zelle ausgelesen (alle Werte sind auf x), so wird die<br />

Verbindung zum Vaterknoten <strong>de</strong>aktiviert. Die Verbindung ist damit abhängig von <strong>de</strong>n<br />

eigenen Daten.<br />

Die oberste Ebene nimmt innerhalb <strong>de</strong>s Heap-Sorts eine Son<strong>de</strong>rstellung ein, da sie auf<br />

alle Kanten lesend zugreifen muss. Diese Verbindungen wer<strong>de</strong>n mit <strong>de</strong>r Zeit verän<strong>de</strong>rt,<br />

es wird nacheinan<strong>de</strong>r auf verschie<strong>de</strong>ne Zellen (entspr. Ebenen) lesend zugegriffen. Da<br />

diese Verän<strong>de</strong>rung nur die Vaterzelle betrifft, welcher eine beson<strong>de</strong>re Position hat, ist<br />

die Verän<strong>de</strong>rung <strong>de</strong>r Verbindungen abhängig von <strong>de</strong>m eigenen Ort innerhalb <strong>de</strong>s GCA.<br />

Zusätzlich ergibt sich hier eine Abhängigkeit <strong>de</strong>r Verbindungen <strong>de</strong>r Zelle von <strong>de</strong>r Zeit.<br />

Mit Hilfe dieser Betrach<strong>tu</strong>ngen ergeben sich folgen<strong>de</strong> Abhängigkeitsfunktionen:<br />

d’= f(t,p,d,d*) und p’ = g(t,l,p*,d).<br />

64


3.2.1.3. Der Kruskal auf <strong>de</strong>m GCA<br />

Die Hauptarbeit <strong>de</strong>s Kruskals, das Sortieren <strong>de</strong>r Kanten nach ihrem Kantengewicht, wur<strong>de</strong><br />

im vorherigen Abschnitt erklärt. Nun gilt es noch, die Wäl<strong>de</strong>rverwal<strong>tu</strong>ng geschickt auf<br />

<strong>de</strong>m GCA zu mo<strong>de</strong>llieren. Prinzipiell kann diese Verwal<strong>tu</strong>ng in einem Vektor geschehen,<br />

in <strong>de</strong>m für je<strong>de</strong>n Knoten die Nummer steht, welche <strong>de</strong>n Wald repräsentiert, zu <strong>de</strong>m <strong>de</strong>r<br />

Knoten gehört. Allerdings muss dann immer dafür gesorgt wer<strong>de</strong>n, dass eine neu aufgenommene<br />

Kante auch dazu führt, dass die ganze Liste ak<strong>tu</strong>alisiert wird, da alle Knoten<br />

eines Wal<strong>de</strong>s <strong>de</strong>n gleichen Repräsentanten haben müssen. Dies wür<strong>de</strong> im ungünstigsten<br />

Fall O(n) Schritte benötigen, weshalb anzuraten ist, sich Gedanken darüber zu machen,<br />

wie diese Information geschickter abgespeichert wer<strong>de</strong>n kann.<br />

Wird je<strong>de</strong>r Knoten durch eine Zelle repräsentiert, so kann dafür gesorgt wer<strong>de</strong>n, dass er<br />

eine Verbindung zu allen Knoten herstellt, die zum gleichen Wald wie er gehören. Wird<br />

dann <strong>de</strong>r Wert eines Knoten <strong>de</strong>s Wal<strong>de</strong>s geän<strong>de</strong>rt, da zwei Wäl<strong>de</strong>r zusammengefasst<br />

wur<strong>de</strong>n, stellen alle benachbarten Zellen dies fest und übernehmen die Verän<strong>de</strong>rung.<br />

Diese Art <strong>de</strong>r Verwal<strong>tu</strong>ng ist sehr verbindungsaufwendig, da im schlimmsten Fall je<strong>de</strong><br />

einen Knoten repräsentieren<strong>de</strong> Zelle eine Verbindung zu an<strong>de</strong>ren Zellen <strong>de</strong>r gleichen Art<br />

benötigt. Dies ergibt einen Bedarf von n 2 Lei<strong>tu</strong>ngen allein für die Informationsverwal<strong>tu</strong>ng.<br />

Wie beim Warshall-Algorithmus bietet sich hier jedoch ein Bus an, um das Problem zu<br />

umgehen. Je<strong>de</strong>r Knoten speichert für sich nur, zu welchem Wald er gehört. Muss dieser<br />

Wert geän<strong>de</strong>rt wer<strong>de</strong>n, so wird auf <strong>de</strong>n Bus einerseits <strong>de</strong>r Wert i gelegt, <strong>de</strong>r geän<strong>de</strong>rt<br />

wer<strong>de</strong>n soll, und an<strong>de</strong>rerseits <strong>de</strong>r neue Wert j, auf <strong>de</strong>n <strong>de</strong>r Wert i gesetzt wer<strong>de</strong>n soll.<br />

Somit kann man mit einem Bus <strong>de</strong>r Größe 2∗log 2 (n) auskommen, wobei n die Anzahl <strong>de</strong>r<br />

Knoten beschreibt. Je<strong>de</strong>r Knoten braucht ebensoviele Lei<strong>tu</strong>ngen, mit <strong>de</strong>nen er lesend auf<br />

<strong>de</strong>n Bus zugreifen kann. Zu<strong>de</strong>m benötigt man noch eine Zelle, welche <strong>de</strong>n eigentlichen<br />

Kruskal ausführt und somit schreibend auf <strong>de</strong>n Bus und lesend auf alle Informationszellen<br />

zugreifen können muss.<br />

Hat diese Zelle sich eine Kante von <strong>de</strong>r Wurzelzelle <strong>de</strong>s Heaps geholt, so liest sie die<br />

Werte <strong>de</strong>r bei<strong>de</strong>n beteiligten Knoten aus. Sind die die Nummern <strong>de</strong>s Wal<strong>de</strong>s, zu <strong>de</strong>m<br />

die Knoten gehören, gleich, so wird die Kante verworfen. Ansonsten wird die größere<br />

<strong>de</strong>r bei<strong>de</strong>n Nummern durch die kleinere ersetzt, in<strong>de</strong>m auf die oberen log 2 (n) Lei<strong>tu</strong>ngen<br />

<strong>de</strong>s Busses <strong>de</strong>r größere und auf die unteren log 2 (n) Lei<strong>tu</strong>ngen <strong>de</strong>r kleinere Wert gelegt<br />

wird. Im nächsten Takt ak<strong>tu</strong>alisieren sich dann alle Zellen, die zum Wald <strong>de</strong>s größeren<br />

Wertes gehören. Innerhalb dieses Takts liest die rechnen<strong>de</strong> Zelle die nächste Kante.<br />

Dieser Vorgang wird solange wie<strong>de</strong>rholt, bis entwe<strong>de</strong>r nur noch ein Baum vorhan<strong>de</strong>n ist<br />

o<strong>de</strong>r keine Kanten mehr vorhan<strong>de</strong>n sind (Wurzel <strong>de</strong>s Heaps auf inaktiv gesetzt). Um<br />

festzustellen, ob alle Knoten verbun<strong>de</strong>n sind, muss die rechnen<strong>de</strong> Zelle nur einen Zähler<br />

mitführen, <strong>de</strong>r je<strong>de</strong>s Mal inkrementiert wird, wenn eine Kante aufgenommen wird. Ein<br />

Baum mit n Knoten hat genau n − 1 Kanten und <strong>de</strong>mnach kann aufgehört wer<strong>de</strong>n,<br />

wenn diese n − 1 Kanten gefun<strong>de</strong>n wur<strong>de</strong>n. Der schematisierte Aufbau <strong>de</strong>s Kruskals ist<br />

in Abbildung 3.35 zu sehen.<br />

Mit <strong>de</strong>r Parallelisierung <strong>de</strong>s Kruskals kann eine Verbesserung <strong>de</strong>r Laufzeit von<br />

65


Abbildung 3.35.: Die rechnen<strong>de</strong> Zelle <strong>de</strong>s Kruskal braucht schreiben<strong>de</strong>n Zugriff auf <strong>de</strong>n<br />

Bus und lesen<strong>de</strong>n Zugriff auf alle Zellen, die Knoten repräsentieren als<br />

auch auf die Wurzel <strong>de</strong>r Heap-Zellen.<br />

O(|E|log(|E|)) auf O(|E|) erreicht wer<strong>de</strong>n. Allerdings ist die wirkliche Laufzeit <strong>de</strong>s Kruskal<br />

direkt vom ak<strong>tu</strong>ellen Problem abhängig, da nicht nötigerweise alle Kanten überprüft<br />

wer<strong>de</strong>n müssen. Die benötigten Zellen <strong>de</strong>s Heap-Algorithmus sind abhängig von <strong>de</strong>r Anzahl<br />

<strong>de</strong>r Kanten, da diese sortiert wer<strong>de</strong>n. Um die Laufzeitverbesserung zu erreichen,<br />

wur<strong>de</strong>n log 2 (|E|) Zellen für <strong>de</strong>n Heap sowie |V |+1 Zellen für <strong>de</strong>n Kruskal und die Informationsverwal<strong>tu</strong>ng<br />

investiert. Der Heap benötigt 3 ∗ (log 2 (|E|) − 1) − 1 unidirektionale<br />

Verbindungen 11 und <strong>de</strong>r eigentliche Kruskal noch einmal (2 ∗ log 2 (|V |) ∗ |V |) + (|V | + 1)<br />

unidirektionale Verbindungen sowie einen Bus <strong>de</strong>r Breite 2 ∗ log 2 (|V |).<br />

Ist es nicht nötig, dass die Kruskal-Zelle im nächsten Schritt ihre Werte schon wie<strong>de</strong>r<br />

überschreiben kann, ist ein Bus nicht notwendig. Die Kruskal-Zelle legt ihren Wert nicht<br />

mehr auf <strong>de</strong>n Bus, son<strong>de</strong>rn je<strong>de</strong> Knoten-Zelle hat eine Verbindung zur Kruskal-Zelle und<br />

fragt direkt dort die Än<strong>de</strong>rungen ab. Um zu vermei<strong>de</strong>n, dass ungültige Daten abgefragt<br />

wer<strong>de</strong>n, wird ein Valid-Bit eingeführt, welches dafür sorgt, dass nur gültige Daten abgeholt<br />

wer<strong>de</strong>n. Die Kruskal-Zelle schreibt nun <strong>de</strong>n zu än<strong>de</strong>rn<strong>de</strong>n Wert in die Variable<br />

Change und <strong>de</strong>n Wert, durch <strong>de</strong>n ersetzt wird, in die Variable Keep. Je<strong>de</strong> Knoten-Zelle,<br />

<strong>de</strong>ren Waldnummer mit <strong>de</strong>m Wert aus Change übereinstimmt, setzt diesen nun auf <strong>de</strong>n<br />

Wert aus Keep. Damit kann ein Takt gespart wer<strong>de</strong>n, in <strong>de</strong>m sonst die Kruskal-Zelle<br />

auf <strong>de</strong>n Bus geschrieben hätte. Da alle Zellen nur lesen<strong>de</strong>n Zugriff auf die Kruskal-Zelle<br />

erhalten, entstehen bei diesem Vorgehen keine Konflikte. Der Aufbau wird in 3.36 veranschaulicht.<br />

Wenn es wie im Fall <strong>de</strong>s Warshall-Algorithmus keine ausgezeichnete Zelle gibt, auf die<br />

alle Zellen lesend zugreifen können, dann muss eine Extra-Zelle genutzt wer<strong>de</strong>n, die<br />

11 Zwischen allen Ebenen <strong>de</strong>s Baumes wird eine Verbindung nach oben und eine nach unten benötigt.<br />

Bei log 2 (|E|) Ebenen sind dies 2∗log 2 (|E|)−2 Verbindungen. Zu<strong>de</strong>m wird noch eine Verbindung von<br />

<strong>de</strong>r Wurzelzelle zu allen an<strong>de</strong>ren Zellen benötigt; dies sind noch einmal log 2 (|E|) − 2 Verbindungen.<br />

66


Abbildung 3.36.: Die rechnen<strong>de</strong> Zelle <strong>de</strong>s Kruskal braucht lesen<strong>de</strong>n Zugriff auf alle Zellen,<br />

die Knoten repräsentieren, sowie die Wurzel <strong>de</strong>r Heap-Zellen. Die<br />

Knoten-Zellen holen sich die Werte Valid, Change und Keep von <strong>de</strong>r<br />

Kruskal-Zelle und wissen dann, ob ihre Variable Waldnr. geän<strong>de</strong>rt wer<strong>de</strong>n<br />

muss.<br />

Abbildung 3.37.: Die rechnen<strong>de</strong> Zelle <strong>de</strong>s Kruskal braucht lesen<strong>de</strong>n Zugriff auf alle Zellen,<br />

die Knoten repräsentieren, sowie die Wurzel <strong>de</strong>r Heap-Zellen. Die<br />

Bus-Zelle holt die Werte Valid, Change und Keep von <strong>de</strong>r Kruskal-<br />

Zelle und speichert diese. Die Knoten-Zellen können dann diese Werte<br />

aus <strong>de</strong>r Bus-Zelle auslesen.<br />

<strong>de</strong>n Bus ersetzt. Die Extra-Zelle hat lesen<strong>de</strong>n Zugriff auf je<strong>de</strong> Zelle, die im Laufe <strong>de</strong>r<br />

Berechnungen einen Wert bereitstellt. Im Beispiel <strong>de</strong>s Kruskal-Algorithmus betrifft dies<br />

nur die eine Zelle und solch ein Aufbau sähe wie in 3.37 aus. Die Bus-Zelle holt <strong>de</strong>n Wert<br />

67


von <strong>de</strong>r betreffen<strong>de</strong>n Zelle ab und alle Zellen, welche auf diesen Wert zugreifen müssen,<br />

können <strong>de</strong>n Wert dann bei dieser Zelle auslesen. Der Aufbau spart im Falle <strong>de</strong>s Kruskal<br />

keine Lei<strong>tu</strong>ngen ein, son<strong>de</strong>rn im Vergleich mit <strong>de</strong>m oben beschriebenen Aufbau wer<strong>de</strong>n<br />

sogar mehr Lei<strong>tu</strong>ngen benötigt, da die Bus-Zelle lesend auf die Kruskal-Zelle zugreifen<br />

muss.<br />

Betrachtet man aber als Vergleich <strong>de</strong>n Warshall-Algorithmus, so wür<strong>de</strong> bei einer lesen<strong>de</strong>n<br />

Verbindung von je<strong>de</strong>r <strong>de</strong>r n Zellen zu je<strong>de</strong>r Zelle mit n Lei<strong>tu</strong>ngen insgesamt (n − 1) ∗ n 2<br />

Lei<strong>tu</strong>ngen benötigt. Im Aufbau mit <strong>de</strong>r Bus-Zelle benötigt man allerdings nur von je<strong>de</strong>r<br />

<strong>de</strong>r n Zellen n Lei<strong>tu</strong>ngen zum lesen<strong>de</strong>n Zugriff auf die Bus-Zelle und n Lei<strong>tu</strong>ngen von<br />

<strong>de</strong>r Bus-Zelle zu je<strong>de</strong>r an<strong>de</strong>ren Zelle. Insgesamt sind dies dann (n+1) ∗n Lei<strong>tu</strong>ngen und<br />

es ergibt sich eine Ersparnis um <strong>de</strong>n Faktor n.<br />

Die Komplexität eines Algorithmus wird sich bei <strong>de</strong>r Verwendung <strong>de</strong>r Bus-Zelle gegenüber<br />

<strong>de</strong>m Bus nicht än<strong>de</strong>rn, da die gleiche Anzahl an Schritten wie bei <strong>de</strong>r Verwendung<br />

<strong>de</strong>s Busses benötigt wer<strong>de</strong>n. Der einzige Unterschied besteht darin, dass auf<br />

<strong>de</strong>n Bus etwas geschrieben wer<strong>de</strong>n muss, während die Bus-Zelle sich aktiv <strong>de</strong>n ak<strong>tu</strong>ellen<br />

Wert holt.<br />

Je nach Aufgabenstellung und Aufbau kann eine <strong>de</strong>r bei<strong>de</strong>n Lösungen Vorteile gegenüber<br />

<strong>de</strong>r an<strong>de</strong>ren haben. Die Verwendung <strong>de</strong>r Bus-Zelle abstrahiert das Verbindungsnetzwerk<br />

und passt somit even<strong>tu</strong>ell besser in das Mo<strong>de</strong>ll <strong>de</strong>s GCA. Im Prinzip wur<strong>de</strong>n die gleichen<br />

Überlegungen wie in Kapitel 3.1.3.4.1 angestellt, bloß dass hier versucht wird, <strong>de</strong>n Bus<br />

einzusparen, ohne dabei mehr Lei<strong>tu</strong>ngen zu benötigen.<br />

Die Einordnung <strong>de</strong>s Algorithmus in das in Kapitel 2.3.4 vorgestellte Schema bedarf in<br />

<strong>de</strong>m Fall <strong>de</strong>s Kruskals (Heap-Sort wur<strong>de</strong> bereits getrennt betrachtet) nur noch einer<br />

Betrach<strong>tu</strong>ng <strong>de</strong>r Daten. Die Verbindungen sind in <strong>de</strong>r Lösung statisch und somit ist für<br />

diese die Funktion p’ = g().<br />

Die Daten <strong>de</strong>r einzelnen Zellen sind sowohl von <strong>de</strong>n Daten <strong>de</strong>r eigenen Zelle als auch von<br />

<strong>de</strong>n Daten <strong>de</strong>r Nachbar-Zellen abhängig. So ist die Kruskal-Zelle auf die oberste Zelle<br />

<strong>de</strong>s Heaps angewiesen, dass sie von dort die Daten erhält. Gleichzeitig benötigt sie auch<br />

die Daten <strong>de</strong>r Knoten-Zellen um festzustellen, ob zwei Wäl<strong>de</strong>r verbun<strong>de</strong>n wer<strong>de</strong>n o<strong>de</strong>r<br />

ein Zyklus entsteht.<br />

Von <strong>de</strong>r eigenen Position o<strong>de</strong>r <strong>de</strong>r Zeit sind die Daten nicht abhängig. Auch von <strong>de</strong>n<br />

Verbindungen an sich sind die Daten nicht abhängig.<br />

Es ergibt sich also als Funktion für die Daten: d’ = f(d,d*).<br />

3.2.2. Modifikation <strong>de</strong>s Hirschberg für minimal aufspannen<strong>de</strong><br />

Bäume<br />

Der Hirschberg bestimmt die zusammenhängen<strong>de</strong>n Komponenten für einen Graphen, in<strong>de</strong>m<br />

er die Knoten als eine Menge von Wäl<strong>de</strong>rn auffasst und die Kanten sucht, die Wäl<strong>de</strong>r<br />

verbin<strong>de</strong>n. Der Algorithmus leistet also im Grun<strong>de</strong> schon das Gefor<strong>de</strong>rte. Allerdings wer<strong>de</strong>n<br />

momentan noch nicht die verwen<strong>de</strong>ten Kanten gespeichert und auch die Auswahl<br />

68


geschieht noch nicht nach <strong>de</strong>r minimalen Kantenwer<strong>tu</strong>ng. Um dies zu ermöglichen, muss<br />

einerseits <strong>de</strong>r Matrix erlaubt wer<strong>de</strong>n, auch Werte größer als eins zu speichern 12 . An<strong>de</strong>rerseits<br />

muss dafür gesorgt wer<strong>de</strong>n, dass die verwen<strong>de</strong>ten Kanten gespeichert wer<strong>de</strong>n.<br />

Um die Kanten zu speichern, muss man zuerst betrachten, nach welchem Schritt die<br />

Kanten feststehen und wieviele und welche Informationen bis dahin verloren gegangen<br />

sind.<br />

Als erstes stellt man fest, dass nach <strong>de</strong>m zweiten Teilschritt die Wahl <strong>de</strong>r Kanten abgeschlossen<br />

ist. Die darauf folgen<strong>de</strong>n Teilschritte drei bis fünf dienen dazu, die Komponenten<br />

zusammenzufassen. Nach <strong>de</strong>m zweiten Teilschritt können also die Kanten in die<br />

Kantenliste <strong>de</strong>s minimalen aufspannen<strong>de</strong>n Baum aufgenommen wer<strong>de</strong>n. Um diese Kanten<br />

korrekt aufzunehmen, muss nun noch betrachtet wer<strong>de</strong>n, welche Informationen im<br />

Original-Hirschberg-Algorithmus in <strong>de</strong>n ersten bei<strong>de</strong>n Teilschritten verloren gehen. Falls<br />

diese Informationen für die minimal aufspannen<strong>de</strong>n Bäume wichtig sind, so müssen sie<br />

geeignet gesichert wer<strong>de</strong>n 13 . Anzumerken ist hier, dass <strong>de</strong>r Algorithmus, falls <strong>de</strong>r Graph<br />

nicht zusammenhängt, keinen minimalen aufspannen<strong>de</strong>n Baum, son<strong>de</strong>rn einen Wald von<br />

minimal aufspannen<strong>de</strong>n Bäumen fin<strong>de</strong>t. Eine offensichtliche Verän<strong>de</strong>rung ist, dass nun<br />

nicht mehr die Kante zur Komponente mit <strong>de</strong>r geringsten Nummer gesucht wird, son<strong>de</strong>rn<br />

die Kante zu einer benachbarten Komponente, welche die geringste Kantenwer<strong>tu</strong>ng<br />

hat.<br />

Im ersten Teilschritt wählt je<strong>de</strong>r Knoten eine Kante zu einer benachbarten Komponente<br />

und speichert die Nummer <strong>de</strong>r Komponente ab. Der Knoten, zu <strong>de</strong>m die Kante führt,<br />

geht dabei verloren. Um später auf <strong>de</strong>n Ziel-Knoten <strong>de</strong>r Kante zugreifen zu können,<br />

muss dieser abgespeichert wer<strong>de</strong>n. Somit steht <strong>de</strong>r Ziel-Knoten beim zweiten Teilschritt<br />

als Information zur Verfügung. Auch wenn diese Information nur für die Kanten wichtig<br />

ist, ist sie essentiell, da ohne die korrekten Kanten kein minimal aufspannen<strong>de</strong>r Baum<br />

gebil<strong>de</strong>t wer<strong>de</strong>n kann.<br />

Im zweiten Teilschritt wird nun für je<strong>de</strong> Komponente die Kante mit <strong>de</strong>r geringsten<br />

Wer<strong>tu</strong>ng gesucht und für die weiteren Teilschritte verwen<strong>de</strong>t. Die verwen<strong>de</strong>te Kante<br />

wird zum Repräsentanten <strong>de</strong>r Komponente umgebogen. In diesem Schritt geht also die<br />

Information verloren, von welchem Knoten die Kante ursprünglich ausging. Auch diese<br />

Information muss gespeichert wer<strong>de</strong>n.<br />

Damit sind alle nötigen Informationen gesichert, es bleibt nur noch das Problem, dass<br />

beim zweiten Schritt pro neuer Komponente auch ein Zyklus <strong>de</strong>r Länge zwei entsteht,<br />

welcher natürlich nicht in <strong>de</strong>n minimal aufspannen<strong>de</strong>n Baum aufgenommen wer<strong>de</strong>n darf.<br />

Dieser Zyklus muss erkannt wer<strong>de</strong>n und die diesen Zyklus auslösen<strong>de</strong> Kante 14 darf nur<br />

12 Um Konflikte zu vermei<strong>de</strong>n, wird hier davon ausgegangen, dass alle Werte positiv sind. Da in <strong>de</strong>n<br />

meisten Fällen minimale Kosten für irgendwelche Aktionen gesucht wer<strong>de</strong>n und die Kosten dabei<br />

nicht negativ wer<strong>de</strong>n können, ist dies eine gerechtfertigte Annahme.<br />

13 In [GR98] wird die Theorie hinter <strong>de</strong>n Modifikationen erklärt, aber welche Modifikationen explizit<br />

wirklich nötig sind, wird nicht erwähnt. Die hier vorgestellten Aktionen wer<strong>de</strong>n dort unter ”<br />

housekeeping“<br />

zusammengefasst.<br />

14 Der Zyklus entsteht dadurch, dass zwei Komponenten die gleiche Kante wählen und sie somit im<br />

gerichteten Graphen <strong>de</strong>s ersten Teilschritts in je<strong>de</strong> Rich<strong>tu</strong>ng einmal verwen<strong>de</strong>t wird.<br />

69


einmal aufgenommen wer<strong>de</strong>n. Um diesen Zyklus zu fin<strong>de</strong>n, kann man die bereits gespeicherten<br />

Informationen nutzen. So ist <strong>de</strong>r Nachfolger <strong>de</strong>s Nachfolgers eines Knotens dieses<br />

Zykluses immer wie<strong>de</strong>r <strong>de</strong>r Knoten selber. Es gilt also für je<strong>de</strong>n Knoten i eines solchen<br />

Zyklus, dass T[T[i]]=i ist. Natürlich kann man argumentieren, dass es mehr als eine minimale<br />

Kante zwischen <strong>de</strong>n bei<strong>de</strong>n Komponenten geben kann. In diesem Fall wäre nicht<br />

sicher, dass bei<strong>de</strong> Komponenten wirklich die gleiche Kante gewählt haben. Allerdings<br />

darf auch in diesem Fall nur eine von bei<strong>de</strong>n Kanten genutzt wer<strong>de</strong>n, da sonst ein Zyklus<br />

entsteht und <strong>de</strong>r entstehen<strong>de</strong> Graph kein Baum mehr ist. Da bei<strong>de</strong> Kanten minimal<br />

sind, ist es egal, welche Kante weiter verwen<strong>de</strong>t und welche verworfen wird. An dieser<br />

Stelle entsteht ein In<strong>de</strong>terminismus, <strong>de</strong>r sich aber in einer konkreten Implementierung<br />

aufgrund <strong>de</strong>r dort verwen<strong>de</strong>ten Auswahlkriterien nicht mehr auftritt.<br />

Im Weiterem wird erst ein Beispiel für die Berechnung von minimalen aufspannen<strong>de</strong>n<br />

Bäumen mit Hilfe <strong>de</strong>s Hirschberg-Algorithmus gegeben und dann wird gezeigt, dass die<br />

nötigen Informationen auch auf <strong>de</strong>m GCA gespeichert und verwen<strong>de</strong>t wer<strong>de</strong>n können.<br />

Beispiel Für dieses Beispiel wird das Beispiel aus Kapitel 3.1.3.1 dahingehend geän<strong>de</strong>rt,<br />

dass es einerseits gewichtet ist (d. h. je<strong>de</strong> Kante hat zusätzlich ein Gewicht, welches die<br />

Kosten ausdrückt) und an<strong>de</strong>rerseits noch Kanten hinzugenommen wer<strong>de</strong>n, damit <strong>de</strong>r<br />

Graph nicht schon an sich ein minimal aufspannen<strong>de</strong>r Baum ist. Als Beispiel soll also<br />

<strong>de</strong>r von <strong>de</strong>r Matrix<br />

⎛<br />

C =<br />

⎜<br />

⎝<br />

0 3 0 1 5 0 0 0<br />

3 0 1 2 0 0 2 0<br />

0 1 0 0 1 1 2 0<br />

1 2 0 0 0 0 0 1<br />

5 0 1 0 0 0 0 0<br />

0 0 1 0 0 0 0 0<br />

0 2 2 0 0 0 0 2<br />

0 0 0 1 0 0 2 0<br />

beschriebene Graph dienen. Gezeichnet gestaltet sich <strong>de</strong>r Graph wie in Abbildung 3.38.<br />

⎞<br />

⎟<br />

⎠<br />

Da <strong>de</strong>r Ablauf <strong>de</strong>s Algorithmus von Hirschberg bereits früher in dieser Arbeit erklärt<br />

wur<strong>de</strong>, beschränke ich mich hier auf die Erklärung und Abbildung <strong>de</strong>s ersten und zweiten<br />

Teilschritts je<strong>de</strong>s Durchlaufs. Diese bei<strong>de</strong>n Teilschritte sind immerhin für die Bestimmung<br />

<strong>de</strong>s minimalen aufspannen<strong>de</strong>n Baums ausschlaggebend.<br />

Abbildung 3.39 repräsentiert <strong>de</strong>n Graphen nach <strong>de</strong>m Ablauf <strong>de</strong>s ersten Schritts. Abweichend<br />

von <strong>de</strong>n Abbildungen in Kapitel 3.1.3.1 wer<strong>de</strong>n hier alle Kanten beibehalten<br />

und die von <strong>de</strong>n Knoten gewählten Kanten wer<strong>de</strong>n dadurch gekennzeichnet, dass sie<br />

gestrichelt und gerichtet dargestellt wer<strong>de</strong>n. Da in diesem Schritt je<strong>de</strong>r Knoten noch<br />

eine Komponente darstellt, wer<strong>de</strong>n diese Kanten alle genommen. Die doppelt genutzten<br />

Kanten von 0 nach 3 und von 1 nach 2 wer<strong>de</strong>n nur einmal in <strong>de</strong>n resultieren<strong>de</strong>n Graphen<br />

aufgenommen. Nach <strong>de</strong>m zweiten Schritt ergibt sich also <strong>de</strong>r Graph aus Abbildung 3.40.<br />

70


Abbildung 3.38.: Dieser ungerichtete Graph verfügt sowohl über Zyklen als auch über unterschiedliche<br />

Kantenwer<strong>tu</strong>ngen. Anhand dieses Graphen soll im weiteren<br />

<strong>de</strong>r Algorithmus ver<strong>de</strong>utlicht wer<strong>de</strong>n.<br />

Abbildung 3.39.: Je<strong>de</strong>r Knoten hat sich die Kante mit <strong>de</strong>r minimalen Kantenwer<strong>tu</strong>ng<br />

ausgewählt. Wenn mehrere solcher Kanten existieren, wird die zum<br />

Knoten mit <strong>de</strong>r kleinsten Nummer gewählt.<br />

Die in <strong>de</strong>n minimalen Spannbaum aufgenommenen Kanten sind dick und ungerichtet<br />

dargestellt. In <strong>de</strong>n nachfolgen<strong>de</strong>n Teilschritten wer<strong>de</strong>n nun die Knoten 0, 3 und 7 sowie<br />

die Knoten 1, 2, 4, 5 und 6 zu Komponenten zusammengefasst und für <strong>de</strong>n Algorithmus<br />

auch entsprechend gekennzeichnet.<br />

Im ersten Teilschritt <strong>de</strong>s zweiten Durchgangs wird dann von je<strong>de</strong>m Knoten eine Kante<br />

mit minimaler Kantenwer<strong>tu</strong>ng zu <strong>de</strong>r an<strong>de</strong>ren Komponente gesucht. Es entsteht <strong>de</strong>r<br />

Graph aus Abbildung 3.41. Die im vorhergehen<strong>de</strong>n Durchgang gefun<strong>de</strong>nen Baumkanten<br />

sind weiterhin dick, die neu gewählten Kanten sind wie<strong>de</strong>r gestrichelt dargestellt.<br />

Nun sucht je<strong>de</strong> Komponente die von ihr ausgehen<strong>de</strong> Kante mit <strong>de</strong>r minimalen Wer<strong>tu</strong>ng.<br />

Bei diesem Beispiel könnte es zu <strong>de</strong>m weiter vorne beschriebenen Problem kommen, da es<br />

<strong>de</strong>nkbar ist, dass die erste Komponente die Kante (3,1) und die zweite Komponente die<br />

71


Abbildung 3.40.: Nach <strong>de</strong>m zweiten Teilschritt sind die Kanten bereits in <strong>de</strong>n minimalen<br />

Spannbaum aufgenommen. Dabei wur<strong>de</strong> darauf geachtet, dass doppelt<br />

benutzte Kanten nur einfach aufgenommen wer<strong>de</strong>n. Die Kanten, die<br />

schon sicher im minimal aufspannen<strong>de</strong>n Baum enthalten sind, sind<br />

hier durch dicke Linien gekennzeichnet.<br />

Abbildung 3.41.: Nach <strong>de</strong>m ersten Teilschritt <strong>de</strong>s zweiten Durchlaufs sind wie<strong>de</strong>r von je<strong>de</strong>m<br />

Knoten eine Kante zu einer benachbarten Komponente ausgewählt<br />

wor<strong>de</strong>n. Da Knoten 5 und 2 über keine Verbindung zu <strong>de</strong>r an<strong>de</strong>ren<br />

Komponente verfügen, wählen sie sich keine Kante. Die bereits im minimal<br />

aufspannen<strong>de</strong>n Baum enthaltenen Kanten sind dick dargestellt,<br />

die neu gewählten Kanten wie<strong>de</strong>r gestrichelt und gerichtet.<br />

Kante(6,7) wählt. In <strong>de</strong>r praktischen Realisierung ist dies jedoch eher unwahrscheinlich,<br />

nimmt man doch in <strong>de</strong>r Regel immer <strong>de</strong>n ersten Wert, <strong>de</strong>n man fin<strong>de</strong>t, außer es lässt<br />

sich ein kleinerer fin<strong>de</strong>n. Dem folgend gehen wir auch hier davon aus, dass die Kante<br />

gewählt wird, die von <strong>de</strong>m kleineren Knoten ausgeht. Abbildung 3.42 repräsentiert <strong>de</strong>n<br />

Graphen nach <strong>de</strong>m Ablauf <strong>de</strong>s zweiten Teilschritts <strong>de</strong>s zweiten Durchlaufs. Auch hier<br />

sind wie<strong>de</strong>r alle Baumkanten dick dargestellt und es ist ersichtlich, dass <strong>de</strong>r minimale<br />

72


aufspannen<strong>de</strong> Baum gefun<strong>de</strong>n wur<strong>de</strong>. Da <strong>de</strong>r Graph am En<strong>de</strong> <strong>de</strong>s zweiten Durchlaufs<br />

auch nur noch über eine Komponente verfügt, wird sich im dritten und finalen Durchlauf<br />

nichts mehr än<strong>de</strong>rn. Es wer<strong>de</strong>n also keine Kanten mehr in <strong>de</strong>n minimalen aufspannen<strong>de</strong>n<br />

Baum aufgenommen und Abbildung 3.42 repräsentiert das En<strong>de</strong>rgbnis <strong>de</strong>s Algorithmus,<br />

wobei nur die dicken Kanten die Ausgabe darstellen.<br />

Abbildung 3.42.: Dieser Graph repräsentiert nicht nur das Teilergebnis nach <strong>de</strong>m zweiten<br />

Schritt <strong>de</strong>s zweiten Durchlaufs, son<strong>de</strong>rn er stellt auch gleichzeitig<br />

das En<strong>de</strong>rgebnis dar. Alle dick dargestellten Kanten gehören zum minimal<br />

aufspannen<strong>de</strong>n Baum und wer<strong>de</strong>n als Ergebnis zurückgegeben.<br />

3.2.2.1. Realisierung auf <strong>de</strong>m GCA<br />

Der erste Schritt zur Modifikation ist die Eingabe eines gewichteten Graphen, was aber<br />

kein Problem darstellt, da man nur innerhalb <strong>de</strong>r Matrix auch Werte größer eins erlauben<br />

muss. Der GCA unterliegt in dieser Rich<strong>tu</strong>ng keinerlei Beschränkung und diese<br />

Modifikation kann einfach übernommen wer<strong>de</strong>n. Je<strong>de</strong> Zelle (außer <strong>de</strong>r Speicherzelle)<br />

beinhaltet weiterhin eine Zeile <strong>de</strong>r Matrix. Eine weitere Modifikation ist, dass nicht<br />

mehr nach <strong>de</strong>r Kante zu <strong>de</strong>m Nachbarn mit <strong>de</strong>m kleinsten Knoten, son<strong>de</strong>rn nach <strong>de</strong>r<br />

Kante mit <strong>de</strong>r kleinsten Wer<strong>tu</strong>ng zur an<strong>de</strong>ren Komponente gesucht wird. Dies ist eine<br />

algorithmische Än<strong>de</strong>rung, die keine Verän<strong>de</strong>rungen im Aufbau <strong>de</strong>s GCA, son<strong>de</strong>rn nur<br />

im darauf ablaufen<strong>de</strong>n Programm, bedingt.<br />

Des weiteren muss im ersten Schritt gespeichert wer<strong>de</strong>n, wohin die gewählte Kante führt.<br />

Dieser Wert kann ohne Anfrage bei <strong>de</strong>r Speicherzelle gefun<strong>de</strong>n wer<strong>de</strong>n, da einfach die<br />

Stelle <strong>de</strong>s gewählten Werts in <strong>de</strong>r Zeile gespeichert wer<strong>de</strong>n muss. Im vorher vorgestellten<br />

Beispiel wird dabei von 0 beginnend gezählt (also 0,1,...,i-1). Zusätzlich sollte gespeichert<br />

wer<strong>de</strong>n, welche Kosten die gewählte Kante hat. Um im zweiten Schritt auf diese Werte<br />

zugreifen zu können, wer<strong>de</strong>n sie mit in die Speicherzelle übernommen.<br />

Auch im zweiten Schritt darf nicht mehr nach <strong>de</strong>r kleinsten Knotennummer ausgewählt<br />

wer<strong>de</strong>n, son<strong>de</strong>rn es muss eine Selektion nach <strong>de</strong>r kleinsten Kantenwer<strong>tu</strong>ng geschehen.<br />

73


Dabei muss wie<strong>de</strong>r mitgespeichert wer<strong>de</strong>n, welcher Knoten eigentlich die Kante ausgewählt<br />

hat. Ist dies geschehen, so kann die Kante in <strong>de</strong>n minimal aufspannen<strong>de</strong>n Baum<br />

aufgenommen wer<strong>de</strong>n. Stellt man sich nun vor, dass die Speicherzelle am En<strong>de</strong> <strong>de</strong>n minimal<br />

aufspannen<strong>de</strong>n Baum enthält, so erscheint es sinnvoll, dort die Kanten entwe<strong>de</strong>r<br />

als Tripel (i,j,k) mit i = Startknoten, j = Zielknoten und k = Kantenwer<strong>tu</strong>ng o<strong>de</strong>r als<br />

Matrix zu speichern. Für <strong>de</strong>n Fall, dass die Tripel verwen<strong>de</strong>t wer<strong>de</strong>n, sollte das Ergebnis<br />

als Menge von ungerichteten Kanten interpretiert wer<strong>de</strong>n. Eine Zelle kann nun mit <strong>de</strong>m<br />

Wissen, welcher Knoten die Kante ausgewählt hat, bei <strong>de</strong>r Speicherzelle sowohl das Ziel<br />

<strong>de</strong>r Kante als auch <strong>de</strong>ren Wert abfragen. Anschließend kann das Ergebnis von <strong>de</strong>r Speicherzelle<br />

ausgelesen und geeignet abgespeichert wer<strong>de</strong>n. Bei diesem Algorithmus wird<br />

beibehalten, dass <strong>de</strong>r Knoten mit <strong>de</strong>r kleinsten Nummer <strong>de</strong>r Repräsentant <strong>de</strong>r Gruppe<br />

ist. Die Kante, die von <strong>de</strong>m Repräsentanten <strong>de</strong>r neu entstan<strong>de</strong>nen Komponente ausgeht,<br />

muss nun noch entfernt wer<strong>de</strong>n, damit keine Zyklen entstehen. Dieser Schritt erspart<br />

die Zyklen<strong>de</strong>tektion, bewirkt aber, dass die Kanten erst nach <strong>de</strong>m fünften Teilschritt<br />

endgültig feststehen. Um zu sehen, dass dieses Vorgehen auch zum Erfolg führt, muss<br />

man sich ver<strong>de</strong>utlichen, dass am En<strong>de</strong> <strong>de</strong>s zweiten Schritts immer <strong>de</strong>r neue Repräsentant<br />

<strong>de</strong>r nach <strong>de</strong>m fünften Schritt entstehen<strong>de</strong>n Komponente in <strong>de</strong>n Zyklus involviert ist.<br />

Die Modifikationen, die am Algorithmus vorgenommen wur<strong>de</strong>n, än<strong>de</strong>rn we<strong>de</strong>r etwas an<br />

<strong>de</strong>r Laufzeitkomplexität noch an <strong>de</strong>r Anzahl <strong>de</strong>r benötigten Zellen. Es ist also möglich,<br />

<strong>de</strong>n minimal aufspannen<strong>de</strong>n Baum eines ungerichteten, gewerteten Graphen in O(log(n) 2 )<br />

mit O(<br />

n 2<br />

log(n) 2 ) Zellen fin<strong>de</strong>n.<br />

Auch bei <strong>de</strong>m modifizierten Algorithmus von Hirschberg än<strong>de</strong>rt sich (wie beim Floyd-<br />

Warshall-Algorithmus gegenüber <strong>de</strong>m Warshall-Algorithmus) nichts an <strong>de</strong>r Einordnung<br />

in das Schema aus Kapitel 2.3.4. Die Abhängigkeit <strong>de</strong>r Funktionen lässt sich also weiterhin<br />

ausdrücken mit: d’ = f(d,d*) und p’ = g().<br />

3.2.3. Derzeitiger Forschungsstand<br />

Momentan gibt es zwei <strong>Algorithmen</strong>, welche die benötigte Laufzeitkomplexität zum Auffin<strong>de</strong>n<br />

<strong>de</strong>s minimalen aufspannen<strong>de</strong>n Baum noch weiter verbessern. Dabei ist anzumerken,<br />

dass alle vorgestellten <strong>Algorithmen</strong> auf einer EREW P-RAM laufen und <strong>de</strong>shalb<br />

größeren Restriktionen als auf einer CRCW P-RAM unterliegen. Da das EREW und<br />

das CREW Prinzip eher die Möglichkeiten <strong>de</strong>s GCA wi<strong>de</strong>rspiegeln, wird hier darauf<br />

verzichtet, <strong>Algorithmen</strong> auf <strong>de</strong>r CRCW P-RAM vorzustellen.<br />

Donald Johnson und Panagiotis Metaxas stellen in [JM92] einen Algorithmus vor, <strong>de</strong>r<br />

mit einer Laufzeit von O(log(|E|) 3 2 ) bei einer Verwendung von |E|+|V | Prozessoren auskommt.<br />

Dieser Algorithmus wird als bahnbrechend betrachtet, da vorher die Meinung<br />

vertreten wur<strong>de</strong>, dass <strong>de</strong>r minimale aufspannen<strong>de</strong> Baum auf einer EREW P-RAM in<br />

schnellstens O(log(|E|) 2 ) gefun<strong>de</strong>n wer<strong>de</strong>n kann. Mit <strong>de</strong>m Algorithmus wur<strong>de</strong> das Gegenteil<br />

bewiesen und Ka Wong Chong, Yijie Han und Tak Wah Lam schafften es sogar<br />

in [CHL99] einen Algorithmus anzugeben, <strong>de</strong>r in O(log(|E|)) das Ergebnis fin<strong>de</strong>t und<br />

dabei nur O(|E| + |V |) Prozessoren verwen<strong>de</strong>t.<br />

74


Bei bei<strong>de</strong>n <strong>Algorithmen</strong> ist die Angabe <strong>de</strong>r Laufzeit kritisch zu betrachten, da die Autoren<br />

mehr Sorgfalt darauf verwen<strong>de</strong>t haben, die Korrektheit <strong>de</strong>r <strong>Algorithmen</strong> als die<br />

Korrektheit <strong>de</strong>r Laufzeit zu beweisen 15 . Davon abgesehen, bieten die <strong>Algorithmen</strong> gute<br />

Ansätze zum Bestimmen <strong>de</strong>s minimal aufspannen<strong>de</strong>n Baums, weshalb sie kurz in ihrer<br />

Funktionalität vorgestellt wer<strong>de</strong>n sollen.<br />

Der Algorithmus von Johnson und Metaxas startet wie <strong>de</strong>r Kruskal und <strong>de</strong>r Hirschberg<br />

mit einer Menge von Wäl<strong>de</strong>rn, die jeweils aus genau einem Knoten bestehen. Im<br />

Gegensatz zum Hirschberg arbeitet <strong>de</strong>r Algorithmus allerdings nicht mit einer Adjazenzmatrix,<br />

son<strong>de</strong>rn mit Adjazenzlisten, d. h. je<strong>de</strong>r Knoten kennt explizit seine Nachfolger.<br />

Damit entfällt die Suche nach Nachbarn. Entwe<strong>de</strong>r ist die Liste leer, dann existiert kein<br />

Nachbar√mehr, o<strong>de</strong>r man entnimmt das erste Element <strong>de</strong>r Liste. Der Algorithmus setzt<br />

sich aus log(n) Phasen zusammen. Je<strong>de</strong> Phase fasst dabei Wäl<strong>de</strong>r weiter zusammen, so<br />

√<br />

log(n)∗(i+1)<br />

dass nach Beendigung <strong>de</strong>r i-ten Phase je<strong>de</strong>r Wald, falls möglich, min<strong>de</strong>stens 2<br />

Knoten beinhaltet. Beim Zusammenfügen von zwei Wäl<strong>de</strong>rn wer<strong>de</strong>n zwei wichtige Operationen<br />

durchgeführt:<br />

• Die Adjazenzlisten <strong>de</strong>r bei<strong>de</strong>n zusammengefassten Wäl<strong>de</strong>r wer<strong>de</strong>n zusammengefasst.<br />

Verwen<strong>de</strong>t man hierfür Mergesort 16 , so erhält man am En<strong>de</strong> wie<strong>de</strong>r eine<br />

sortierte Adjazenzliste.<br />

• Je<strong>de</strong> Kante (u,v) innerhalb <strong>de</strong>r Adjazenzliste wird umbenannt in (p(u),p(v)), wobei<br />

p(u) und p(v) <strong>de</strong>n Repräsentanten <strong>de</strong>r Gruppen von u bzw. v bezeichnet. Interne<br />

Kanten (gleicher Start- und Endknoten) wer<strong>de</strong>n entfernt.<br />

Aufgrund dieser Verwal<strong>tu</strong>ngsarbeiten gelingt es, dass <strong>de</strong>r Algorithmus schneller abläuft<br />

als die bis dahin bekannten <strong>Algorithmen</strong> auf <strong>de</strong>r EREW P-RAM.<br />

Die Autoren von [CHL99] erweitern diesen Algorithmus noch in <strong>de</strong>r Weise, dass die<br />

Phasen parallel ablaufen. Dazu startet Phase i, sobald Phase ⌈ i ⌉ abgeschlossen ist und<br />

2<br />

beginnt, schon einmal eine Vorauswahl zu treffen. Je<strong>de</strong> Phase, die danach been<strong>de</strong>t wird,<br />

sorgt dafür, dass diese Vorauswahl verfeinert und an das En<strong>de</strong>rgebnis angenähert wer<strong>de</strong>n<br />

kann. Mit Hilfe dieser Parallelisierung schaffen es die Autoren, dass <strong>de</strong>r Algorithmus<br />

einen minimal aufspannen<strong>de</strong>n Baum in O(⌊log(n)⌋) bestimmt.<br />

3.3. NP-vollständige Probleme<br />

In die Klasse NP fallen diejenigen Probleme, für die man zwar auf einer nicht<strong>de</strong>terministischen<br />

Turingmaschine einen Lösungsalgorithmus in polynomieller Zeit gefun<strong>de</strong>n<br />

15 Die Autoren schätzen z. B. an einer Stelle <strong>de</strong>n Aufwand einer Aktion in einem Baum mit n Knoten<br />

und m Kanten mit O(log(m)) ab und fassen dies dann in O(log(n)) zusammen. Diese Aussage ist<br />

richtig, wenn man betrachtet, dass ein Graph mit n Knoten maximal n 2 Kanten hat, allerdings<br />

wer<strong>de</strong>n die Aussagen dadurch schwerer nachvollziehbar.<br />

16 Mergesort kann in O(log(n)) Zeit mit n Prozessoren zwei sortierte Listen zu einer sortierten Liste<br />

zusammenfügen.<br />

75


hat, <strong>de</strong>ren beste bislang gefun<strong>de</strong>ne Lösung auf einer <strong>de</strong>terministischen Turingmaschine<br />

jedoch exponentiell viel Zeit benötigt. Die Klasse P umfasst die <strong>Algorithmen</strong>, die auf<br />

einer <strong>de</strong>terministischen Turingmaschine in polynomieller Zeit laufen. Die Frage, ob NP<br />

= P gilt, ist zur Zeit noch ungeklärt, es wird aber vermutet, dass diese Gleichung nicht<br />

gilt. Die Theorie zu diesen Klassen ist in vielen (Lehr-) Büchern zu fin<strong>de</strong>n, u. a. auch in<br />

[Sch01].<br />

Die Klasse <strong>de</strong>r NP-vollständigen Probleme ist eine Unterklasse <strong>de</strong>r Klasse NP. Dabei<br />

lässt sich je<strong>de</strong>s NP-vollständige Problem auf je<strong>de</strong>s an<strong>de</strong>re NP-vollständige Problem reduzieren.<br />

Mit an<strong>de</strong>ren Worten, fin<strong>de</strong>t man eine effiziente Lösung für eines <strong>de</strong>r Probleme<br />

dieser Klasse, so kann man auch alle an<strong>de</strong>ren Probleme effizient lösen.<br />

Auch in <strong>de</strong>r Graphentheorie existieren einige NP-vollständige Probleme, z. B. das Graphenfärbbarkeitsproblem,<br />

das ”<br />

Travelling-Salesman“-Problem und die Bestimmung <strong>de</strong>r<br />

maximalen Clique eines Graphen. In <strong>de</strong>r Regel lassen sich solche NP-vollständige Probleme<br />

nur durch Backtracking 17 lösen und sind somit bezüglich <strong>de</strong>r Laufzeit ineffizient.<br />

Auch die Möglichkeiten, die parallele Automaten und Rechenmo<strong>de</strong>lle bieten, än<strong>de</strong>rn<br />

wenig an dieser Problematik. Trotz<strong>de</strong>m gibt es einige Unterklassen <strong>de</strong>r Probleme, für<br />

die es effiziente Lösungen gibt. Für an<strong>de</strong>re Probleme existieren Approximationsalgorithmen,<br />

die zwar kein optimales Ergebnis liefern, aber <strong>de</strong>m optimalen Ergebnis sehr nahe<br />

kommen.<br />

Die Möglichkeiten, für einige Spezialfälle eine effiziente Lösung zu fin<strong>de</strong>n, wird im Folgen<strong>de</strong>n<br />

exemplarisch am Beispiel <strong>de</strong>s Graphenfärbbarkeitsproblem erläutert.<br />

3.3.1. Das Graphenfärbbarkeitsproblem<br />

Das Graphenfärbbarkeitsproblem besteht darin, dass man versucht, einen Graphen mit<br />

einer minimalen Anzahl an Farben einzufärben. Dabei kann man entwe<strong>de</strong>r die Knoten<br />

o<strong>de</strong>r die Kanten färben, wobei darauf zu achten ist, dass keine benachbarten Knoten<br />

(Kanten) die gleiche Farbe haben.<br />

Für allgemeine Graphen ist dieses Problem nur mittels Backtracking zu lösen, was zu<br />

einer exponentiellen Laufzeit führt. Das Backtracking-Verfahren ist in Kapitel A.1 <strong>de</strong>s<br />

Anhangs beschrieben.<br />

Allerdings existieren Graphenklassen, für die sich das Graphenfärbungsproblem effizient<br />

lösen lassen, dazu gehören u. a. die bipartiten, outerplanaren und Halin-Graphen.<br />

Bipartite Graphen sind solche Graphen, <strong>de</strong>ren Knotenmenge V sich <strong>de</strong>rart in zwei Mengen<br />

zerlegen lässt, dass V 1 ∪ V 2 = V und V 1 ∩ V 2 = ∅ gilt und es zu<strong>de</strong>m nur Kanten<br />

gibt, die V 1 und V 2 verbin<strong>de</strong>n. Das heißt es existieren keine Kanten, die von V 1 nach V 1<br />

o<strong>de</strong>r von V 2 nach V 2 gehen. Diese Graphen sind bezüglich <strong>de</strong>r Knoten zweifärbar (alle<br />

17 Backtracking beschreibt eine struk<strong>tu</strong>rierte Art und Weise, Probleme per Austesten zu lösen und<br />

falsche Annahmen wie<strong>de</strong>r zurückzunehmen. Da dieses Verfahren im worst-case alle Möglichkeiten<br />

austestet, kann es keine effiziente Laufzeit haben. Eine Beschreibung <strong>de</strong>s Verfahrens ist in Kapitel<br />

A.1 zu fin<strong>de</strong>n.<br />

76


Knoten aus V 1 wer<strong>de</strong>n in einer gemeinsamen Farbe gefärbt und alle Knoten aus V 2 in<br />

einer an<strong>de</strong>ren Farbe), weshalb die Betrach<strong>tu</strong>ng <strong>de</strong>r Kanten hier interessanter ist. Die<br />

Definition von bipartiten Graphen wird in Abbildung 3.43 ver<strong>de</strong>utlicht. Abbildungsteil<br />

a) zeigt einen bipartiten Graph, in b) ist nur eine Kante zusätzlich vorhan<strong>de</strong>n, <strong>de</strong>nnoch<br />

ist es kein bipartiter Graph mehr.<br />

Abbildung 3.43.: Der Graph in a) ist bipartit, da seine Knotenmenge in zwei Teilmengen<br />

zerlegen lässt, so dass alle Kanten eine Verbindung zwischen diesen<br />

Mengen schaffen und keine Kanten zwei Knoten innerhalb einer Menge<br />

verbin<strong>de</strong>n. Aus diesem Grund ist <strong>de</strong>r Graph aus b) nicht bipartit, seine<br />

Knotenmenge lässt sich nicht in zwei Teilengen mit dieser Eigenschaft<br />

zerlegen.<br />

Outerplanare Graphen sind planare Graphen, bei <strong>de</strong>nen alle Knoten an einer gemeinsamen<br />

Fläche liegen. Planare Graphen lassen sich so darstellen, dass keine Kantenüberschneidungen<br />

existieren. Ohne Einschränkung <strong>de</strong>r Allgemeinheit wird angenommen, dass<br />

die gemeinsame Fläche <strong>de</strong>r outerplanaren Kanten die äußere Fläche ist 18 . Ein outerplanarer<br />

Graph ist in Abbildung 3.44 gegeben.<br />

Abbildung 3.44.: Der angegebene Graph ist planar (es existieren keine Kantenüberschneidungen)<br />

und alle Knoten liegen an <strong>de</strong>r Außenfläche. Damit ist<br />

<strong>de</strong>r Graph outerplanar.<br />

Halin-Graphen sind planare Graphen mit <strong>de</strong>r folgen<strong>de</strong>n Eigenschaft: Der Graph setzt sich<br />

aus einem Baum zusammen, <strong>de</strong>r keine Knoten mit <strong>de</strong>m Grad zwei enthält. Es existiert<br />

zu<strong>de</strong>m ein Zyklus, <strong>de</strong>r alle Blätter <strong>de</strong>s Baums und keine inneren Knoten beinhaltet. Ein<br />

möglicher Halin-Graph ist in Abbildung 3.45 abgebil<strong>de</strong>t.<br />

18 Es existieren Theoreme, dass je<strong>de</strong>r planare Graph, <strong>de</strong>ssen Knoten eine gemeinsame Fläche haben, so<br />

dargestellt wer<strong>de</strong>n kann, dass diese Fläche die Außenfläche ist.<br />

77


Abbildung 3.45.: Die Baumkanten sind hervorgehoben. Der Baum beinhaltet keine Knoten<br />

mit <strong>de</strong>m Grad 2. Zu<strong>de</strong>m sind in <strong>de</strong>m Graphen alle Blätter <strong>de</strong>s<br />

Baums durch einen Zyklus verbun<strong>de</strong>n. Zusammen sorgen diese Eigenschaften<br />

dafür, dass dieser Graph ein Halin-Graph ist.<br />

Nachfolgend wird ein Algorithmus angegeben, mit <strong>de</strong>ssen Hilfe die Kanten von bipartiten<br />

Graphen gefärbt wer<strong>de</strong>n können.<br />

3.3.1.1. Kantenfärbung von bipartiten Graphen<br />

Im diesem Abschnitt sei ∆ <strong>de</strong>finiert als <strong>de</strong>r maximale Grad eines Knotens <strong>de</strong>s zu untersuchen<strong>de</strong>n<br />

Graphen. Da es also min<strong>de</strong>stens einen Knoten gibt, <strong>de</strong>ssen Grad ∆ ist, wer<strong>de</strong>n<br />

min<strong>de</strong>stens ∆ Farben benötigt, um die Kanten <strong>de</strong>s Graphen zu färben. In [GR98] wird<br />

zuerst ein Algorithmus angegeben, <strong>de</strong>r <strong>de</strong>n Graphen einfärbt für <strong>de</strong>n Fall, dass ∆ eine<br />

Zweierpotenz ist. Darauf aufbauend wur<strong>de</strong> dann erst ein Algorithmus vorgestellt, <strong>de</strong>r<br />

die Kantenfärbung für bipartite Graphen mit beliebigem ∆ ermöglicht. Im Folgen<strong>de</strong>n<br />

soll gezeigt wer<strong>de</strong>n, wie <strong>de</strong>r Algorithmus zur Färbung von bipartiten Graphen mit maximalem<br />

Knotengrad ∆ = 2 x auf <strong>de</strong>m GCA implementiert wer<strong>de</strong>n kann. Der Algorithmus<br />

zum Kantenfärben allgemeiner bipartiter Graphen hingegen wird in dieser Arbeit nicht<br />

auf <strong>de</strong>m GCA mo<strong>de</strong>lliert. An <strong>de</strong>m Algorithmus interessierte Leser wer<strong>de</strong>n an dieser Stelle<br />

auf [GR98] verwiesen.<br />

Der Algorithmus zur Färbung bipartiter Graphen mit einer Zweierpotenz als maximalen<br />

Grad (Euler-Färbung) verwen<strong>de</strong>t das parallele Divi<strong>de</strong>-and-Conquer-Prinzip (siehe A.2<br />

und B.3). Da vorausgesetzt wur<strong>de</strong>, dass ∆ eine Zweierpotenz ist, kann <strong>de</strong>r Graph in zwei<br />

Teilgraphen zerlegt wer<strong>de</strong>n, die dann auch wie<strong>de</strong>r ein ∆ haben, welches eine Zweierpotenz<br />

darstellt. Der Ausgangsgraph war bipartit, daher sind auch die Teilgraphen bipartit, da<br />

keine neuen Kanten hinzukommen, welche die Bedingungen verletzen könnten.<br />

Entschei<strong>de</strong>nd ist zunächst, wie diese Zerlegung gefun<strong>de</strong>n wer<strong>de</strong>n kann. Die Euler-Zerlegung<br />

erlaubt es <strong>de</strong>m Algorithmus, <strong>de</strong>n Graphen so zu teilen, dass bei<strong>de</strong> Teilgraphen<br />

eigenständig bearbeitet wer<strong>de</strong>n können und am En<strong>de</strong> das korrekte Ergebnis ohne großen<br />

Aufwand aus <strong>de</strong>n Teilergebnissen ermittelt wer<strong>de</strong>n kann. Unter <strong>de</strong>r Euler-Zerlegung<br />

versteht man die Aufteilung <strong>de</strong>s Graphen in mehrere kantendisjunkte Zyklen und Wege.<br />

Dabei ist je<strong>de</strong>r Knoten mit ungera<strong>de</strong>m Grad Endpunkt genau eines Wegs. Diese<br />

Euler-Zerlegung bil<strong>de</strong>t die Grundlage für die anschließend vorgenommenen Zerlegung,<br />

die darauf beruht, dass die Kanten alternierend mit 0 und 1 beschriftet wer<strong>de</strong>n. Alle<br />

78


Kanten mit <strong>de</strong>r Beschrif<strong>tu</strong>ng 0 sind im Teilgraphen G 1 , alle mit 1 im Teilgraphen G 2<br />

enthalten. Da die Euler-Zerlegung also <strong>de</strong>n Startpunkt und die Reihenfolge bestimmt, in<br />

<strong>de</strong>r die Kanten betrachtet wer<strong>de</strong>n, wird im nächsten Absatz näher darauf eingegangen,<br />

wie die Euler-Zerlegung gefun<strong>de</strong>n wer<strong>de</strong>n kann.<br />

Abbildung 3.46.: In a) ist <strong>de</strong>r Graph zu sehen, Knoten 2 hat Grad 4, somit ist ∆ eine<br />

Zweierpotenz. Teil b) zeigt die Zerlegung in die Wege und Zyklen. In<br />

Teil c) sind alle Kanten <strong>de</strong>s Graphen G 1 gestrichelt, die Kanten von<br />

G 2 sind wie gewohnt abgebil<strong>de</strong>t.<br />

Bei einem Eulerschem Graphen 19 gibt es genau einen Zyklus, <strong>de</strong>r bestimmt wer<strong>de</strong>n kann.<br />

Ist <strong>de</strong>r Graph nicht eulersch, so wird ein Zusatzknoten und von je<strong>de</strong>m Knoten mit ungera<strong>de</strong>m<br />

Knotengrad eine Kante zu diesem eingeführt. Dann wird die Euler-Zerlegung für<br />

diesen Graphen G ∗ gesucht. Die Euler-Zerlegung von G erhält man, in<strong>de</strong>m <strong>de</strong>r Zusatzknoten<br />

und alle zu ihm führen<strong>de</strong>n Kanten gelöscht wer<strong>de</strong>n. Dadurch kann es passieren,<br />

dass Zyklen zu Wegen zerfallen, aber auch diese sind in <strong>de</strong>r Euler-Zerlegung erlaubt.<br />

Betrachtet man <strong>de</strong>n Graphen aus Abbildung 3.46.a, so sieht <strong>de</strong>ssen Euler-Zerlegung wie<br />

in 3.46.b aus. Der Graph wird dann in die zwei Teilgraphen G 1 und G 2 zerlegt, in<strong>de</strong>m<br />

alle Kanten mit einer 0 als Markierung in G 1 und alle Kanten mit <strong>de</strong>r Markierung 1 in<br />

G 2 enthalten sind.<br />

Diese Zerlegung wird solange fortgesetzt, bis ∆=1 gilt. Dann können alle Kanten mit<br />

einer Farbe eingefärbt wer<strong>de</strong>n. Je<strong>de</strong>r <strong>de</strong>r Teilgraphen färbt die Kanten mit einer Nummer<br />

von 1 bis ∆ . Beim Zusammenfassen <strong>de</strong>r Teilgraphen wer<strong>de</strong>n dann die Farben im<br />

2<br />

Teilgraphen G 2 umbenannt, in<strong>de</strong>m man ∆ hinzuaddiert. Damit hat man insgesamt alle<br />

2<br />

Kanten mit einer Nummer versehen und da in G 1 und G 2 nach <strong>de</strong>r Umbenennung keine<br />

19 Man spricht davon, dass ein Graph eulersch ist o<strong>de</strong>r von einem Eulerschem Graphen, wenn es in<br />

<strong>de</strong>m Graphen einen Eulerschen Kreis gibt. Dieser Eulersche Kreis beschreibt einen Zyklus, <strong>de</strong>r je<strong>de</strong><br />

Kante <strong>de</strong>s Graphen genau einmal benutzt. Ein ungerichteter Graph kann nur dann eulersch sein,<br />

wenn alle Knoten einen gera<strong>de</strong>n Grad haben.<br />

79


gemeinsamen Farben (die Nummern entsprechen Farben) mehr existieren, ist das Gesamtergebnis<br />

eine korrekte Färbung, wenn bei<strong>de</strong> Teilgraphen korrekt gefärbt waren. In<br />

Pseudoco<strong>de</strong> wird <strong>de</strong>r Algorithmus wie folgt formuliert:<br />

Listing 3.6: Eulerfärbung für bipartite Graphen<br />

1 procedure Euler−colour (G)<br />

2 begin<br />

3 /∗ G hat maximalen Grad ∆ ∗/<br />

4 if ∆ = 1 then<br />

5 markiere a l l e Kanten in G mit <strong>de</strong>r Farbe 1<br />

6 else<br />

7 begin<br />

8 fin<strong>de</strong> eine Euler−Zerlegung von G<br />

9 mit Hilfe <strong>de</strong>r Euler−Zerlegung bil<strong>de</strong> die zwei Graphen G 1<br />

und G 2 , bei<strong>de</strong> mit maximalen Grad ∆ 2<br />

10 for G = G 1 und G = G 2 in parallel do Euler−colour (G)<br />

11 s t e l l e G aus G 1 und G 2 wie<strong>de</strong>r her , in<strong>de</strong>m die Farben aus<br />

G 2 umbenannt wer<strong>de</strong>n , damit G 1 und G 2 disjunkte<br />

Farbmengen benutzen .<br />

12 end<br />

13 end<br />

Für die Mo<strong>de</strong>llierung auf <strong>de</strong>m GCA ist es notwendig, die einzelnen Schritte genauer zu<br />

betrachten. Kann man je<strong>de</strong>n Schritt auf <strong>de</strong>m GCA mo<strong>de</strong>llieren, so ist auch <strong>de</strong>r gesamte<br />

Algorithmus auf <strong>de</strong>m GCA mo<strong>de</strong>llierbar. Nachfolgend sollen die Pseudo-Befehle aus <strong>de</strong>m<br />

obigen Programm näher erläutert und auf <strong>de</strong>m GCA mo<strong>de</strong>lliert wer<strong>de</strong>n:<br />

• In Zeile 4 <strong>de</strong>s Programms fin<strong>de</strong>t sich die Abbruchbedingung <strong>de</strong>s rekursiven Algorithmus.<br />

Um parallel testen zu können, ob alle Knoten einen maximalen Grad von<br />

eins haben, braucht man auf <strong>de</strong>m GCA für je<strong>de</strong>n Knoten eine Zelle. Je<strong>de</strong> Zelle<br />

kennt ihren eigenen Grad, <strong>de</strong>r von an<strong>de</strong>ren Zellen abgefragt wer<strong>de</strong>n kann. Sinnvoll<br />

ist an dieser Stelle auch eine Master-Zelle, die von allen Knoten-Zellen <strong>de</strong>n Grad<br />

abfragt und aufgrund <strong>de</strong>r Daten entschei<strong>de</strong>d, ob das Abbruchkriterium erfüllt ist<br />

o<strong>de</strong>r nicht. Dies kann unter an<strong>de</strong>rem dadurch geschehen, dass die Master-Zelle alle<br />

Knotengra<strong>de</strong> vero<strong>de</strong>rt und, wenn das Ergebnis größer als eins ist, entschei<strong>de</strong>t, dass<br />

das Kriterium noch nicht erfüllt ist.<br />

Ist das Abbruchkriterium erfüllt, so müssen alle im Graphen enthaltenen Kanten<br />

mit <strong>de</strong>r Farbe 1 gefärbt wer<strong>de</strong>n. Eine Färbung kann als Label an <strong>de</strong>r Kante<br />

dargestellt wer<strong>de</strong>n.<br />

In Bezug auf <strong>de</strong>n weiteren Algorithmus ist es sinnvoll, die Kanten auch als Zellen<br />

zu mo<strong>de</strong>llieren. Da aber eine Kante immer zwei Knoten verbin<strong>de</strong>t, ist es im<br />

Weiteren notwendig, für je<strong>de</strong> Kanten-Zelle die Knoten-Zelle zu bestimmen, welche<br />

im weiteren Verlauf <strong>de</strong>s Algorithmus regelt, ob und wie umgefärbt wer<strong>de</strong>n muss.<br />

80


Deswegen wird hier festgelegt, dass je<strong>de</strong> Kanten-Zelle, die (i,j) repräsentiert, lesend<br />

auf die Knoten-Zelle i zugreift.<br />

Die Knoten-Zellen haben lesen<strong>de</strong>n Zugriff auf alle Kanten-Zellen, die sie berühren,<br />

und können somit anhand ihrer Verbindungen feststellen, welchen Grad sie haben.<br />

Das Färben einer Kante gestaltet sich dann so, dass die Knoten-Zellen alle die<br />

Farbe 1 in <strong>de</strong>n Ausgabeport schreiben und die Kanten-Zellen diese lesen und ihre<br />

Kanten labeln.<br />

• In Zeile 8 fin<strong>de</strong>t sich die nächste interessante Anweisung. Diese Anweisung wird<br />

immer dann ausgeführt, wenn das Abbruchkriterium nicht erfüllt ist. Sie besteht<br />

aus mehreren Schritten, die im Pseudo-Co<strong>de</strong> nicht <strong>de</strong>utlich wer<strong>de</strong>n. Die Schritte<br />

um eine Euler-Zerlegung zu fin<strong>de</strong>n wer<strong>de</strong>n in <strong>de</strong>r folgen<strong>de</strong>n Aufzählung benannt<br />

und gleich <strong>de</strong>ren Mo<strong>de</strong>llierung auf <strong>de</strong>m GCA erläutert.<br />

Zur Ver<strong>de</strong>utlichung <strong>de</strong>r Schritte wer<strong>de</strong>n auch immer die Auswirkungen auf <strong>de</strong>n<br />

Beispielgraphen 3.47 anhand von Graphiken aufgezeigt. Wichtig ist hierbei, dass<br />

<strong>de</strong>r Graph für diesen Schritt eulersch sein muss; wie er angepasst wer<strong>de</strong>n kann,<br />

wur<strong>de</strong> bereits weiter vorne beschrieben.<br />

Da eine Dummy-Zelle nicht zur Laufzeit erzeugt wer<strong>de</strong>n kann, muss diese Zelle<br />

von vornherein vorhan<strong>de</strong>n sein. Die Dummy-Knoten-Zelle übernimmt es, je<strong>de</strong>n<br />

Knoten auf seinen Grad zu überprüfen. Haben alle Knoten einen gera<strong>de</strong>n Grad,<br />

so <strong>de</strong>aktiviert sie sich. Ansonsten geht sie in einen Zustand, welche <strong>de</strong>n Dummy-<br />

Kanten-Zellen anzeigt, dass diese sich mit einem Wert zu initialisieren haben (auch<br />

die Dummy-Kanten-Zellen müssen aktiv nach einer nötigen Verbindung suchen, da<br />

beim GCA nicht schreibend auf Zellen zugegriffen wer<strong>de</strong>n darf).<br />

Da somit eine große Anzahl even<strong>tu</strong>ell unbeschäftigter Zellen nötig ist, erscheint es<br />

sinnvoller, entwe<strong>de</strong>r nur eulersche Graphen mit Hilfe <strong>de</strong>s GCA zu bearbeiten o<strong>de</strong>r<br />

die Speicherzellen-Konstruktion zu verwen<strong>de</strong>n. Bei <strong>de</strong>r zweiten Variante wür<strong>de</strong> die<br />

Speicherzelle alle relevanten Daten abspeichern (u. a. die Kanten) und könnte einfach<br />

bei sich feststellen, wenn ein neuer Knoten und dazugehörige Kanten eingefügt<br />

wer<strong>de</strong>n müssen. Auch dieses Konstrukt ist nicht einfach, da die Knoten dann bei<br />

<strong>de</strong>r Speicherzelle abfragen müssen, ob neue Kanten für sie hinzugekommen sind<br />

o<strong>de</strong>r nicht, aber es entsteht kein so hoher Prozessorbedarf wie bei <strong>de</strong>r an<strong>de</strong>ren<br />

Möglichkeit.<br />

– Um die Euler-Zerlegung zu fin<strong>de</strong>n wird ein Teil <strong>de</strong>s Algorithmus verwen<strong>de</strong>t,<br />

<strong>de</strong>r <strong>de</strong>n Euler-Kreis eines eulerschen Graphen bestimmt. Da dieser Algorithmus<br />

einen gerichteten Graphen als Eingabe erhält und <strong>de</strong>r bislang behan<strong>de</strong>lte<br />

Graph ungerichtet ist, müssen alle Kanten durch gerichtete Kanten in bei<strong>de</strong><br />

Rich<strong>tu</strong>ngen ersetzt wer<strong>de</strong>n. Dies könnte dadurch mo<strong>de</strong>lliert wer<strong>de</strong>n, dass je<strong>de</strong><br />

Kante (i,j) repräsentieren<strong>de</strong> Zelle zusätzlich die Kante (j,i) speichert und<br />

somit eine ungerichtete Kante durch zwei gerichtete ersetzt.<br />

Allerdings ist es für die folgen<strong>de</strong>n Schritte besser, wenn je<strong>de</strong> gerichtete Kante<br />

durch eine eigene Zelle repräsentiert wird. Dazu wer<strong>de</strong>n |E| zusätzliche Zellen<br />

81


Abbildung 3.47.: Ein einfacher bipartiter Graph, <strong>de</strong>r in <strong>de</strong>n folgen<strong>de</strong>n Schritten dazu<br />

verwen<strong>de</strong>t wer<strong>de</strong>n soll, <strong>de</strong>n Algorithmus zu ver<strong>de</strong>utlichen.<br />

Abbildung 3.48.: Die Kanten <strong>de</strong>s Graphen wur<strong>de</strong>n alle durch zwei gerichtete Kanten ersetzt.<br />

Dieser Vorgang ist auch noch einmal anhand <strong>de</strong>r Zellen ver<strong>de</strong>utlicht.<br />

Für je<strong>de</strong> Kante und je<strong>de</strong>n Knoten existiert eine repräsentieren<strong>de</strong><br />

Zelle.<br />

benötigt, die jeweils die Knoten einer Kante abfragen und sie in umgekehrter<br />

Reihenfolge speichern. Wichtig ist, dass dies struk<strong>tu</strong>riert geschieht, damit die<br />

Knoten-Zellen auch lesend auf diese Zellen zugreifen können.<br />

Zu<strong>de</strong>m muss je<strong>de</strong> neue Kanten-Zelle eine lesen<strong>de</strong> Verbindung zu bei<strong>de</strong>n Knoten-<br />

Zellen, die sie berührt, aufbauen. Das ist eine Abweichung zu <strong>de</strong>r vorher getroffenen<br />

Aussage, ist aber für diesen Schritt sehr hilfreich und kann ohne<br />

weiteres realisiert wer<strong>de</strong>n. Die für <strong>de</strong>n vorhergehen<strong>de</strong>n Schritt unnötige lesen<strong>de</strong><br />

Verbindung wird dort einfach <strong>de</strong>aktiviert (nicht genutzt). Das Ergebnis<br />

dieses Schritts ist in Abbildung 3.48 graphisch dargestellt.<br />

– Damit eine Kante <strong>de</strong>s Original-Graphen auch nur ein Label erhält und damit<br />

<strong>de</strong>r Euler-Algorithmus erfolgreich ist, muss nun eine <strong>de</strong>r bei<strong>de</strong>n gerichteten<br />

Kanten eliminiert wer<strong>de</strong>n. Die Auswahl, welche <strong>de</strong>r bei<strong>de</strong>n gerichteten Kanten<br />

eliminiert wer<strong>de</strong>n muss, geschieht in diesem und <strong>de</strong>n nächsten Schritten.<br />

Zunächst wird die Liste <strong>de</strong>r Kanten sortiert. In vorangehen<strong>de</strong>n Abschnitten<br />

wur<strong>de</strong> bereits <strong>de</strong>r Heap-Sort als ein möglicher Sortier-Algorithmus vorgestellt,<br />

82


Abbildung 3.49.: Die Kanten wur<strong>de</strong>n in diesem Schritt sortiert. An <strong>de</strong>n Verbindungen<br />

zu <strong>de</strong>n Knoten än<strong>de</strong>rt sich nichts.<br />

allerdings hat dieser auch in <strong>de</strong>r parallelen Variante eine Laufzeit von O(n).<br />

In [GR98] wer<strong>de</strong>n parallele Sortieralgorithmen vorgestellt, die in O(log(n))<br />

sortieren. Da dies ein <strong>de</strong>utlich besseres Laufzeitverhalten ist, sollte ein entsprechen<strong>de</strong>r<br />

Sortieralgorithmus gewählt wer<strong>de</strong>n.<br />

Zum Sortieren wird in [GR98] Coles Sortieralgorithmus vorgeschlagen. Allerdings<br />

wird in [Nat90] sehr anschaulich illustriert, dass dieser Sortieralgorithmus<br />

zwar eine Laufzeitkomplexität von O(log(n)) hat, aber in <strong>de</strong>r konkreten<br />

Implementierung eine schlechtere Laufzeit aufweist als das bitonische Sortieren<br />

mit einer Laufzeit von O(log 2 (n)). Da sich dort <strong>de</strong>r Sortieralgorithmus<br />

von Cole bis zu einer Eingabegröße von n = 2 69 schlechter als das bitonische<br />

Sortieren verhält (dabei wur<strong>de</strong>n manche Verbesserungsmöglichkeiten <strong>de</strong>s bitonischen<br />

Sortierens bewusst nicht genutzt), wird hier darauf verzichtet, <strong>de</strong>n<br />

Algorithmus von Cole auf <strong>de</strong>m GCA zu implementieren. Dies erscheint vor<br />

allem <strong>de</strong>swegen sinnvoll, da das bitonische Sortieren bereits in [Hee01] sehr<br />

anschaulich und effizient auf <strong>de</strong>m GCA mo<strong>de</strong>lliert wur<strong>de</strong>.<br />

Die einzige Aufgabe besteht nun darin, <strong>de</strong>n Komperator <strong>de</strong>rgestalt anzupassen,<br />

dass eine Kante (i,j) genau dann als kleiner als eine Kante (k,l) <strong>de</strong>finiert<br />

wird, wenn i < k∨(i = k∧j < l) gilt. In Abbildung 3.49 wur<strong>de</strong> die Sortierung<br />

<strong>de</strong>r Kanten für <strong>de</strong>n Beispielgraph vorgenommen.<br />

– Mit Hilfe <strong>de</strong>r Sortierung wer<strong>de</strong>n nun die Successoren (Nachfolger) <strong>de</strong>r Kanten<br />

bestimmt. Da die Kanten untereinan<strong>de</strong>r über keine Verbindungen verfügen,<br />

muss die Zuweisung <strong>de</strong>s Successors von <strong>de</strong>n Knoten-Zellen aus erfolgen. Je<strong>de</strong><br />

Knoten-Zelle bestimmt anhand <strong>de</strong>r von ihr ausgehen<strong>de</strong>n Kanten die Successoren<br />

<strong>de</strong>r in sie eingehen<strong>de</strong>n Kanten (momentan wird auf einem gerichteten<br />

Graphen gearbeitet, <strong>de</strong>swegen sind die Kanten so am einfachsten zu<br />

beschreiben). Die Kanten-Zellen, welche die eingehen<strong>de</strong>n Kanten repräsentieren,<br />

können dann nach <strong>de</strong>r Berechnung ihren Successor bei <strong>de</strong>r Knoten-Zelle<br />

auslesen und abspeichern.<br />

Um auszurechnen, welche eingehen<strong>de</strong> Kante welche ausgehen<strong>de</strong> Kante zugewiesen<br />

bekommt, wer<strong>de</strong>n die sortierten Kanten betrachtet. Alle Kanten, die<br />

vom Knoten i ausgehen, sind in dieser sortierten Kantenfolge hintereinan<strong>de</strong>r<br />

angeordnet. Da <strong>de</strong>r Knoten lesen<strong>de</strong>n Zugriff auf alle an ihm anliegen<strong>de</strong>n Kanten<br />

hat, kann davon ausgegangen wer<strong>de</strong>n, dass seine Verweise auch in einer<br />

Art abgespeichert sind, dass sie <strong>de</strong>r Sortierung entsprechen.<br />

83


Abbildung 3.50.: Für je<strong>de</strong> Kantenzelle wur<strong>de</strong> <strong>de</strong>r Successor bestimmt. Die Zelle hat sich<br />

ihren Successor nicht nur gespeichert, son<strong>de</strong>rn auch eine Verbindung<br />

zu ihm aufgebaut.<br />

Da <strong>de</strong>r Graph eulersch ist, hat je<strong>de</strong>r Knoten i eine gera<strong>de</strong> Anzahl ausgehen<strong>de</strong>r<br />

Kanten, welche mit (i,v 0 )...(i,v k ) bezeichnet wer<strong>de</strong>n. Die v l (die Durchnummerierung<br />

folgt <strong>de</strong>r Sortierung und je<strong>de</strong>s v l bezeichnet einen Endknoten<br />

einer Kante) sind wichtig, da sie bestimmen, welche eingehen<strong>de</strong> Kante<br />

und welche ausgehen<strong>de</strong> Kante ein Successor-Paar bil<strong>de</strong>n. Für je<strong>de</strong>s ungera<strong>de</strong><br />

l berechnet <strong>de</strong>r Knoten i nun SUCCESSOR((v l ,i)) ← (i,v l+1 ) und<br />

SUCCESSOR((v l+1 ,i)) ← (i,v l ).<br />

Dabei bil<strong>de</strong>n die letzte Kante <strong>de</strong>r Liste und die erste Kante <strong>de</strong>r Liste ein Paar<br />

(dies geschieht in <strong>de</strong>r Berechnung, in<strong>de</strong>m einfach modulo <strong>de</strong>r Anzahl <strong>de</strong>r<br />

ausgehen<strong>de</strong>n Kanten gerechnet wird). Sinnvoll ist bei diesem Schritt, dass die<br />

Kanten-Zellen sich nicht nur <strong>de</strong>n Wert ihres Successors abspeichern, son<strong>de</strong>rn<br />

auch eine Verbindung zu <strong>de</strong>r entsprechen<strong>de</strong>n Zelle herstellen. Betrachtet man<br />

nur die Zellen nach <strong>de</strong>m Ablauf <strong>de</strong>s Schritts, so ergibt sich Abbildung 3.50.<br />

– Nun wird die Doubling-Technik auf die Kanten-Zellen angewen<strong>de</strong>t und gleichzeitig<br />

speichert die Kanten-Zelle einen Wert. Dieser Wert gibt am En<strong>de</strong> <strong>de</strong>s<br />

Schritts die kleinste Kante <strong>de</strong>s Zyklusses an, zu <strong>de</strong>m die Kanten-Zelle gehört.<br />

Je<strong>de</strong> Kanten-Zelle berechnet <strong>de</strong>swegen:<br />

V alue ← min{eigener V alue,V alue <strong>de</strong>s Successors}.<br />

Initialwert <strong>de</strong>r Kanten-Zelle ist die eigene Kante. Nach<strong>de</strong>m <strong>de</strong>r neue Value<br />

ausgerechnet wur<strong>de</strong>, wird die Successor-Verbindung auf <strong>de</strong>n Successor <strong>de</strong>s<br />

Successors gesetzt. Somit ist nach log(n) Schritten <strong>de</strong>r gesamte Zyklus durchlaufen<br />

und je<strong>de</strong> Kanten-Zelle enthält als Value die kleinste Kante <strong>de</strong>s eigenen<br />

Zyklusses. Die Kanten-Zellen nach Been<strong>de</strong>n <strong>de</strong>r Doubling-Technik sind in<br />

Abbildung 3.51 dargestellt.<br />

– Um nun die gerichteten Kanten auszuwählen, die die Kantenfärbung bestimmen,<br />

wird zwischen einem Kantenpaar (i,j) und (j,i) immer diejenige ausgewählt,<br />

<strong>de</strong>ren Value kleiner ist. Dabei wird die oben beschriebene Vergleichsoperation<br />

verwen<strong>de</strong>t.<br />

Dieser Vergleich kann entwe<strong>de</strong>r durch die Kanten-Zellen erfolgen, falls sie<br />

lesen<strong>de</strong>n Zugriff auf die jeweils an<strong>de</strong>re Kanten-Zelle haben, o<strong>de</strong>r durch die<br />

Knoten-Zellen. Da Knoten-Zellen lesen<strong>de</strong>n Zugriff auf alle sie berühren<strong>de</strong><br />

84


Abbildung 3.51.: Nach<strong>de</strong>m die Doubling-Technik auf die Kanten-Zellen angewen<strong>de</strong>t wur<strong>de</strong>,<br />

hat je<strong>de</strong> Zelle sich selber als Successor und <strong>de</strong>r Value steht auf <strong>de</strong>r<br />

kleinsten Kante <strong>de</strong>s Zyklusses, zu <strong>de</strong>m die Kante gehört.<br />

Abbildung 3.52.: Anhand <strong>de</strong>s Values wur<strong>de</strong> jetzt für je<strong>de</strong> Kanten-Zelle entschie<strong>de</strong>n, ob<br />

sie aktiv o<strong>de</strong>r inaktiv ist. Grau hinterlegte Kanten sind inaktiv, die<br />

an<strong>de</strong>ren aktiv.<br />

Kanten-Zellen haben, können sie <strong>de</strong>n Vergleich ohne weiteren Verbindungsaufwand<br />

durchführen. Wichtig ist, dass die Kanten-Zellen mit <strong>de</strong>m größeren<br />

Value <strong>de</strong>aktiviert wer<strong>de</strong>n und somit für die nachfolgen<strong>de</strong>n Schritte nicht mehr<br />

zur Verfügung stehen. Auch die Knoten-Zellen merken, welche Kanten-Zellen<br />

<strong>de</strong>aktiviert wur<strong>de</strong>n und ignorieren eine Verbindung dorthin. Die <strong>de</strong>aktivierten<br />

Kanten-Zellen sind in Abbildung 3.52 durch eine graue Hinterlegung gekennzeichnet.<br />

– Der letzte Schritt besteht nun darin, für die aktiven Kanten-Zellen einen<br />

Nachfolger zu fin<strong>de</strong>n, so dass die Kanten mit 0 o<strong>de</strong>r 1 gelabelt wer<strong>de</strong>n können.<br />

Hierbei ist anzumerken, dass bei diesem Beispiel zwar ein Zyklus entsteht, dies<br />

aber nicht immer so sein muss. Die einzige Garantie, die <strong>de</strong>r Algorithmus an<br />

dieser Stelle liefert, ist, dass <strong>de</strong>r Graph in kantendisjunkte Kreise und Wege<br />

zerlegt wur<strong>de</strong>. Da ein Euler-Kreis aber auch nicht für <strong>de</strong>n weiteren Verlauf<br />

<strong>de</strong>s Algorithmus notwendig ist, reicht diese Garantie.<br />

Um nun die Nachfolger zu bestimmen, müssen im Original-P-RAM-Algorithmus<br />

<strong>de</strong>r Kantenvektor und <strong>de</strong>r Successorvektor (die P-RAM arbeitet auf<br />

Vektoren) jeweils sortiert wer<strong>de</strong>n. Der Successorvektor nimmt dafür <strong>de</strong>n schon<br />

bekannten Komperator. Bei <strong>de</strong>m Kantenvektor ist eine Kante (i,j) kleiner als<br />

(k,l), wenn j < l ∨ (j = l ∧ i < k). Mit an<strong>de</strong>ren Worten, <strong>de</strong>r Kantenvektor<br />

wird nach <strong>de</strong>r zweiten Komponente je<strong>de</strong>r Kante sortiert.<br />

Auf <strong>de</strong>m GCA lässt sich dieser Schritt folgen<strong>de</strong>rmaßen realisieren: je<strong>de</strong> Knoten-<br />

Zelle kennt die an sie angrenzen<strong>de</strong>n Kanten-Zellen. Außer<strong>de</strong>m hat je<strong>de</strong>s Kante-<br />

85


Abbildung 3.53.: Je<strong>de</strong> Kanten-Zelle kennt nun ihren Successor (explizit in <strong>de</strong>r Kanten-<br />

Zelle gespeichert und als Link).<br />

Successor-Paar genau einen Knoten gemeinsam: die zweite Komponente <strong>de</strong>r<br />

Kante und die erste Komponente <strong>de</strong>s Successors. Da bei<strong>de</strong> Kanten also <strong>de</strong>r<br />

Knoten-Zelle bekannt sind, kann diese auch die Zuordnung übernehmen. Dazu<br />

muss sie intern ihre Kanten aufspalten in die Hälfte <strong>de</strong>r von ihr ausgehen<strong>de</strong>n<br />

Kanten (erste Komponente ist gleich <strong>de</strong>r Zell-ID <strong>de</strong>s Knotens) und <strong>de</strong>r eingehen<strong>de</strong>n<br />

Kanten (zweite Komponente ist gleich <strong>de</strong>r Zell-ID <strong>de</strong>s Knotens).<br />

Die ausgehen<strong>de</strong>n Kanten wer<strong>de</strong>n wie <strong>de</strong>r Successorvektor sortiert. Da hierbei<br />

die erste Komponente immer gleich ist, reduziert sich <strong>de</strong>r Vergleich nun<br />

darauf, ob die zweite Komponente kleiner ist.<br />

Die eingehen<strong>de</strong>n Kanten wer<strong>de</strong>n wie <strong>de</strong>r Kantenvektor sortiert. Auch hier<br />

reicht es, nach <strong>de</strong>r ersten Komponente zu sortieren, da die zweite Komponente<br />

bei allen ausgehen<strong>de</strong>n Kanten gleich <strong>de</strong>r Zell-ID <strong>de</strong>s Knotens ist.<br />

Nun wird <strong>de</strong>r i-ten eingehen<strong>de</strong>n Kante (aus <strong>de</strong>r sortierten eingehen<strong>de</strong>n Kanten-<br />

Liste) die i-te ausgehen<strong>de</strong> Kante (aus <strong>de</strong>r sortierten ausgehen<strong>de</strong>n Kanten-<br />

Liste) als Successor zugewiesen und die Kanten fragen wie<strong>de</strong>r bei <strong>de</strong>r entsprechen<strong>de</strong>n<br />

Knoten-Zelle ihren Successor ab.<br />

In <strong>de</strong>m hier gewählten Beispiel hat je<strong>de</strong>r Knoten nur eine eingehen<strong>de</strong> und<br />

eine ausgehen<strong>de</strong> Kante, das Sortieren ist also nicht notwendig. In Abbildung<br />

3.53 wur<strong>de</strong> je<strong>de</strong>r in <strong>de</strong>n Knoten eingehen<strong>de</strong>n Kante die aus <strong>de</strong>m Knoten<br />

ausgehen<strong>de</strong> als Successor zugewiesen.<br />

• In Zeile 9 wird nun gefor<strong>de</strong>rt, dass mit Hilfe <strong>de</strong>r eben gefun<strong>de</strong>nen Zerlegung <strong>de</strong>r<br />

Graph in zwei Teilgraphen zerlegt wird. Dies geschieht dadurch, dass alle noch aktiven<br />

Kanten-Zellen (ab diesem Schritt wer<strong>de</strong>n sie wie<strong>de</strong>r als ungerichtete Kanten<br />

betrachtet) entwe<strong>de</strong>r eine 0 o<strong>de</strong>r eine 1 als Label erhalten. Anschließend wer<strong>de</strong>n<br />

die Kanten, die mit <strong>de</strong>m Dummy verbun<strong>de</strong>n sind, entfernt.<br />

Die Kanten können mit Hilfe <strong>de</strong>r Doubling-Technik mit <strong>de</strong>m Label versehen wer<strong>de</strong>n.<br />

Wichtig dafür ist, dass eine Zelle mit einem Label initialisiert wird. Bereits<br />

in <strong>de</strong>n vorangehen<strong>de</strong>n Schritten benötigten die Kanten einen lesen<strong>de</strong>n Zugriff auf<br />

<strong>de</strong>n Master, also kann dieser die Initialisierung <strong>de</strong>r Kante veranlassen. Dies geschieht,<br />

in<strong>de</strong>m er die Kante und <strong>de</strong>n Label, auf <strong>de</strong>n sie zu setzen ist, in seinen<br />

86


Zustand kodiert. Die Kanten-Zellen lesen diesen Zustand und diejenige, die ihre<br />

Werte liest (Start- und Endpunkt sind gleich) initialisiert sich auf das angegebene<br />

Label. Anschließend entfernt sie ihre Verbindung zum Nachfolger.<br />

Die Kanten-Zellen fragen nun das Label ihres Nachbarn ab. Ist das Label we<strong>de</strong>r<br />

0 noch 1, so setzen sie ihre Nachfolgerverbindung auf die ihres Nachbarn. Dieser<br />

Schritt wird solange wie<strong>de</strong>rholt, bis <strong>de</strong>r Nachbar ein gültiges Label besitzt (nach<br />

maximal O(log(n)) Schritten).<br />

Mit Hilfe dieses Labels wird nun das eigene Label gesetzt. Wur<strong>de</strong> das an<strong>de</strong>re Label<br />

im ersten Schritt gefun<strong>de</strong>n, so ist das gefun<strong>de</strong>ne Label zu negieren, ansonsten ist<br />

<strong>de</strong>r Wert einfach zu übernehmen.<br />

• In Zeile 10 fin<strong>de</strong>t <strong>de</strong>r rekursive Aufruf statt. Es gibt die Möglichkeit, bei<strong>de</strong> Aufrufe<br />

nacheinan<strong>de</strong>r abzuarbeiten (auch das liefert schon eine Laufzeitverbesserung<br />

gegenüber an<strong>de</strong>ren <strong>Algorithmen</strong>) o<strong>de</strong>r bei<strong>de</strong> Aufrufe parallel zu bearbeiten. Das<br />

be<strong>de</strong>utet allerdings, dass die Knoten-Zellen bei je<strong>de</strong>m Aufruf dupliziert wer<strong>de</strong>n<br />

müssen. Sinnvoller ist es, <strong>de</strong>r Master-Zelle die Koordination zu überlassen und<br />

einen Aufruf nach <strong>de</strong>m an<strong>de</strong>ren zu bearbeiten.<br />

• Als letzter Schritt (Zeile 11) müssen nun die Farben im Ergebnis <strong>de</strong>s zweiten<br />

Aufrufs umbenannt wer<strong>de</strong>n. Auch dies kann die Master-Zelle erledigen. Sie muss<br />

lediglich für die Kanten-Zellen lesbar hinterlegen, ob es sich um <strong>de</strong>n ersten o<strong>de</strong>r<br />

<strong>de</strong>n zweiten Aufruf han<strong>de</strong>lt und wie groß das ∆ auf dieser Aufruf-Ebene war. Die<br />

Kanten addieren dann alle ∆ zu ihrer Farbe und die Umbenennung ist abgeschlossen.<br />

2<br />

Insgesamt wer<strong>de</strong>n also für diesen Algorithmus die Master-Zelle, |V | Knoten-Zellen und<br />

|2E| Kanten-Zellen benötigt. Zusätzlich wird entwe<strong>de</strong>r eine Speicherzelle o<strong>de</strong>r eine Dummy-Knoten-Zelle<br />

und maximal 2(|V |−1) Dummy-Kanten-Zellen benötigt. Daraus ergibt<br />

sich ein Zellenaufwand von O(|V |+|E|), was <strong>de</strong>utlich schlechter ist als <strong>de</strong>r Aufwand von<br />

O(|V |) auf <strong>de</strong>r P-RAM. Auch die Laufzeitkomplexität ist mit O(log 3 (n)) schlechter als<br />

auf <strong>de</strong>r P-RAM. Dies liegt aber an <strong>de</strong>r bewussten Entscheidung, nicht Coles Algorithmus<br />

zum Sortieren zu verwen<strong>de</strong>n.<br />

Auch die Euler-Färbung soll mit Hilfe <strong>de</strong>s Schemas aus Kapitel 2.3.4 klassifiziert wer<strong>de</strong>n.<br />

Als erstes soll dabei die Abhängigkeit <strong>de</strong>r Daten untersucht wer<strong>de</strong>n. Anschließend wer<strong>de</strong>n<br />

die Abhängigkeiten <strong>de</strong>r Verbindungen beleuchtet.<br />

In diesem Algorithmus existieren drei Arten von Zellen: Die Kanten-Zellen, die Knoten-<br />

Zellen und die Master-Zelle (sowie even<strong>tu</strong>ell eine Speicherzelle). Die Zellen verhalten sich<br />

unterschiedlich je nach<strong>de</strong>m, welcher Art sie angehören. Ordnet man die Zellen gemäß<br />

ihrer Art an, so kann man dieses Verhalten durch ihren Ort im GCA bestimmen. Es<br />

besteht dann also eine Ortsabhängigkeit <strong>de</strong>r Daten.<br />

Die Kanten-Zellen än<strong>de</strong>rn ihr Label, wenn sie dies von <strong>de</strong>r Master-Zelle o<strong>de</strong>r ihrer<br />

Knoten-Zelle vorgegeben bekommen. Die Daten <strong>de</strong>r Zellen sind also abhängig von <strong>de</strong>n<br />

Daten <strong>de</strong>r Nachbarzellen. Da das eigene Label durch hinzuaddieren <strong>de</strong>s gelesenen Werts<br />

87


aus <strong>de</strong>r Nachbarzelle gewonnen wird, sind die Daten auch abhängig von <strong>de</strong>n eigenen<br />

Daten.<br />

Es ergibt sich keine ersichtliche Verän<strong>de</strong>rung <strong>de</strong>r Daten aufgrund <strong>de</strong>r Zeit, weshalb dafür<br />

keine Abhängigkeit besteht. Auch eine Abhängigkeit von <strong>de</strong>n Verbindungen ist nicht<br />

festzustellen.<br />

Bereits bei <strong>de</strong>r Betrach<strong>tu</strong>ng <strong>de</strong>r Abhängigkeiten <strong>de</strong>r Daten wur<strong>de</strong> festgestellt, dass sich<br />

Zellen unterschiedlicher Arten verschie<strong>de</strong>n verhalten. Auch hier wird dies als eine Ortsabhängigkeit<br />

beschrieben.<br />

Die Verbindungen wer<strong>de</strong>n innerhalb <strong>de</strong>s Algorithmus dynamisch gehandhabt. So wird u.<br />

a. die Doubling-Technik mehrfach angewen<strong>de</strong>t. Da diesmal die Doubling-Technik ohne<br />

die Zuhilfenahme einer Speicherzelle angewen<strong>de</strong>t wird, sind die Verbindungen abhängig<br />

von <strong>de</strong>n Verbindungen <strong>de</strong>r Nachbar-Zelle.<br />

Im Laufe <strong>de</strong>s Algorithmus wer<strong>de</strong>n Successoren bestimmt und auch eine Verbindung zu<br />

diesen hergestellt. Die Successoren wer<strong>de</strong>n aber aufgrund <strong>de</strong>r Daten <strong>de</strong>r Nachbar-Zelle<br />

(<strong>de</strong>r Knoten) gebil<strong>de</strong>t und <strong>de</strong>swegen ist die Verbindung abhängig von <strong>de</strong>n Daten <strong>de</strong>r<br />

Nachbar-Zelle.<br />

Die oben festgestellten Abhängigkeiten treten in <strong>de</strong>m Algorithmus mehrfach auf und<br />

die angeführten Argumente sind nur als Beispiele zu sehen, weshalb die Abhängigkeit<br />

besteht. An<strong>de</strong>re Abhängigkeiten wur<strong>de</strong>n nicht festgestellt.<br />

3.4. Zusammenfassung<br />

In diesem Kapitel wur<strong>de</strong>n einige Graphen-<strong>Algorithmen</strong> vorgestellt. Da die Graphentheorie<br />

ein sehr weites und umfangreiches Feld ist, wur<strong>de</strong>n hier drei Problematiken exemplarisch<br />

beleuchtet: die Bestimmung zusammenhängen<strong>de</strong>r Komponenten, die Bestimmung<br />

eines minimal aufspannen<strong>de</strong>n Baums und die Färbung bipartiter Graphen.<br />

Für die Bestimmung zusammenhängen<strong>de</strong>r Komponenten und die Berechnung <strong>de</strong>s minimal<br />

aufspannen<strong>de</strong>n Baums wur<strong>de</strong>n zuerst klassische sequentielle <strong>Algorithmen</strong> vorgestellt.<br />

Diese wur<strong>de</strong>n dann anschließend auf <strong>de</strong>m GCA mo<strong>de</strong>liert und es wur<strong>de</strong> versucht,<br />

eine Laufzeitverbesserung zu erreichen.<br />

Im Falle <strong>de</strong>s Warshall-Algorithmus (zusammenhängen<strong>de</strong> Komponenten) ist es gelungen,<br />

die Laufzeitkomplexität von O(n 3 ) auf O(n) zu reduzieren. Allerdings wur<strong>de</strong> dies nur<br />

dadurch erreicht, dass ein hoher Aufwand in <strong>de</strong>r Hardware betrieben wur<strong>de</strong> (O(n) Zellen<br />

und O(n) Lei<strong>tu</strong>ngen).<br />

Es hat sich herausgestellt, dass die sequentiellen <strong>Algorithmen</strong> durch die parallelen Möglichkeiten<br />

<strong>de</strong>s GCA beschleunigt wer<strong>de</strong>n können. Allerdings schnei<strong>de</strong>n sie in <strong>de</strong>r Laufzeitanalyse<br />

schlechter ab als <strong>Algorithmen</strong>, die bereits für parallele Systeme entwickelt wur<strong>de</strong>n.<br />

Es wur<strong>de</strong>n einige <strong>Algorithmen</strong>, die für die P-RAM entwickelt wur<strong>de</strong>n auf <strong>de</strong>m GCA<br />

mo<strong>de</strong>lliert und eine Laufzeitanalyse betrieben. Im Laufe dieser Analyse wur<strong>de</strong> das Mo-<br />

88


Algorithmus Sequentiell Zeit P-RAM Proz. P-RAM Zeit GCA Zellen GCA<br />

Warshall O(n 3 ) - - O(n) O(n)<br />

Floyd-Warshall O(n 3 ) - - O(n) O(n)<br />

Hirschberg - O(log 2 (n)) O( n2<br />

log(n) ) O(log2 (n)) O( n2<br />

log(n) )<br />

Heapsort O(n log(n)) - - O(n) O(log(n))<br />

Kruskal O(|E|) - - O(|E|) O(|V |)<br />

mod. Hirschberg - O(log 2 (n)) O( n2<br />

log(n) ) O(log2 (n)) O( n2<br />

log(n) )<br />

Euler-Färbung - O(log 2 (n)) O(m) O(n log(n)) O(m + n)<br />

Tabelle 3.1.: Eine Betrach<strong>tu</strong>ng <strong>de</strong>r Laufzeit <strong>de</strong>r behan<strong>de</strong>lten <strong>Algorithmen</strong>. Falls vorhan<strong>de</strong>n<br />

sind die sequentielle o<strong>de</strong>r die P-RAM-Laufzeit angegeben. Wur<strong>de</strong> <strong>de</strong>r<br />

Algorithmus ursprünglich auf <strong>de</strong>r P-RAM angegeben und eine Abschätzung<br />

<strong>de</strong>r benötigten Prozessoren gegeben, so ist diese auch hier angegeben. Abschließend<br />

sind die Laufzeit und die benötigte Anzahl an Zellen angegeben.<br />

<strong>de</strong>ll einer Speicherzelle entwickelt, welche ermöglicht, dass alle betrachteten P-RAM-<br />

<strong>Algorithmen</strong> auf <strong>de</strong>m GCA mo<strong>de</strong>lliert wer<strong>de</strong>n können.<br />

Eine Zusammenfassung <strong>de</strong>r betrachteten <strong>Algorithmen</strong> zusammen mit ihrer Laufzeit im<br />

sequentiellen Fall, auf <strong>de</strong>r P-RAM und <strong>de</strong>m GCA ist in Tabelle 3.1 gegeben. Es erscheint<br />

immer interessant, bei parallelen <strong>Algorithmen</strong> nicht nur die Laufzeit son<strong>de</strong>rn auch die<br />

Anzahl <strong>de</strong>r benötigten Prozessoren (bzw. Zellen) zu betrachten. In [GR98] fin<strong>de</strong>t sich<br />

auf S. 1 folgen<strong>de</strong>s Zitat:<br />

A subclass of problems of particular interst are those which have optimal<br />

parallel algorithms. An optimal parallel algorithm is an algorithm for which<br />

the product of the parallel time t with the number of processors p used is<br />

linear in the problem size n. That is, pt = O(n). Optimality may also mean<br />

that the product pt is equal to the computation time of the fastest known<br />

sequential-time algorithm for the problem. Here we specifically refer to the<br />

problem as having optimal speed-up.<br />

Das Produkt pt wird an an<strong>de</strong>ren Stellen (u. a. in [KKT01]) auch als work bezeichnet.<br />

Alle betrachteten <strong>Algorithmen</strong> wur<strong>de</strong>n in das in Kapitel 2.3.4 vorgestellte Schema eingeordnet.<br />

Dabei wur<strong>de</strong>n die Abhängigkeiten <strong>de</strong>r Daten und <strong>de</strong>r Verbindungen <strong>de</strong>r nächsten<br />

Generationen von <strong>de</strong>n momentanen Gegebenenheiten untersucht. Das Ergebnis dieser<br />

Untersuchungen ist in Tabelle 3.2 (Daten) und 3.3 (Verbindungen) gegeben. Dabei stehen<br />

die Abkürzungen für folgen<strong>de</strong> Werte:<br />

• t = Zeit: kennzeichnet eine Verän<strong>de</strong>rung <strong>de</strong>s betrachteten Werts (Daten / Verbindung)<br />

abhängig von <strong>de</strong>r Zeit.<br />

• l = Eigener Ort: kennzeichnet eine Verän<strong>de</strong>rung <strong>de</strong>s betrachteten Werts abhängig<br />

von <strong>de</strong>m Ort <strong>de</strong>r eigenen Zelle.<br />

89


t l l* d d* p p*<br />

Warshall - - - + + - -<br />

Floyd-Warshall - - - + + - -<br />

Hirschberg - - - + + - -<br />

Heapsort + - - + + + -<br />

Kruskal + - - + + - -<br />

mod. Hirschberg - - - + + - -<br />

Euler-Färbung - + - + + + -<br />

Tabelle 3.2.: Abhängigkeit <strong>de</strong>r Daten einer Zelle in <strong>de</strong>m jeweils links stehen<strong>de</strong>n Algorithmus.<br />

Ein + steht für eine Abhängigkeit, ein - für keine Abhängigkeit. Die<br />

Be<strong>de</strong>u<strong>tu</strong>ng <strong>de</strong>r Abkürzungen ist <strong>de</strong>m Text zu entnehmen.<br />

t l l* d d* p p*<br />

Warshall + - - + - - -<br />

Floyd-Warshall + - - + - - -<br />

Hirschberg - - - - - - -<br />

Heapsort - + - + - - +<br />

Kruskal - - - - - - -<br />

mod. Hirschberg - - - - - - -<br />

Euler-Färbung - + - - + - +<br />

Tabelle 3.3.: Abhängigkeit <strong>de</strong>r Verbindungen einer Zelle in <strong>de</strong>m jeweils links stehen<strong>de</strong>n<br />

Algorithmus. Ein + steht für eine Abhängigkeit, ein - für keine Abhängigkeit.<br />

Die Be<strong>de</strong>u<strong>tu</strong>ng <strong>de</strong>r Abkürzungen ist <strong>de</strong>m Text zu entnehmen.<br />

• l* = Ort <strong>de</strong>r Nachbarn: kennzeichnet eine Verän<strong>de</strong>rung <strong>de</strong>s betrachteten Werts<br />

abhängig von <strong>de</strong>m Ort <strong>de</strong>r Nachbar-Zellen.<br />

• d = Eigene Daten: kennzeichnet eine Verän<strong>de</strong>rung <strong>de</strong>s betrachteten Werts abhängig<br />

von <strong>de</strong>n eigenen Daten einer Zelle.<br />

• d* = Daten <strong>de</strong>r Nachbarn: kennzeichnet eine Verän<strong>de</strong>rung <strong>de</strong>s betrachteten Werts<br />

abhängig von <strong>de</strong>n Daten <strong>de</strong>r Nachbar-Zellen.<br />

• p = Eigene Verbindung (Pointer): kennzeichnet eine Verän<strong>de</strong>rung <strong>de</strong>s betrachteten<br />

Werts abhängig von <strong>de</strong>n eigenen Verbindungen (nicht <strong>de</strong>n Werten, auf die sie<br />

zeigen).<br />

• p* = Verbindungen <strong>de</strong>r Nachbarn: kennzeichnet eine Verän<strong>de</strong>rung <strong>de</strong>s betrachteten<br />

Werts abhängig von <strong>de</strong>n Verbindungen <strong>de</strong>r Nachbar-Zellen.<br />

90


4. Die Mo<strong>de</strong>llierung von<br />

Krypto-<strong>Algorithmen</strong> auf <strong>de</strong>m GCA<br />

In <strong>de</strong>r heutigen Zeit wer<strong>de</strong>n die meisten Dokumente elektronisch verwaltet und Nachrichten<br />

wer<strong>de</strong>n elektronisch verschickt. Daher ist es wichtig, dass die Daten vor frem<strong>de</strong>n<br />

Zugriff geschützt wer<strong>de</strong>n können. Die Kryptographie ist eine Disziplin, die Metho<strong>de</strong>n<br />

<strong>de</strong>r Mathematik und Informatik kombiniert, um u. a. zu verhin<strong>de</strong>rn, dass Unbefugte,<br />

die in Besitz von Daten gelangen, daraus Informationen gewinnen können. Gera<strong>de</strong> da<br />

<strong>de</strong>r Briefverkehr sich immer mehr in die elektronische Variante, die E-Mail, verlagert,<br />

ist es wichtig, diese Informationen vor unbefugten Zugriff zu schützen.<br />

Viele in <strong>de</strong>r Kryptographie genutzte <strong>Algorithmen</strong> lassen sich leicht parallelisieren. Meist<br />

wer<strong>de</strong>n mehrere Berechnungen <strong>de</strong>r gleichen Art benötigt, um am En<strong>de</strong> das Gesamtergebnis<br />

zu erhalten. Diese Berechnungen können (falls sie nicht voneinan<strong>de</strong>r abhängig sind)<br />

parallel ausgeführt wer<strong>de</strong>n und somit kann eine Laufzeitverringerung erreicht wer<strong>de</strong>n.<br />

Ein solcher Algorithmus ist <strong>de</strong>r Chinesische Restsatz. Mit Hilfe <strong>de</strong>s Chinesischen Restsatzes<br />

ist es u. a. möglich, simultane Kongruenzen zu lösen. Um n Kongruenzen zu<br />

lösen, muss unabhängig voneinan<strong>de</strong>r n mal <strong>de</strong>r erweiterte euklidische Algorithmus ausgeführt<br />

wer<strong>de</strong>n. Der erweiterte euklidische Algorithmus (EEA) selber eignet sich lei<strong>de</strong>r<br />

nicht zur Parallelisierung. Da er aber nötig ist, um <strong>de</strong>n Chinesischen Restsatz anwen<strong>de</strong>n<br />

zu können, wird im folgen<strong>de</strong>n erst <strong>de</strong>r EEA erklärt. Erst danach wird die Anwendung<br />

<strong>de</strong>s Chinesischen Restsatzes erläutert und auf <strong>de</strong>m GCA simuliert. Bei<strong>de</strong> <strong>Algorithmen</strong><br />

verwen<strong>de</strong>n Grundlagen <strong>de</strong>r Gruppentheorie, auf die in dieser Arbeit aber nicht näher eingegangen<br />

wird. Eine gute Zusammenfassung <strong>de</strong>r Gruppentheorie und eine Beschreibung<br />

<strong>de</strong>r <strong>Algorithmen</strong> liefert [Buc01].<br />

4.1. Der erweiterte euklidische Algorithmus<br />

Der Euklidische Algorithmus dient dazu, <strong>de</strong>n größten gemeinsamen Teiler (ggT) zweier<br />

Zahlen a und b zu berechnen. Der erweiterte euklidische Algorithmus nutzt die Einzelschritte<br />

<strong>de</strong>s euklidischen Algorithmuses, um zusätzlich das Inverse von a mod b und<br />

von b mod a zu berechnen. Für <strong>de</strong>n Chinesischen Restsatz ist es notwendig, genau diese<br />

Inversen zu bestimmen, weshalb <strong>de</strong>r erweiterte euklidische Algorithmus so wichtig ist.<br />

Der euklidische Algorithmus benötigt maximal log(b)/log( 1+√ 5<br />

2<br />

) + 1 Schritte.<br />

Um <strong>de</strong>n ggT zweier ganzer Zahlen a und b mit a > b zu berechnen, wird a zerlegt in<br />

a = q × b + r. Dabei ist q das Ergebnis <strong>de</strong>r Ganzzahldivision ohne Rest von a durch b<br />

91


und r <strong>de</strong>r dabei anfallen<strong>de</strong> Rest. Im Algorithmus wird <strong>de</strong>shalb a durch b und b durch r<br />

ersetzt. Dieser Schritt wird solange wie<strong>de</strong>rholt, bis b = 0 ist. Ist dies <strong>de</strong>r Fall, so wird a<br />

als Ergebnis zurückgegeben.<br />

Die Schritte <strong>de</strong>s euklidischen Algorithmus können sehr anschaulich in einer Tabelle dargestellt<br />

wer<strong>de</strong>n. Die erste Zeile beinhaltet die Werte aus <strong>de</strong>nen <strong>de</strong>r ggT berechnet wer<strong>de</strong>n<br />

soll (die Werte <strong>de</strong>s ersten Durchlaufs stehen in <strong>de</strong>r ersten und zweiten Spalte, die Werte<br />

<strong>de</strong>s zweiten Durchlaufs stehen in <strong>de</strong>r zweiten und dritten Spalte usw.). Die zweite Zeile<br />

gibt das q an (also die Anzahl, wie oft b in a passt).<br />

k 0 1 2 3<br />

r k 27 6 3 0<br />

q k 4 2<br />

Tabelle 4.1.: Beispiel: Der euklidische Algorithmus wird verwen<strong>de</strong>t um <strong>de</strong>n ggT von 27<br />

und 6 zu berechnen.<br />

Der erweiterte euklidische Algorithmus nutzt nun die q k , um ein x und ein y zu berechnen,<br />

so dass gilt: ggT(a,b) = ax + by. Dies gelingt mit Hilfe <strong>de</strong>r Erkenntnis, dass r n =<br />

(−1) n x n a + (−1) n+1 y n b, d. h. es wird lediglich eine Berechnungsvorschrift benötigt, um<br />

die x k und y k (für alle 1 ≤ k ≤ n) zu berechnen. Durch die oben genannte Endbedingung<br />

ergeben sich die Berechnungsvorschriften: x k+1 = q k x k + x k−1 und y k+1 = q k y k + y k−1 .<br />

Da die oben genannte Erkenntnis für alle n ≤ k (also insbeson<strong>de</strong>re auch für r 0 = a und<br />

r 1 = b) gilt, ergeben sich folgen<strong>de</strong> Initialisierungen: x 0 = 1, x 1 = 0, y 0 = 0 und y 1 = 1.<br />

Wen<strong>de</strong>t man <strong>de</strong>n erweiterten euklidischen Algorithmus auf das vorherige Beispiel an, so<br />

muss die Tabelle um die Einträge x k und y k erweitert wer<strong>de</strong>n. Die Tabelle 4.2 berechnet<br />

neben <strong>de</strong>m ggT auch noch das x und das y.<br />

Da in <strong>de</strong>r Tabelle <strong>de</strong>r letzte Wert von r k ≠ 0 <strong>de</strong>n ggT angibt, kann man aus <strong>de</strong>r Tabelle<br />

folgen<strong>de</strong>s entnehmen:<br />

ggT(27, 6) = 3 = 1 × 27 + (−4) × 6 = 27 − 24 = 3.<br />

Für Werte a und b, die teilerfremd sind (d. h. ggT(a,b) = 1), ist <strong>de</strong>r Wert x gleichzeitig<br />

auch das Inverse von a modulo b. Da modulo b gerechnet wird gilt:<br />

k 0 1 2 3<br />

r k 27 6 3 0<br />

q k 4 2<br />

x k (+)1 (-)0 (+)1 (-)2<br />

y k (-)0 (+)1 (-)4 (+)9<br />

Tabelle 4.2.: Beispiel: Der erweiterte euklidische Algorithmus wird verwen<strong>de</strong>t um <strong>de</strong>n<br />

ggT von 27 und 6 zu berechnen. Gleichzeitig wer<strong>de</strong>n das x und das y bestimmt,<br />

für die gilt: ggT(27, 6) = 27 × x + 6 × y. In <strong>de</strong>n Klammern ist das<br />

alternieren<strong>de</strong> Vorzeichen gegeben, welches aber lediglich für die endgültigen<br />

Werte von x und y benötigt wer<strong>de</strong>n.<br />

92


1 mod b = ggT(a,b) mod b = (a × x + b × y) mod b = a × x mod b. Somit liefert<br />

<strong>de</strong>r erweiterte euklidische Algorithmus für teilerfrem<strong>de</strong> Zahlen eine einfache Metho<strong>de</strong><br />

zur Bestimmung von Inversen in einem Modulo-Ring. Der Source-Co<strong>de</strong> in C sieht im<br />

Prinzip folgen<strong>de</strong>rmaßen aus:<br />

Listing 4.1: Der erweiterte euklidische Algorithmus in C<br />

1 int euklid ( int a , int b , int ∗x , int ∗y){<br />

2 int xPrev , xCur , yPrev , yCur , xNext , yNext , r , q ;<br />

3 int sign = 1;<br />

4 xPrev = 1;<br />

5 xCur = 0;<br />

6 yPrev = 0;<br />

7 yCur = 1;<br />

8 while (b != 0) {<br />

9 r = a%b ;<br />

10 q = a/b ;<br />

11 a = b ;<br />

12 b = r ;<br />

13 xNext = q∗xCur + xPrev ;<br />

14 xPrev = xCur ;<br />

15 xCur = xNext ;<br />

16 yNext = q∗yCur + yPrev ;<br />

17 yPrev = yCur ;<br />

18 yCur = yNext ;<br />

19 sign = −sign ;<br />

20 }<br />

21 ∗x = sign ∗ xCur ;<br />

22 ∗y = −sign ∗ yCur ;<br />

23 re<strong>tu</strong>rn a ;<br />

24 }<br />

Da in <strong>de</strong>r Anwendung <strong>de</strong>s Chinesischen Restsatzes nur Kongruenzen <strong>de</strong>r Form<br />

yM ≡ 1 mod m nach <strong>de</strong>r Verwendung <strong>de</strong>s erweiterten euklidischen Algorithmus verlangen<br />

(es wird das Inverse von M modulo m gesucht) und dabei i. A. gilt, dass M > m,<br />

wird nur die Berechnung <strong>de</strong>s Wertes x benötigt.<br />

4.2. Die Anwendung <strong>de</strong>s Chinesische Restsatzes<br />

Der Chinesische Restsatz lautet: Seien m,n teilerfremd. Dann hat das Gleichungssystem<br />

∣<br />

x = a mod m<br />

x = b mod n<br />

∣<br />

93


eine ein<strong>de</strong>utige Lösung modulo m × n.<br />

Der Chinesische Restsatz wird u. a. angewen<strong>de</strong>t, um simultane Kongruenzen zu lösen.<br />

Dabei müssen die Faktoren, nach <strong>de</strong>nen modularisiert wird paarweise teilerfremd sein.<br />

Betrachtet man nun das Beispiel aus [Buc01] S. 41, so stehen dort mehrere Kongruenzen,<br />

die ein gemeinsames Ergebnis x haben. Diese allgemeinen Kongruenzen möchte man<br />

dann mit Hilfe <strong>de</strong>s Chinesischen Restsatzes lösen:<br />

x ≡ a 1 mod m 1 ,x ≡ a 2 mod m 2 , · · · ,x ≡ a n mod m n .<br />

Alle m i (1 ≤ i ≤ n) sind untereinan<strong>de</strong>r teilerfremd. Dann setzt man m = ∏ n<br />

i=1 m i und<br />

berechnet anschließend für je<strong>de</strong>s i ein eigenes M i = m m i<br />

. Dann wird für je<strong>de</strong>s M i sein<br />

Inverses modulo m i gesucht, es muss also die Gleichung y i M i ≡ 1 mod m i für je<strong>de</strong>s<br />

i größer 1 und kleiner n gelöst wer<strong>de</strong>n. Um die y i zu berechnen, wird <strong>de</strong>r erweiterte<br />

euklidische Algorithmus verwen<strong>de</strong>t.<br />

Mit Hilfe dieser Ergebnisse kann nun das Gesamtergebnis x <strong>de</strong>r simultanen Kongruenz<br />

bestimmt wer<strong>de</strong>n, da x = ( ∑ n<br />

i=1 a i y i M i ) mod m gilt.<br />

Interessant ist diese Metho<strong>de</strong>, wenn entwe<strong>de</strong>r bekannt ist, dass mehrere Kongruenzen<br />

das gleiche (noch unbekannte) Ergebnis haben, o<strong>de</strong>r wenn man quadratische Gleichungen<br />

modulo einer Zahl m berechnen muss.<br />

Betrachtet man das Beispiel x 2 = 1 mod 30, so kann diese Gleichung in die drei Kongruenzen<br />

x 2 = 1 mod 2, x 2 = 1 mod 3 und x 2 = 1 mod 5 zerlegt wer<strong>de</strong>n (man zerlegt<br />

die 30 in seine Primfaktoren).<br />

Der Chinesische Restsatz gilt zwar nur für lineare Gleichungen, es gestaltet sich jedoch<br />

einfacher, quadratische Gleichungen für die Primfaktoren zu lösen als für m. So kann<br />

die Gleichung x 2 = 1 mod 2 gelöst wer<strong>de</strong>n, in<strong>de</strong>m alle Werte i mit 0 ≤ i ≤ überprüft<br />

wer<strong>de</strong>n, ob sie die Gleichung erfüllen.<br />

Die 0 wird für keine <strong>de</strong>r Gleichungen eine Lösung sein, also müssen pro Gleichung maximal<br />

4 Werte (für m i = 5) eingesetzt und geprüft wer<strong>de</strong>n. Die erste Gleichung ist ein<strong>de</strong>utig<br />

mit x = 1 mod 2 bestimmt, bei <strong>de</strong>r zweiten und dritten gibt es jedoch jeweils zwei Lösungen.<br />

Die zwei Lösungen <strong>de</strong>r zweiten Gleichung sind x = 1 mod 3 und x = 2 mod 3. Die<br />

dritte Gleichung dagegen hat die Lösungen x = 1 mod 5 und x = 4 mod 5.<br />

Es gibt vier Möglichkeiten, diese Gleichungen zu kombinieren, also vier verschie<strong>de</strong>ne<br />

” Sets“ an a i:<br />

• a 1 = 1, a 2 = 1, a 3 = 1<br />

• a 1 = 1, a 2 = 2, a 3 = 1<br />

• a 1 = 1, a 2 = 1, a 3 = 4<br />

• a 1 = 1, a 2 = 2, a 3 = 4<br />

Die Inversen können unabhängig von <strong>de</strong>n a i berechnet wer<strong>de</strong>n, aber beim Bestimmen<br />

<strong>de</strong>r Lösung für die ursprüngliche Gleichung muss je<strong>de</strong>s ”<br />

Set“ eingesetzt wer<strong>de</strong>n. Es gibt<br />

also vier Lösungen für das Gleichungssystem.<br />

94


Für die Anwendung <strong>de</strong>s Chinesischen Restsatzes gelten folgen<strong>de</strong> Werte: m 1 = 2, m 2 = 3,<br />

m 3 = 5 und m = 30. Daraus berechnen sich nun die M i : M 1 = 15, M 2 = 10 und M 3 = 6.<br />

Anschließend muss für je<strong>de</strong>s M i sein Inverses modulo m i bestimmt wer<strong>de</strong>n. Dies geschieht<br />

mit <strong>de</strong>m erweiterten euklidischen Algorithmus:<br />

k 0 1 2 3<br />

r k 15 2 1 0<br />

q k 7 2<br />

x k (+)1 (-)0 (+)1 (-)2<br />

y k (-)0 (+)1 (-)7 (+)15<br />

Tabelle 4.3.: Zur Berechnung <strong>de</strong>s Inversen von M 1 modulo m 1 wird <strong>de</strong>r erweiterte euklidische<br />

Algorithmus verwen<strong>de</strong>t.<br />

Aus <strong>de</strong>n Tabellen 4.3, 4.4 und 4.5 lassen sich nun die y i nun jeweils in <strong>de</strong>r Spalte k = 2<br />

und Zeile x k ablesen. Daraus ergeben sich die folgen<strong>de</strong>n y i : y 1 = y 2 = y 3 = 1.<br />

Setzt man nun dieses Ergebnis ein, so erhält man die Lösungen:<br />

• x = 1 × 1 × 15 + 1 × 1 × 10 + 1 × 1 × 6 mod 30 = 1 mod 30<br />

• x = 1 × 1 × 15 + 1 × 2 × 10 + 1 × 1 × 6 mod 30 = 41 mod 30 = 11 mod 30<br />

• x = 1 × 1 × 15 + 1 × 1 × 10 + 1 × 4 × 6 mod 30 = 49 mod 30 = 19 mod 30<br />

• x = 1 × 1 × 15 + 1 × 2 × 10 + 1 × 4 × 6 mod 30 = 59 mod 30 = 29 mod 30<br />

k 0 1 2 3<br />

r k 10 3 1 0<br />

q k 3 3<br />

x k (+)1 (-)0 (+)1 (-)3<br />

y k (-)0 (+)1 (-)3 (+)9<br />

Tabelle 4.4.: Auch zur Berechnung <strong>de</strong>s Inversen von M 2 modulo m 2 wird <strong>de</strong>r erweiterte<br />

euklidische Algorithmus verwen<strong>de</strong>t.<br />

k 0 1 2 3<br />

r k 6 5 1 0<br />

q k 1 5<br />

x k (+)1 (-)0 (+)1 (-)5<br />

y k (-)0 (+)1 (-)1 (+)6<br />

Tabelle 4.5.: Das letzte benötigte Inverse (von M 3 modulo m 3 ) wird auch mit Hilfe <strong>de</strong>s<br />

erweiterten euklidischen Algorithmus berechnet.<br />

95


Abbildung 4.1.: Je<strong>de</strong> Zelle <strong>de</strong>s GCA wird mit einem a i und einem m i initialisiert. Daraufhin<br />

setzt die Zelle m auf m i damit die Doubling-Technik erfolgreich<br />

angewen<strong>de</strong>t wer<strong>de</strong>n kann. Die letzte Zelle hat eine inaktive Verbindung,<br />

dies ist dadurch dargestellt, dass die Zelle grau hinterlegt wur<strong>de</strong>.<br />

Abbildung 4.2.: Nach log(n) (hier zwei) Schritten steht das korrekte Ergebnis in <strong>de</strong>r ersten<br />

Zelle. Die an<strong>de</strong>ren Zellen haben eine <strong>de</strong>aktivierte Verbindung. Dies<br />

wur<strong>de</strong> auch hier wie<strong>de</strong>r durch eine graue Hinterlegung gekennzeichnet.<br />

Wie anhand <strong>de</strong>s Beispiels ersichtlich wird, gibt es Lösungen für das Gleichungssystem,<br />

welche man ohne <strong>de</strong>n Chinesischen Restsatz nicht so einfach erkannt hätte.<br />

Für die Implementierung <strong>de</strong>r Anwendung <strong>de</strong>s Chinesischen Restsatzes wird angenommen,<br />

dass bereits die linearen Kongruenzen vorhan<strong>de</strong>n sind. Dann wird je<strong>de</strong> Zelle mit<br />

einer Kongruenz initialisiert, d. h. sie erhält ein a i und ein m i (s. Abbildung 4.1).<br />

Als ersten Schritt muss m berechnet wer<strong>de</strong>n. Dazu wird wie<strong>de</strong>r die Doubling-Technik<br />

verwen<strong>de</strong>t. Je<strong>de</strong> Zelle überprüft, ob die Verbindung <strong>de</strong>r Nachbar-Zelle noch aktiv ist.<br />

Falls dies nicht <strong>de</strong>r Fall ist, so multipliziert die Zelle ihren eigenen Wert m mit <strong>de</strong>m<br />

Wert m ihrer Nachbar-Zelle und <strong>de</strong>aktiviert seine Verbindung. Ist die Verbindung <strong>de</strong>r<br />

Nachbar-Zelle nicht <strong>de</strong>aktiviert, so multipliziert die Zelle ihren eigenen Wert m mit <strong>de</strong>m<br />

Wert m ihrer Nachbar-Zelle und setzt seine Verbindung auf die Verbindung <strong>de</strong>r Nachbar-<br />

Zelle. Alle Zellen mit einer <strong>de</strong>aktivierten Verbindung warten, bis die log(n) Schritte <strong>de</strong>r<br />

Ausrechenphase been<strong>de</strong>t sind, bevor sie wie<strong>de</strong>r etwas machen.<br />

Nach log(n) Schritten hat nur die erste Zelle einen korrekten Wert (s. Abbildung 4.2),<br />

also müssen alle Zellen eine Verbindung zu <strong>de</strong>r ersten Zelle aufbauen. In 4.3 haben alle<br />

Zellen außer <strong>de</strong>r ersten eine Verbindung zu <strong>de</strong>r ersten Zelle aufgebaut und <strong>de</strong>ren Wert<br />

von m übernommen.<br />

In <strong>de</strong>n nachfolgen<strong>de</strong>n Schritten ist keinerlei Kommunikationsaufwand nötig. Die Zellen<br />

berechnen ihr eigenes M i in<strong>de</strong>m sie m durch m i teilen. Anschließend wird <strong>de</strong>r erweiterte<br />

euklidische Algorithmus verwen<strong>de</strong>t, um das y i zu berechnen. Nach<strong>de</strong>m die Zelle y i , a i<br />

und M i kennt, kann sie diese miteinan<strong>de</strong>r multiplizieren und schreibt sie in <strong>de</strong>n x-Wert.<br />

96


Abbildung 4.3.: Alle Zellen haben eine Verbindung zu <strong>de</strong>r ersten Zelle aufgebaut. Anschließend<br />

wur<strong>de</strong> <strong>de</strong>r Wert m bei <strong>de</strong>r ersten Zelle ausgelesen und in die<br />

eigene Variable m geschrieben.<br />

Nachfolgend wird wie<strong>de</strong>r eine Kommunikation notwendig, <strong>de</strong>nn zum Abschluss <strong>de</strong>s Algorithmus<br />

müssen die gefun<strong>de</strong>nen Werte x aller Zellen aufaddiert wer<strong>de</strong>n. Dies kann<br />

wie das initiale Berechnen <strong>de</strong>s Wertes m mit <strong>de</strong>r Doubling-Technik geschehen. Dazu<br />

addieren die Zellen ihren eigenen x-Wert und <strong>de</strong>n x-Wert ihres Nachbarn falls die eigene<br />

Verbindung aktiv ist. Ist die eigene Verbindung und die Verbindung <strong>de</strong>s Nachbarn aktiv,<br />

so wird anschließend die eigene Verbindung auf die Verbindung <strong>de</strong>s Nachbarn gesetzt.<br />

Die Lösung <strong>de</strong>r simultanen Kongruenz steht dann in <strong>de</strong>r ersten Zelle und kann dort<br />

ausgelesen wer<strong>de</strong>n.<br />

Da die Kongruenzen alle parallel gelöst wer<strong>de</strong>n, ist die Laufzeit <strong>de</strong>r Anwendung <strong>de</strong>s Chinesischen<br />

Restsatzes von <strong>de</strong>r Laufzeit <strong>de</strong>s erweiterten euklidischen Algorithmus abhängig.<br />

Wie bereits bei <strong>de</strong>r Beschreibung <strong>de</strong>s erweiterten euklidischen Algorithmus erwähnt<br />

wur<strong>de</strong>, sind maximal log(b)/log( 1+√ 5) + 1 Schritte nötig. Allerdings fallen in <strong>de</strong>m erweiterten<br />

euklidischen Algorithmus auch zwei Multiplikationen an, welche in [Buc01]<br />

2<br />

als <strong>de</strong>r zeitkritische Faktor angesehen wer<strong>de</strong>n und <strong>de</strong>swegen die Laufzeit dominieren.<br />

Es ergibt sich <strong>de</strong>shalb eine Laufzeit für <strong>de</strong>n erweiterten euklidischen Algorithmus von<br />

O(log(a) × log(b)). Betrachte man die Anwendung <strong>de</strong>s Chinesischen Restsatzes unter<br />

diesen Gesichtspunkten, so hat die Initialisierung auf das gemeinsame m anfangs trotz<br />

<strong>de</strong>r benötigten log(m) Schritte die gleiche Laufzeitkomplexität wie <strong>de</strong>r erweiterte euklidische<br />

Algorithmus. Insgesamt benötigt die Anwendung <strong>de</strong>s Chinesischen Restsatzes<br />

also eine Laufzeit von O(log(m) × log(n)) wobei m das größte und n das zweitgrößte<br />

Ausgangsmodul ist.<br />

4.3. Einordnung <strong>de</strong>r Anwendung <strong>de</strong>s Chinesischen<br />

Restsatzes<br />

In diesem Abschnitt wird die Anwendung <strong>de</strong>s Chinesischen Restsatzes in das in Kapitel<br />

2.3.4 vorgestellte Schema eingeordnet. Da die Kommunikation innerhalb dieses Algorithmus<br />

auf die Initialisierung und das Zusammenrechnen am En<strong>de</strong> beschränkt, reicht eine<br />

Betrach<strong>tu</strong>ng dieser bei<strong>de</strong>n Abläufe.<br />

Betrachtet man diese bei<strong>de</strong>n Abläufe genauer, so erkennt man, dass es sich bei<strong>de</strong> Male<br />

um eine Doubling-Technik han<strong>de</strong>lt. Bei <strong>de</strong>r Initialisierung müssen die Werte <strong>de</strong>r Zellen<br />

97


t l l* d d* p p*<br />

Daten - - - + + + -<br />

Verbindungen + - - - - + +<br />

Tabelle 4.6.: Abhängigkeit <strong>de</strong>r Verbindungen und <strong>de</strong>r Daten einer Zelle für die Anwendung<br />

<strong>de</strong>s Chinesischen Restsatzes. Ein + steht für eine Abhängigkeit, ein<br />

- für keine Abhängigkeit. Die Be<strong>de</strong>u<strong>tu</strong>ng <strong>de</strong>r Abkürzungen ist entsprechend<br />

<strong>de</strong>nen in Kapitel 3.4.<br />

multipliziert, am En<strong>de</strong> muss addiert wer<strong>de</strong>n. Ansonsten sind bei<strong>de</strong> Abläufe gleich und<br />

können somit gemeinsam betrachtet wer<strong>de</strong>n.<br />

Die Daten sind abhängig von <strong>de</strong>n eigenen Daten und <strong>de</strong>n Daten <strong>de</strong>r Nachbarn.<br />

Eine Abhängigkeit <strong>de</strong>r Daten von <strong>de</strong>r Zeit ist nicht feststellbar.<br />

Auch von <strong>de</strong>m eigenen Ort sind die Daten nicht abhängig, außer dass das Ergebnis <strong>de</strong>r<br />

Rechnung am En<strong>de</strong> in <strong>de</strong>r ersten Zelle steht.<br />

Interessant ist, dass sich die Daten in Abhängigkeit <strong>de</strong>r eigenen Verbindung än<strong>de</strong>rn. Nur<br />

wenn die eigene Verbindung aktiv ist, wer<strong>de</strong>n die eigenen Daten geän<strong>de</strong>rt.<br />

Die Verbindungen sind abhängig von <strong>de</strong>r Verbindung <strong>de</strong>s Nachbarn. Ist die Verbindung<br />

<strong>de</strong>s Nachbarn aktiv, so wird die eigene Verbindung auf diese Verbindung geän<strong>de</strong>rt, ansonsten<br />

wird die eigene Verbindung <strong>de</strong>aktiviert. Außer<strong>de</strong>m sind sie abhängig von <strong>de</strong>n<br />

eigenen Verbindungen, da auf <strong>de</strong>n Nachbar nur zugegriffen wird, wenn die eigene Verbindung<br />

aktiv ist.<br />

Da nach <strong>de</strong>r Doubling-Technik alle Zellen eine Verbindung zu <strong>de</strong>r ersten Zelle aufbauen,<br />

ist die Verbindung auch abhängig von <strong>de</strong>r Zeit. Es ist vorhersehbar, dass zu diesem<br />

Zeitpunkt genau diese Verbindungen existieren.<br />

Die Abhängigkeit vom Ort bedarf <strong>de</strong>r Interpretation. Man kann sich einerseits auf <strong>de</strong>n<br />

Standpunkt stellen, dass alle Zellen, die nicht an <strong>de</strong>r ersten Stelle sind, eine Verbindung<br />

zu <strong>de</strong>r ersten Zelle aufbauen. Mit dieser Betrach<strong>tu</strong>ng ist die Verbindung ortsabhängig.<br />

An<strong>de</strong>rerseits kann man die Auffassung vertreten, dass je<strong>de</strong> Zelle eine Verbindung zu<br />

<strong>de</strong>r ersten Zelle aufbaut (also auch die erste Zelle). In diesem Fall ist die Verbindung<br />

ortsunabhängig. Da sich die Abhängigkeit hier sehr leicht vermei<strong>de</strong>n lässt und keine<br />

Fehler dadurch entstehen, wird <strong>de</strong>r zweite Ansatz vertreten.<br />

Es ergeben sich also die Funktionen: d’ = f(d,d*,p) und p’ = g(t,p,p*).<br />

98


5. Implementierungseigenheiten<br />

Die in Kapitel 3 und Kapitel 4 vorgestellten <strong>Algorithmen</strong> wur<strong>de</strong>n nicht nur in <strong>de</strong>r Theorie<br />

auf <strong>de</strong>m GCA mo<strong>de</strong>lliert, son<strong>de</strong>rn auch in C implementiert. Allerdings besteht bei <strong>de</strong>r<br />

Programmierung in C das Problem, dass diese Sprache selbst nicht parallel ist. Die<br />

einzige Möglichkeit, Parallelität in C zu programmieren, sind Threads.<br />

Threads bieten die Möglichkeit, mehrere Prozesse zur gleichen Zeit aufzurufen. Auf einem<br />

Single-Prozessor-System ergibt sich dadurch keine Laufzeitbeschleunigung. Da <strong>de</strong>r<br />

Prozessor versucht, alle Prozesse möglichst gleich zu behan<strong>de</strong>ln, schaltet er nach gewissen<br />

Zeitspannen zwischen <strong>de</strong>n Prozessen um (Scheduling).<br />

Durch dieses Verhalten wird ein Programm mit Threads auf einem Einprozessor-System<br />

eher langsamer laufen als das gleiche Programm ohne Threads.<br />

Aus Vergleichsgrün<strong>de</strong>n wur<strong>de</strong> <strong>de</strong>r Warshall-Algorithmus mit und ohne Threads implementiert.<br />

Es stellte sich heraus, dass <strong>de</strong>r Warshall-Algorithmus mit Threads auch auf<br />

einem Multiprozessor-System langsamer ist als ohne Threads. Ein Grund dafür ist die<br />

Synchronisation, die für <strong>de</strong>n Ablauf <strong>de</strong>s Warshall-Algorithmus notwendig ist. Außer<strong>de</strong>m<br />

stellte sich heraus, dass die Problemgröße zu gering ist, als dass sich die Verwendung<br />

von Threads positiv auf die Laufzeit auswirken könnte.<br />

Um zu überprüfen, ob dies die einzigen Grün<strong>de</strong> sind, wur<strong>de</strong> ein Testprogramm geschrieben,<br />

welches keiner Kommunikation bedarf. Das Programm besteht aus zwei Prozessen,<br />

die 1000000000 mal eine <strong>Ra</strong>ndom-Variable bestimmen, sie aufaddiert und modulo rechnet.<br />

Das charakteristische Programmfragment ist im Folgen<strong>de</strong>m gegeben.<br />

Listing 5.1: Effizienztest für Threads in C<br />

1 #<strong>de</strong>fine ANZAHL WIEDERHOLUNGEN 100000000<br />

2<br />

3 void some thread (void∗ some){<br />

4 int i , x1 , x2 ;<br />

5 for ( i =0; i


Es stellte sich heraus, dass sich mit Threads eine Laufzeitverbesserung erreichen lässt<br />

(siehe Tabelle 5.1). Allerdings stellte sich auch heraus, dass Programme mit hohem<br />

Kommunikationsaufwand und geringen Problemgrößen nicht dazu geeignet sind, mit<br />

Threads parallelisiert zu wer<strong>de</strong>n.<br />

Prozessoren Intel ohne Threads mit Threads<br />

Singleprozessor P4 Hyperthreading 3,2 GHz 94 s 80 s<br />

Dualprozessor Xeon Hyperthreading 3,0 GHz 80 s 41 s<br />

Tabelle 5.1.: Ausführungszeiten ohne Threads und mit Threads<br />

Die in Kapitel 3 betrachteten <strong>Algorithmen</strong> haben alle einen hohen Kommunikationsaufwand<br />

und sind zu<strong>de</strong>m auf ein synchrones System ausgelegt. Aus diesem Grund sind<br />

diese <strong>Algorithmen</strong> nicht geeignet, um mit Hilfe von Threads parallelisiert zu wer<strong>de</strong>n.<br />

Der kryptographische Algorithmus aus Kapitel 4 hat einen geringeren Kommunikationsaufwand,<br />

allerdings sind die Berechnungen nicht sehr aufwendig. Eine Parallelisierung<br />

ist hier also möglich aber nicht sinnvoll. Außer<strong>de</strong>m bedarf die beschriebene Initialisierung<br />

und Endberechnung wie<strong>de</strong>r eines synchronen Systems. Ein Thread kann also nur<br />

sinnvoll zwischen Initialisierung und Endrechnung betrieben wer<strong>de</strong>n.<br />

Der Warshall-Algorithmus wur<strong>de</strong> auf verschie<strong>de</strong>ne Arten implementiert, um zu testen, ob<br />

eine geeignete Implementierung die Laufzeit verbessert. Die meisten Implementierungen<br />

benötigten ungefähr die gleiche Laufzeit, außer <strong>de</strong>m modularen Warshall-Algorithmus.<br />

Dieser stellte sich als sehr laufzeitineffizient heraus. Es wur<strong>de</strong> bei <strong>de</strong>n weiteren <strong>Algorithmen</strong><br />

<strong>de</strong>swegen auf eine Modularisierung und Kapselung verzichtet.<br />

Der Hirschberg wur<strong>de</strong> mit Hilfe von structs implementiert. Dabei gab es ein Array (<strong>de</strong>n<br />

GCA) von structs (<strong>de</strong>n Zeilen), welche die Eingabe bil<strong>de</strong>ten. Alle weiteren Schritte wur<strong>de</strong>n<br />

sequentiell abgearbeitet. Programmiert man einen parallelen Algorithmus sequentiell,<br />

so muss je<strong>de</strong> parallele Anweisung durch eine Schleife ersetzt wer<strong>de</strong>n.<br />

Um Datenkonflikte (die bei synchroner Parallelität im Programm nicht auftreten) zu vermei<strong>de</strong>n,<br />

müssen Variablen doppelt gehalten wer<strong>de</strong>n. Im Schritt <strong>de</strong>r parallelen Verän<strong>de</strong>rung<br />

wird die Än<strong>de</strong>rung nicht in die eigentliche Variable geschrieben, son<strong>de</strong>rn in <strong>de</strong>ren<br />

Kopie. Nach Durchlauf <strong>de</strong>r Schleife muss dann das Ergebnis in die Variable zurückgespeichert<br />

wer<strong>de</strong>n.<br />

Im konkreten Fall wur<strong>de</strong> mit Pointern gearbeitet und nach einem Durchlauf <strong>de</strong>r Pointer<br />

auf die ak<strong>tu</strong>elle Variable umgebogen.<br />

Dieses Verfahren wur<strong>de</strong> nicht nur bei <strong>de</strong>m Algorithmus von Hirschberg angewen<strong>de</strong>t, son<strong>de</strong>rn<br />

auch bei <strong>de</strong>n an<strong>de</strong>ren <strong>Algorithmen</strong>. Eine Ausnahme bil<strong>de</strong>te dabei die Anwendung<br />

<strong>de</strong>s Chinesischen Restsatzes, da dieser keine Kommunikation bedarf. Soll das Programm<br />

für die Anwendung <strong>de</strong>s Chinesischen Restsatzes parallelisiert wer<strong>de</strong>n, muss lediglich die<br />

Schleife durch eine parallele Anweisung ersetzt wer<strong>de</strong>n.<br />

100


6. Zusammenfassung und Ausblick<br />

In dieser Arbeit wur<strong>de</strong> anfangs ein Überblick über momentan verwen<strong>de</strong>te parallele Mo<strong>de</strong>lle<br />

geliefert. Die Auswahl umfasst hierbei die P-RAM, die parallelen Pointer-Maschinen<br />

und die Zellularautomaten. Innerhalb <strong>de</strong>r Zellularautomaten wur<strong>de</strong> <strong>de</strong>r ”<br />

Global Cellular<br />

Automata “ (GCA) als Mo<strong>de</strong>ll vorgestellt. Dieser wur<strong>de</strong> im weiteren verwen<strong>de</strong>t.<br />

In Kapitel 3 und Kapitel 4 wur<strong>de</strong>n dann <strong>Algorithmen</strong> auf <strong>de</strong>m GCA mo<strong>de</strong>lliert. Im<br />

<strong>Ra</strong>hmen <strong>de</strong>r Mo<strong>de</strong>llierungen <strong>de</strong>r Graphen-<strong>Algorithmen</strong> in Kapitel 3 wur<strong>de</strong> auch eine<br />

Speicherzelle entwickelt. Diese ist als Konstrukt portabel und erlaubt es <strong>de</strong>m GCA,<br />

einen gemeinsamen Speicher zu simulieren. Es können allerdings keine Daten in <strong>de</strong>n<br />

Speicher geschrieben wer<strong>de</strong>n, son<strong>de</strong>rn die Speicherzelle muss sich aktiv die Daten bei<br />

<strong>de</strong>n Zellen holen und an <strong>de</strong>n von <strong>de</strong>n Zellen vorgeschriebenen Platz schreiben. Auch<br />

gestaltet sich ein Lese-Zugriff auf <strong>de</strong>n Speicher schwerer, da eine Zelle erst die Stelle<br />

angeben muss, von <strong>de</strong>r sie lesen möchte, bevor die Speicherzelle <strong>de</strong>n Wert bereitstellt.<br />

Trotz<strong>de</strong>m ermöglicht die Speicherzelle eine Mo<strong>de</strong>llierung von P-RAM-Programmen auf<br />

<strong>de</strong>m GCA. Der Autorin sind momentan keine Fälle bekannt, in <strong>de</strong>nen ein sofortiger<br />

wahlfreier Zugriff auf die Daten <strong>de</strong>s Speichers nötig ist.<br />

Ein weiteres neu entwickeltes Konstrukt ist die Bus-Zelle. Erscheint es in einer Anwendung<br />

als sinnvoll, einen Bus zu verwen<strong>de</strong>n, ist dies aber aus diversen Grün<strong>de</strong>n nicht<br />

möglich, so kann die Bus-Zelle verwen<strong>de</strong>t wer<strong>de</strong>n. Allerdings ist bei <strong>de</strong>r Bus-Zelle eine<br />

geeignete Verwal<strong>tu</strong>ng notwendig, damit die Bus-Zelle weiß, bei welcher Zelle sie momentan<br />

lesen muss. Im Falle <strong>de</strong>s Warshall-Algorithmus ist das einfach, allerdings gestaltet<br />

sich die Verwendung <strong>de</strong>s Busses nicht immer einfach.<br />

Für eine Entscheidung, ob die Bus-Zelle sinnvoll verwendbar ist o<strong>de</strong>r nicht, kann das<br />

in Kapitel 2.3.4 vorgestellte Schema verwen<strong>de</strong>t wer<strong>de</strong>n. Es eignen sich vor allem solche<br />

<strong>Algorithmen</strong>, <strong>de</strong>ren Verbindungen möglichst von nichts o<strong>de</strong>r nur <strong>de</strong>r Zeit (und even<strong>tu</strong>ell<br />

wie beim Warshall-Algorithmus von <strong>de</strong>n Daten) abhängen.<br />

Im weiteren sind die in Kapitel 3 gegebenen <strong>Algorithmen</strong> ein gutes Beispiel dafür, dass<br />

parallel entwickelte <strong>Algorithmen</strong> meist effizienter sind als parallelisierte sequentielle <strong>Algorithmen</strong>.<br />

Die zeigt sich u. a. in <strong>de</strong>m Vergleich zwischen Warshall-Algorithmus (O(n))<br />

und <strong>de</strong>m Algorithmus von Hirschberg (O(log 2 (n))).<br />

Allerdings ist die Laufzeitkomplexität meist trügerisch. Die <strong>Algorithmen</strong> <strong>de</strong>r Klasse O(n)<br />

beispielsweise müssen erst ab einem Wert n 0 langsamer o<strong>de</strong>r gleich schnell wie n wachsen.<br />

Dieses n 0 wird in <strong>de</strong>r Laufzeitkomplexität allerdings nicht angegeben.<br />

Betrachtet man laufzeiteffiziente <strong>Algorithmen</strong> genauer, kann man also u. U. feststellen,<br />

dass dieses n 0 größer als normale Problemgrößen ist. Ein Beispiel hierfür ist Coles Al-<br />

101


gorithmus, <strong>de</strong>r erst ab einem n 0 ≥ 2 70 genauso langsam wie log(n) wächst. Es erscheint<br />

in solchen Fällen sinnvoller, einen langsameren Algorithmus (statt Coles Algorithmus z.<br />

B. das Bitonische Mischen) zu verwen<strong>de</strong>n, <strong>de</strong>r innerhalb <strong>de</strong>r gewünschten Problemgröße<br />

ein besseres Laufzeitverhalten aufweist.<br />

Auch in Zukunft wird es interessant sein, parallele <strong>Algorithmen</strong> zu entwickeln. Da zur<br />

heutigen Zeit wie<strong>de</strong>r vermehrt parallele Architek<strong>tu</strong>ren entworfen und gebaut wer<strong>de</strong>n,<br />

erscheint es auch sinnvoll, die Möglichkeiten dieser Architek<strong>tu</strong>ren genauer zu s<strong>tu</strong>dieren.<br />

Ein Algorithmus, <strong>de</strong>r sich optimal an die Gegebenheiten <strong>de</strong>r Architek<strong>tu</strong>r anpasst und für<br />

sie entwickelt wur<strong>de</strong>, wird in <strong>de</strong>n meisten Fällen um einiges besser sein als ein allgemeiner<br />

Algorithmus. Da solche <strong>Algorithmen</strong> jedoch nicht mehr portabel sind, ist zu bezweifeln,<br />

dass eine Architek<strong>tu</strong>r es schafft, einen so großen Ausbrei<strong>tu</strong>ngsgrad zu erreichen, dass<br />

sich solch ein Aufwand lohnt.<br />

Bis zu <strong>de</strong>m Zeitpunkt an <strong>de</strong>m es eine ”<br />

universelle“ Architek<strong>tu</strong>r gibt, die mehr als 50%<br />

<strong>de</strong>s Marktanteils ausmacht, wer<strong>de</strong>n nur teure Speziallösungen wirklich auf die Architek<strong>tu</strong>r<br />

angepasst wer<strong>de</strong>n. Es ist zu erwarten, dass solange es keine solche Architek<strong>tu</strong>r gibt,<br />

auch weiterhin <strong>Algorithmen</strong> auf Automatenmo<strong>de</strong>llen entwickelt wer<strong>de</strong>n. Denn Automatenmo<strong>de</strong>lle<br />

erlauben es, dass man einen Algorithmus entwickeln kann, ohne die genaue<br />

Architek<strong>tu</strong>r zu kennen, auf <strong>de</strong>m das Programm später läuft. Wer <strong>de</strong>n Algorithmus dann<br />

implementieren will, kann sich immer noch entschei<strong>de</strong>n, ob er dies sehr architek<strong>tu</strong>rnah<br />

o<strong>de</strong>r so abstrakt wie möglich realisieren möchte.<br />

102


A. Traversierungsstrategien<br />

Im folgen<strong>de</strong>n sollen einige Traversierungsstrategien angegeben wer<strong>de</strong>n, die verwen<strong>de</strong>t<br />

wer<strong>de</strong>n, um schwierige Probleme zu lösen.<br />

A.1. Backtracking<br />

Backtracking liefert einen Algorithmus, um struk<strong>tu</strong>riert alle möglichen Lösungen durchzutesten.<br />

Dabei wird immer versucht, auf <strong>de</strong>r bislang erzielten Teillösung aufbauend, <strong>de</strong>m<br />

En<strong>de</strong>rgebnis näher zu kommen. Entsteht dabei ein Konflikt, so wird die vorhergehen<strong>de</strong><br />

Entscheidung zurückgenommen und somit die Ausgangslage verän<strong>de</strong>rt. Das Verfahren<br />

soll anhand <strong>de</strong>s Acht-Damen-Problems erläutert wer<strong>de</strong>n. Aufgabe ist es dabei, auf einem<br />

8x8-Schachbrett acht Damen so zu positionieren, dass keine Dame eine an<strong>de</strong>re schlagen<br />

kann.<br />

Die erste Dame wird in die obere linke Ecke gesetzt, Abb. A.1 ver<strong>de</strong>utlicht zu<strong>de</strong>m auch<br />

noch alle Fel<strong>de</strong>r, auf die keine Dame mehr gestellt wer<strong>de</strong>n kann.<br />

Die zweite Dame kann nun we<strong>de</strong>r auf <strong>de</strong>m ersten noch <strong>de</strong>m zweiten Feld positioniert<br />

wer<strong>de</strong>n, also lan<strong>de</strong>t sie auf <strong>de</strong>m dritten Feld <strong>de</strong>r zweiten Zeile. In Abb. A.2 ist die Dame<br />

schon positioniert und es wur<strong>de</strong> wie<strong>de</strong>r zum Verständnis markiert, welche Fel<strong>de</strong>r alle<br />

für die zukünftige Positionierung von Damen gesperrt ist. Die dritte Dame kann jetzt<br />

erst an die vierte Stelle <strong>de</strong>r dritten Zeile positioniert wer<strong>de</strong>n, auch dies wird wie<strong>de</strong>r mit<br />

einer Abbildung ver<strong>de</strong>utlicht. Abb. A.3 zeigt das Schachbrett nach <strong>de</strong>r Positionierung<br />

Abbildung A.1.: Die erste Dame wur<strong>de</strong> in die obere linke Ecke positioniert. Da Damen<br />

gera<strong>de</strong>aus und diagonal gehen können, ist die restliche Zeile sowie die<br />

Spalte und die Diagonale als gesperrt markiert. Diese Markierung dient<br />

nur <strong>de</strong>m Verständnis, <strong>de</strong>r Algorithmus liefert sie nicht.<br />

103


Abbildung A.2.: Die zweite Dame wur<strong>de</strong> an die dritte Position <strong>de</strong>r zweiten Zeile positioniert.<br />

Da Damen gera<strong>de</strong>aus und diagonal gehen können, ist die restliche<br />

Zeile sowie die Spalte und die Diagonale als gesperrt markiert.<br />

Abbildung A.3.: Die dritte Dame wur<strong>de</strong> auf das erste Feld in <strong>de</strong>r dritten Zeile gesetzt,<br />

welches keinen Konflikt auslöst. Es wur<strong>de</strong>n wie<strong>de</strong>r alle Fel<strong>de</strong>r markiert,<br />

die von <strong>de</strong>r dritten Dame geschlagen wer<strong>de</strong>n.<br />

<strong>de</strong>r dritten Dame. Im nächsten Schritt wird dann die vierte Dame in <strong>de</strong>r vierten Zeile<br />

positioniert.<br />

Das Schachbrett nach <strong>de</strong>m positionieren <strong>de</strong>r vierten Dame zeigt Abb. A.4.<br />

Nach<strong>de</strong>m die fünfte Dame positioniert wur<strong>de</strong>, sieht das Feld wie in Abb. A.5 aus. Durch<br />

die Markierung wird <strong>de</strong>utlich, dass die sechste Dame nicht mehr gesetzt wer<strong>de</strong>n kann,<br />

ohne dass sie sofort geschlagen wird. Eine korrekte Lösung ist also bei <strong>de</strong>r vorher getroffenen<br />

Annahme nicht möglich.<br />

Da die sechste Dame also nicht positioniert wer<strong>de</strong>n kann, muss die Positionierung <strong>de</strong>r<br />

fünften Dame falsch sein. Also wird die fünfte Dame wie<strong>de</strong>r vom Brett genommen und<br />

neu positioniert. Dabei wird darauf geachtet, dass die fünfte Dame nicht mehr an die<br />

Stelle kommen kann, an <strong>de</strong>r sie davor saß. Dieses Zurücknehmen einer Entscheidung<br />

kennzeichnet das Backtracking. Allerdings wird auch durch die Neupositionierung <strong>de</strong>r<br />

fünften Dame (s. Abb. A.6) kein Feld in <strong>de</strong>r sechsten Zeile frei, weshalb die fünfte Dame<br />

komplett vom Feld genommen wird und die vierte Dame neu positioniert wird. Die neue<br />

Si<strong>tu</strong>ation ist in Abb. A.7 dargestellt.<br />

Diese Schritte <strong>de</strong>s Setzens und Zurücknehmens von Damen wird solange wie<strong>de</strong>rholt, bis<br />

104


Abbildung A.4.: Die vierte Dame wur<strong>de</strong> auf das erste Feld in <strong>de</strong>r dritten Zeile gesetzt,<br />

welches keinen Konflikt auslöst. Es wur<strong>de</strong>n wie<strong>de</strong>r alle Fel<strong>de</strong>r markiert,<br />

die von <strong>de</strong>r dritten Dame geschlagen wer<strong>de</strong>n.<br />

Abbildung A.5.: Die fünfte Dame ist auf <strong>de</strong>m Feld positioniert wor<strong>de</strong>n und es ist ersichtlich,<br />

dass die sechste Dame nicht mehr positioniert wer<strong>de</strong>n kann.<br />

Abbildung A.6.: Die fünfte Dame ist auf <strong>de</strong>m Feld reposiotioniert wor<strong>de</strong>n, in <strong>de</strong>r sechsten<br />

Zeile kann trotz<strong>de</strong>m keine Dame positioniert wer<strong>de</strong>n. Es ist also<br />

ein weiterer Backtracking-Schritt notwendig.<br />

105


Abbildung A.7.: Nach<strong>de</strong>m die vierte Dame auf <strong>de</strong>m Feld repositioniert wur<strong>de</strong>, gibt es<br />

zwei interessante Stellen, an die die fünfte Dame positioniert wer<strong>de</strong>n<br />

kann.<br />

die achte Dame positioniert wur<strong>de</strong>, dann ist eine Lösung für das Problem gefun<strong>de</strong>n.<br />

Anzumerken ist, dass <strong>de</strong>r Algorithmus auch die Stellen, die in <strong>de</strong>n Abbildungen mit<br />

einem * gekennzeichnet sind, abtestet und somit viele Tests vonnöten sind. Beim Acht-<br />

Damen-Problem ist die Laufzeit aus zwei Grün<strong>de</strong>n erträglich:<br />

1. Die Anzahl <strong>de</strong>r Damen ist begrenzt und sehr klein.<br />

2. Es existiert mehr als eine Lösung, je<strong>de</strong> Lösung wird als gleichwertig angesehen,<br />

weshalb nach <strong>de</strong>m Fin<strong>de</strong>n einer Lösung <strong>de</strong>r Algorithmus been<strong>de</strong>t ist.<br />

Diese zwei Punkte gelten nicht für alle Probleme, die mittels Backtracking gelöst wer<strong>de</strong>n<br />

müssen, so dass das exponentielle Verhalten <strong>de</strong>s Backtrackings die meisten Lösungen<br />

uninteressant machen.<br />

A.2. Divi<strong>de</strong> and Conquer<br />

Für das Divi<strong>de</strong>-and-Conquer-Verfahren eignen sich solche Problemstellungen, die sich in<br />

unabhängige Teilprobleme zerlegen lassen. Der bekannteste Algorithmus, <strong>de</strong>r nach <strong>de</strong>m<br />

Divi<strong>de</strong>-and-Conquer-Verfahren arbeitet, ist <strong>de</strong>r Quicksort.<br />

Quicksort macht eine Vorsortierung auf <strong>de</strong>r gegebenen Liste und teilt diese dann in<br />

zwei Teillisten. Je<strong>de</strong> <strong>de</strong>r Teillisten kann für sich allein sortiert wer<strong>de</strong>n, und am Schluss<br />

müssen die Teillisten nur noch zusammengehängt wer<strong>de</strong>n, um das korrekte Gesamtergebnis<br />

zu erhalten. Divi<strong>de</strong>-and-Conquer wird in <strong>de</strong>r Regel rekursiv verwen<strong>de</strong>t, da die<br />

Lösung so verständlicher ist 1 . Im Falle <strong>de</strong>s Quicksorts be<strong>de</strong>utet dies, dass je<strong>de</strong> Liste vorsortiert<br />

wird und dann auf die Teillisten wie<strong>de</strong>r <strong>de</strong>r Quicksort angewen<strong>de</strong>t wird. Dabei<br />

1 Sequentielle rekursive Lösungen weisen meist ein schlechteres Laufzeitverhalten auf als iterative<br />

Lösungen. Trotz<strong>de</strong>m verzichtet man meist darauf, die <strong>Algorithmen</strong> iterativ zu programmieren, da<br />

rekursive Programme übersichtlicher und somit für <strong>de</strong>n Fachmann leichter lesbar sind. Grundsätzlich<br />

können alle rekursiven Programme in iterative Programme umgearbeitet wer<strong>de</strong>n, allerdings ist<br />

oft <strong>de</strong>r Aufwand dafür recht groß und das Programm wird unleserlich.<br />

106


Abbildung A.8.: Die Startinitialisierung <strong>de</strong>s Quicksorts. Das Pivot ist durch einen<br />

dicken Kasten markiert, die ersten zu vergleichen<strong>de</strong>n Elemente durch<br />

die Zeiger i und j. Das Pivot-Element wird anfangs nicht verglichen<br />

son<strong>de</strong>rn am En<strong>de</strong> <strong>de</strong>s Vorsortierens an <strong>de</strong>n korrekten Platz getauscht.<br />

Abbildung A.9.: Die Vorsortierung wur<strong>de</strong> been<strong>de</strong>t. Da j links von i steht, wird die Vorsortierung<br />

abgeschlossen.<br />

wird eine einelementige Liste als sortiert betrachtet. Die Länge <strong>de</strong>r Liste bietet also das<br />

Abbruchkriterium, mit <strong>de</strong>m die Rekursion kontrolliert been<strong>de</strong>t wird.<br />

Zur Veranschaulichung soll die Liste (7, 5, 10, 1, 3, 4, 9, 6, 12, 15, 2, 20, 22, 14) sortiert wer<strong>de</strong>n.<br />

Dazu wird ein Vergleichselement, das sogenannte Pivot, gewählt und alle Werte<br />

mit diesem verglichen. Werte die kleiner als das Pivot sind, wer<strong>de</strong>n nach links, größere<br />

Werte nach rechts sortiert. Wie dies genau geschieht hängt von <strong>de</strong>r jeweiligen Implementierung<br />

ab, hier wer<strong>de</strong>n zwei Zeiger i und j verwen<strong>de</strong>t, die <strong>de</strong>n Vergleich kleinergleich<br />

(i) bzw. größer (j) durchführen und dazu am linken En<strong>de</strong> (i) bzw. rechten En<strong>de</strong> (j) <strong>de</strong>r<br />

Liste starten. Als Pivot wird hier das linke Element <strong>de</strong>r Liste gewählt. Die Ausgangssi<strong>tu</strong>ation<br />

ist in Abbildung A.8 dargestellt. Das Pivot-Element ist dick umran<strong>de</strong>t, i zeigt<br />

auf das zweite Feld von links (da das linke Element das Pivot ist, erübrigt sich hier ein<br />

Vergleich), j auf das letzte Element <strong>de</strong>r Liste.<br />

Im Laufe <strong>de</strong>s Algorithmus wird <strong>de</strong>r linke Zeiger i immer eine Stelle nach rechts gerückt,<br />

wenn <strong>de</strong>r Wert auf <strong>de</strong>n i momentan zeigt kleinergleich <strong>de</strong>m Pivot-Element ist. Ist <strong>de</strong>r<br />

Vergleich nicht erfolgreich (ist also <strong>de</strong>r Wert an <strong>de</strong>r Stelle i größer als das Pivot-Element),<br />

so bleibt i stehen und <strong>de</strong>r Zeiger j wird solange nach links gerückt, bis entwe<strong>de</strong>r ein Wert<br />

gefun<strong>de</strong>n wird, <strong>de</strong>r kleinergleich <strong>de</strong>m Pivot ist o<strong>de</strong>r bis <strong>de</strong>r Zeiger j links vom Zeiger i<br />

steht. Bleibt j auf einem Feld stehen, <strong>de</strong>ssen Inhalt kleinergleich <strong>de</strong>m Pivot ist, so wird<br />

getauscht, ansonsten kann <strong>de</strong>r rekursive Aufruf erfolgen.<br />

Für <strong>de</strong>n rekursiven Aufruf wird zuerst das Pivot-Element mit <strong>de</strong>m Wert an <strong>de</strong>r j-ten<br />

Stelle getauscht. Dann wird Quicksort auf die Liste von <strong>de</strong>r ersten Stelle bis zur Stelle j-1<br />

und von <strong>de</strong>r j+1-ten Stelle bis zur letzten Stelle <strong>de</strong>r Liste aufgerufen. Abbildung A.9 zeigt<br />

die Liste nach <strong>de</strong>r Vorsortierung, wobei das Pivot-Element noch nicht getauscht wur<strong>de</strong>.<br />

In Abbildung A.10 wur<strong>de</strong> <strong>de</strong>r Tausch <strong>de</strong>s Pivots bereits durchgeführt, das Pivot-Element<br />

steht an <strong>de</strong>r korrekten Stelle. Zu<strong>de</strong>m wur<strong>de</strong> die Liste aufgeteilt, dies wird durch die Zeiger<br />

i1, j1, i2 und j2 ver<strong>de</strong>utlicht, welche für die linke bzw. rechte Teilliste die Grenzen mar-<br />

107


kieren. Für bei<strong>de</strong> Teillisten wur<strong>de</strong> das Pivot dick umran<strong>de</strong>t. Für diese bei<strong>de</strong>n Teillisten<br />

wird wie<strong>de</strong>r wie beschrieben die Vorsortierung ausgeführt und wie<strong>de</strong>r geteilt. Dabei wird<br />

bei je<strong>de</strong>r Vorsortierung min<strong>de</strong>stens ein Element (das Pivot-Element) endgültig sortiert.<br />

Abbildung A.10.: Das Pivot wur<strong>de</strong> an seine endgültige Stelle getauscht und die Liste in<br />

zwei Teillisten zerlegt. Die umran<strong>de</strong>ten Fel<strong>de</strong>r geben die Pivots für die<br />

Teillisten an, i1,j1,i2 und j2 bezeichnen die Grenzen <strong>de</strong>r Teillisten.<br />

Der Quicksort wird mit diesen Teillisten rekursiv ausgeführt.<br />

108


B. <strong>Parallele</strong> Algorithmische Techniken<br />

Bei parallelen Systemen gibt es zusätzlich zu <strong>de</strong>n bekannten sequentiellen Techniken<br />

(s. vorheriges Kapitel) noch die Möglichkeit, die Kommunikation <strong>de</strong>r Prozessoren nach<br />

bestimmten Prinzipien aufzubauen und dadurch schnelle Lösungen zu erhalten. In <strong>de</strong>n<br />

nachfolgen<strong>de</strong>n Abschnitten sollen einige <strong>de</strong>r Techniken, die im <strong>Ra</strong>hmen dieser Ausarbei<strong>tu</strong>ng<br />

verwen<strong>de</strong>t o<strong>de</strong>r erwähnt wer<strong>de</strong>n, näher erklärt wer<strong>de</strong>n. Meist fin<strong>de</strong>t sich bereits<br />

im Text eine kurze Erläuterung zu <strong>de</strong>n Metho<strong>de</strong>n, allerdings wer<strong>de</strong>n hier noch einmal<br />

an<strong>de</strong>re Beispiele angeführt, um die Metho<strong>de</strong>n weiter zu ver<strong>de</strong>utlichen. Die benötigte Anzahl<br />

an Prozessoren kann bei <strong>de</strong>n meisten Metho<strong>de</strong>n verringert wer<strong>de</strong>n, aber in diesem<br />

Anhang wird nicht näher auf diese Möglichkeiten eingegangen, <strong>de</strong>r interessierte Leser sei<br />

an [GR98] verwiesen.<br />

B.1. Die Balanced-Binary-Tree-Technik<br />

Grundlage <strong>de</strong>r Balanced-Binary-Tree-Technik ist ein vollständiger Binärbaum. Dieser<br />

Baum wird <strong>de</strong>rmaßen belegt, dass in die Blätter die Werte geschrieben wer<strong>de</strong>n, die bearbeitet<br />

wer<strong>de</strong>n sollen. Die inneren Knoten wer<strong>de</strong>n durch Prozessoren dargestellt, die<br />

auf <strong>de</strong>n Werten ihrer Söhne einen Teil <strong>de</strong>r gefor<strong>de</strong>rten Berechnung ausführen. Dementsprechend<br />

arbeiten zuerst die Prozessoren, die in <strong>de</strong>r untersten Ebene angeordnet sind,<br />

und erst wenn diese ihr Ergebnis haben, können die Prozessoren <strong>de</strong>r Ebene darüber<br />

ihre Berechnung ausführen. Probleme, die sich effizient mit <strong>de</strong>r Balanced-Binary-Tree-<br />

Technik lösen lassen, sind das Fin<strong>de</strong>n <strong>de</strong>s Minimums, <strong>de</strong>s Maximums o<strong>de</strong>r <strong>de</strong>r Summe<br />

einer gegebenen Menge von Zahlen.<br />

Das erfolgreiche Auffin<strong>de</strong>n <strong>de</strong>s Minimums <strong>de</strong>r Liste mit Hilfe <strong>de</strong>r Technik wur<strong>de</strong> bereits<br />

in Kapitel 3.1.3.2 vorgestellt, ein Beispiel für das Maximum fin<strong>de</strong>t sich u. a. in [GR98].<br />

Aus diesem Grund soll hier das Beispiel zur Bestimmung <strong>de</strong>r Summe anhand <strong>de</strong>r Abbildung<br />

B.1 veranschaulicht wer<strong>de</strong>n. Die zu untersuchen<strong>de</strong>n Werte stehen in <strong>de</strong>n Blättern<br />

<strong>de</strong>s Baums und je<strong>de</strong>r Knoten addiert seine Söhne. Damit ist die Summe <strong>de</strong>s Knotens berechnet<br />

und sein Vater kann mit diesem Teilergebnis weiterrechnen. Die Wurzel enthält<br />

am En<strong>de</strong> das Gesamtergebnis. Möchte man die Summe einer Liste bestimmen, <strong>de</strong>ren<br />

Länge keine Zweierpotenz ist, so müssen die freien Blätter mit <strong>de</strong>m Wert 0 gefüllt wer<strong>de</strong>n,<br />

damit das Ergebnis nicht verfälscht wird.<br />

109


Abbildung B.1.: In <strong>de</strong>n Blättern stehen die Werte <strong>de</strong>ren Summe gebil<strong>de</strong>t wer<strong>de</strong>n soll.<br />

Je<strong>de</strong>r Prozessor (innerer Knoten) berechnet die Summe für seine Nachfolger.<br />

Das Ergebnis steht nach log(n) Schritten in <strong>de</strong>r Wurzel.<br />

Der Pseudoco<strong>de</strong> zur Bestimmung <strong>de</strong>r Summe lautet:<br />

Listing B.1: Berechnung <strong>de</strong>r Summe mit <strong>de</strong>r Balanced-Binary-Tree-Technik<br />

1 for k


Abbildung B.2.: Je<strong>de</strong>r Prozessor repräsentiert einen Wert <strong>de</strong>r Liste. Für je<strong>de</strong>s Listenelement<br />

wird nacheinan<strong>de</strong>r <strong>de</strong>r Abstand zum Listenen<strong>de</strong> bestimmt. Die<br />

Graphik zeigt die einzelnen Schleifendurchläufe <strong>de</strong>r Doubling-Technik,<br />

die unterste Abbildung entspricht <strong>de</strong>m En<strong>de</strong>rgebnis.<br />

B.3. Die Divi<strong>de</strong>-and-Conquer-Technik<br />

Die parallele Divi<strong>de</strong>-and-Conquer-Technik ist <strong>de</strong>r seriellen sehr ähnlich, allerdings wird<br />

bei <strong>de</strong>r parallelen gefor<strong>de</strong>rt, dass die Teilprobleme einer Rekursionsebene sich vollständig<br />

unabhängig voneinan<strong>de</strong>r lösen lassen 1 . Die Balanced-Binary-Tree-Technik kann als ein<br />

Spezialfall <strong>de</strong>r Divi<strong>de</strong>-and-Conquer-Technik angesehen wer<strong>de</strong>n, da auch dort die Knoten<br />

einer Ebene die Berechnungen unabhängig voneinan<strong>de</strong>r ausführen. Allerdings verfolgt<br />

die Balanced-Binary-Tree-Technik die Bottom-up-Metho<strong>de</strong> 2 , während die Divi<strong>de</strong>-and-<br />

Conquer-Technik die Top-down-Metho<strong>de</strong> 3 verwen<strong>de</strong>t. Die Divi<strong>de</strong>-and-Conquer-Technik<br />

kann für alle Probleme angewen<strong>de</strong>t wer<strong>de</strong>n, die auch bei <strong>de</strong>r Balanced-Binary-Tree-<br />

Technik angeführt wur<strong>de</strong>n (natürlich sind dies nicht die einzigen Probleme, für die die<br />

bei<strong>de</strong>n Techniken sich eignen, allerdings sind sie einfach und damit leicht nachvollziehbar).<br />

So kann das Minimum einer Liste z. B. dadurch bestimmt wer<strong>de</strong>n, dass man das<br />

Minimum in <strong>de</strong>r linken Hälfte und in <strong>de</strong>r rechten Hälfte <strong>de</strong>r Liste bestimmt. Danach<br />

muss aus diesen bei<strong>de</strong>n Werten nur noch das Minimum bestimmt wer<strong>de</strong>n und das Gesamtergebnis<br />

ist gefun<strong>de</strong>n.<br />

1 Meist wird diese Bedingung auch im sequentiellen Fall erfüllt, da die Probleme dadurch überschaubarer<br />

bleiben<br />

2 Bei <strong>de</strong>r Bottom-up-Metho<strong>de</strong> wer<strong>de</strong>n zuerst die Teilprobleme gelöst und diese nach und nach zu <strong>de</strong>r<br />

Gesamtlösung zusammengesetzt.<br />

3 Bei <strong>de</strong>r Top-down-Metho<strong>de</strong> wird das zu lösen<strong>de</strong> Problem solange weiter zerlegt, bis die gefun<strong>de</strong>nen<br />

Teilprobleme leicht lösbar sind.<br />

111


C. Implementierungen <strong>de</strong>r <strong>Algorithmen</strong><br />

C.1. Warshall-Algorithmus<br />

Listing C.1: Implementierung <strong>de</strong>s Warshall-Algorithmus<br />

1<br />

2 #inclu<strong>de</strong> <br />

3 #inclu<strong>de</strong> <br />

4 #inclu<strong>de</strong> <br />

5 #inclu<strong>de</strong> <br />

6 #<strong>de</strong>fine N 30<br />

7 #<strong>de</strong>fine DATEI ”Matrix0 . dat”<br />

8 #<strong>de</strong>fine ANZAHL WIEDERHOLUNGEN 1<br />

9 #<strong>de</strong>fine Debug 1<br />

10 FILE ∗ f i l e ;<br />

11 struct handle {<br />

12 int row [N] ;<br />

13 int ID ;<br />

14 };<br />

15<br />

16 int takt [N] ;<br />

17 int currentID ;<br />

18 int done ;<br />

19 int Bus [N] ;<br />

20 int ak<strong>tu</strong>al ;<br />

21 int NumReady;<br />

22 int Result [N] [N] ;<br />

23<br />

24 void c e l l ( struct handle ∗ give ) {<br />

25 int i ;<br />

26 int ownRow[N] ;<br />

27 int ownID=(∗give ) . ID ;<br />

28 for ( i =0; i


67 void main () {<br />

68 struct handle give [N] ;<br />

69 int i , j , k ;<br />

70 char da<strong>tu</strong>m ;<br />

71 LARGE INTEGER start ticks , en<strong>de</strong> ticks , frequenz ;<br />

72 double t i c k d i f f = 0;<br />

73 for ( i =0; i


24<br />

25 void hirschberg ( int GCA[N] [N] ) {<br />

26 int i , j , k , l ;<br />

27 int Thelp [N] ;<br />

28 int ∗ ak<strong>tu</strong>al ;<br />

29 int ∗ start ;<br />

30 int ∗ old ;<br />

31 int found ; /∗ z e i g t an , ob <strong>de</strong>r gesuchte Eintrag gefun<strong>de</strong>n wur<strong>de</strong> ∗/<br />

32<br />

33 for ( l =1; l


114 int complete [N]={0 ,0 ,0 ,0 ,0 ,0 ,0 ,0};<br />

115 LARGE INTEGER start ticks , en<strong>de</strong> ticks , frequenz ;<br />

116 double t i c k d i f f = 0;<br />

117 QueryPerformanceFrequency(&frequenz ) ;<br />

118 printf (”Beginne mit <strong>de</strong>r Messung <strong>de</strong>r benoetigten Zeit <strong>de</strong>s Hirschbergalgorithmus \n”) ;<br />

119 /∗ I n i t i a l i s i e r u n g Zeitmessung ∗/<br />

120 s t a r t t i c k s . QuadPart = 0;<br />

121 en<strong>de</strong> ticks . QuadPart = 0;<br />

122 QueryPerformanceCounter(& s t a r t t i c k s ) ;<br />

123 hirschberg (GCA) ;<br />

124 /∗ Abschliessen <strong>de</strong>r Zeitmessung ∗/<br />

125 QueryPerformanceCounter(&en<strong>de</strong> ticks ) ;<br />

126 t i c k d i f f = ((double) en<strong>de</strong> ticks . QuadPart − (double) s t a r t t i c k s . QuadPart) /<br />

127 frequenz . QuadPart ;<br />

128 printf (” Benoetigte Zeit in Sekun<strong>de</strong>n %f \n” , t i c k d i f f ) ;<br />

129 printf (”Ergebnis : ”) ;<br />

130 /∗Ausgabe <strong>de</strong>r Komponenten ∗/<br />

131 for ( i =0; i


51 Graph [ last ]=Graph [ 0 ] ;<br />

52 last −−;<br />

53 heap (0 , last ) ;<br />

54 re<strong>tu</strong>rn ret ;<br />

55 }<br />

56<br />

57 void main () {<br />

58 int tree [N] ;<br />

59 int number ;<br />

60 int in , i , j , tested , trunc , keep ;<br />

61 struct vector edge ;<br />

62 struct vector spanntree [N−1];<br />

63 for ( i =0; i


141 number++;<br />

142 in = 1;<br />

143<br />

144 /∗Die Teilbaeume zusammenfuegen . ∗/<br />

145 if ( in==1){<br />

146 for ( j =0; j


56 }<br />

57 Tglobal [ i ] = Cglobal [ to [ i ] ] ;<br />

58 value [ i ] = GCA[ i ] [ to [ i ] ] ;<br />

59 }/∗En<strong>de</strong> erster T e i l s c h r i t t<br />

60 / ∗2. T e i l s c h r i t t von Schritt 1. Auch hier i s t die s e r i e l l e Loesung wie<strong>de</strong>r schlechter<br />

61 als die parallele , das Minimum muss e x p l i z i t gesucht wer<strong>de</strong>n und es muss gespeichert wer<strong>de</strong>n , von<br />

62 wo die Kante kam. ∗/<br />

63 for ( i =0; i 0) ) {<br />

66 doppelt [ i ]= to [ i ] ;<br />

67 }<br />

68 else doppelt [ i ]=N;<br />

69 }<br />

70 for ( i =0; i


146 /∗ Abschliessen <strong>de</strong>r Zeitmessung ∗/<br />

147 QueryPerformanceCounter(&en<strong>de</strong> ticks ) ;<br />

148 t i c k d i f f = ((double) en<strong>de</strong> ticks . QuadPart − (double) s t a r t t i c k s . QuadPart) /<br />

149 frequenz . QuadPart ;<br />

150 printf (” Benoetigte Zeit in Sekun<strong>de</strong>n %f \n” , t i c k d i f f ) ;<br />

151 printf (”Ergebnis : ”) ;<br />

152 /∗Ausgabe <strong>de</strong>r Komponenten ∗/<br />

153 for ( i =0; ikey . points [ 1 ] != key2 . points [ 1 ] ) ) {<br />

42 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

43 }<br />

44 if ( ac<strong>tu</strong>al == NULL | | ac<strong>tu</strong>al −>next == NULL && ( ac<strong>tu</strong>al −>key . points [ 0 ] != key2 . points [ 0 ] | | ac<strong>tu</strong>al<br />

−>key . points [ 1 ] != key2 . points [ 1 ] ) ) {<br />

45 help = malloc ( sizeof ( struct list element ) ) ;<br />

46 (∗ help ) . key = key2 ;<br />

47 help−>value = value ;<br />

48 help−>next = NULL;<br />

49 if ( Liste == NULL) Liste = help ;<br />

50 else ac<strong>tu</strong>al −>next = help ;<br />

51 }<br />

52 else {<br />

53 ac<strong>tu</strong>al −>value = value ;<br />

119


54 }<br />

55 re<strong>tu</strong>rn Liste ;<br />

56 }<br />

57<br />

58 /∗Die Funktion get l i e f e r t zu einem Key <strong>de</strong>n zugehoerigen Value einer Kante . Die Liste b l e i b t dabei<br />

59 unveraen<strong>de</strong>rt . ∗/<br />

60 struct EDGE get ( struct list element ∗Liste , struct EDGE key ) {<br />

61 struct list element ∗ac<strong>tu</strong>al ;<br />

62 struct EDGE err ;<br />

63 int finished ;<br />

64 ac<strong>tu</strong>al = Liste ;<br />

65 finished = 0;<br />

66 err . points [0]= 0;<br />

67 err . points [1]= 0;<br />

68 while( ac<strong>tu</strong>al != NULL && finished == 0) {<br />

69 if ( ac<strong>tu</strong>al −>key . points [ 0 ] == key . points [ 0 ] && ac<strong>tu</strong>al −>key . points [ 1 ] == key . points [ 1 ] ) {<br />

70 finished = 1;<br />

71 re<strong>tu</strong>rn ac<strong>tu</strong>al −>value ;<br />

72 }<br />

73 else ac<strong>tu</strong>al= ac<strong>tu</strong>al −>next ;<br />

74 }<br />

75 re<strong>tu</strong>rn err ;<br />

76 }<br />

77<br />

78 /∗Die Funktion doubling succ fuehrt einen Durchlauf <strong>de</strong>r Doubling −Technik fuer die Successor −Liste<br />

aus . ∗/<br />

79 struct list element ∗ doubling succ ( struct list element ∗ Liste ) {<br />

80 struct list element ∗ ac<strong>tu</strong>al , ∗ start ;<br />

81<br />

82 ac<strong>tu</strong>al = Liste ;<br />

83 start = NULL;<br />

84 while( ac<strong>tu</strong>al != NULL) {<br />

85 start = set ( start , ac<strong>tu</strong>al −>key , get ( Liste , ac<strong>tu</strong>al −>value ) ) ;<br />

86 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

87 }<br />

88 re<strong>tu</strong>rn start ;<br />

89 }<br />

90<br />

91 /∗Fuehrt einen Schritt <strong>de</strong>r doubling −Technik fuer die Kanten aus , dabei wird die Edge−Liste<br />

a k t u a l i s i e r t .<br />

92 Im Laufe <strong>de</strong>s Schritts wird eine modifizierte Kopie <strong>de</strong>r Edge−Liste angelegt und die uebergebene<br />

Liste dann geloescht . ∗/<br />

93<br />

94 struct list element ∗ doubling edge ( struct list element ∗ Liste , struct list element ∗Successor ) {<br />

95 struct list element ∗ac<strong>tu</strong>al , ∗ start ;<br />

96 struct EDGE kante , vergleich ;<br />

97<br />

98 ac<strong>tu</strong>al = Liste ;<br />

99 start = NULL;<br />

100 while( ac<strong>tu</strong>al != NULL) {<br />

101 kante = ac<strong>tu</strong>al −>value ;<br />

102 vergleich = get ( Liste , get ( Successor , ac<strong>tu</strong>al −>key ) ) ;<br />

103 if ( kante . points [ 0 ] < vergleich . points [ 0 ] | | ( kante . points [ 0 ] == vergleich . points [ 0 ] && kante .<br />

points [ 1 ] < vergleich . points [ 1 ] ) ) {<br />

104 start = set ( start , ac<strong>tu</strong>al −>key , kante ) ;<br />

105 }<br />

106 else start = set ( start , ac<strong>tu</strong>al −>key , vergleich ) ;<br />

107 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

108 }<br />

109 re<strong>tu</strong>rn start ;<br />

110 }<br />

111<br />

112 /∗Die Funktion size l i e f e r t die Anzahl <strong>de</strong>r Elemente <strong>de</strong>r Liste zurueck . ∗/<br />

113 int size ( struct list element ∗ Liste ) {<br />

114 int i ;<br />

115 struct list element ∗ac<strong>tu</strong>al ;<br />

116 ac<strong>tu</strong>al = Liste ;<br />

117 i= 0;<br />

118 if ( Liste == NULL) re<strong>tu</strong>rn 0;<br />

119 else {<br />

120 while( ac<strong>tu</strong>al != NULL ) {<br />

121 i++;<br />

122 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

123 }<br />

124 re<strong>tu</strong>rn i ;<br />

125 }<br />

126 }<br />

127<br />

128 /∗Die Funktion s o r t l i s t l i e f e r t die s o r t i e r t e Liste zurueck . Als Sortieralgorithmus dient hier<br />

129 Bubblesort . ∗/<br />

130 struct list element ∗ s o r t l i s t ( struct list element ∗ Liste ) {<br />

131 struct list element ∗switcher , ∗ac<strong>tu</strong>al , ∗previous ;<br />

132 struct EDGE help ;<br />

133 int i , j ;<br />

134<br />

135 for ( i = 0; i< size ( Liste ) ; i++){<br />

136 ac<strong>tu</strong>al = Liste ;<br />

137 previous = Liste ;<br />

138 for ( j =0; j< size ( Liste ) −1; j++){<br />

139 if ( ac<strong>tu</strong>al != NULL && ac<strong>tu</strong>al −>next != NULL) {<br />

120


140 if (( ac<strong>tu</strong>al −>key . points [ 0 ] > ac<strong>tu</strong>al −>next−>key . points [ 0 ] ) | | ( ac<strong>tu</strong>al −>key . points [ 0 ] ==<br />

ac<strong>tu</strong>al −>next−>key . points [ 0 ] && ac<strong>tu</strong>al −>key . points [ 1 ] > ac<strong>tu</strong>al −>next−>key . points [ 1 ] ) ) {<br />

141 if ( j==0){<br />

142 switcher = ac<strong>tu</strong>al −>next ;<br />

143 ac<strong>tu</strong>al −>next = switcher −>next ;<br />

144 switcher −>next = ac<strong>tu</strong>al ;<br />

145 Liste = switcher ;<br />

146 }<br />

147 else {<br />

148 switcher = ac<strong>tu</strong>al −>next ;<br />

149 ac<strong>tu</strong>al −>next = switcher −>next ;<br />

150 switcher −>next = ac<strong>tu</strong>al ;<br />

151 previous −>next = switcher ;<br />

152 help=get ( Liste , ac<strong>tu</strong>al −>key ) ;<br />

153 previous = previous −>next ;<br />

154 }<br />

155 }<br />

156 else {<br />

157 if ( j != 0) previous = previous −>next ;<br />

158 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

159 }<br />

160 }<br />

161 }<br />

162 }<br />

163 re<strong>tu</strong>rn Liste ;<br />

164 }<br />

165 /∗Die Funktion strange sort l i e f e r t die s o r t i e r t e Liste zurueck . Als Sortieralgorithmus dient hier<br />

166 Bubblesort . ∗/<br />

167 struct list element ∗ strange sort ( struct list element ∗ Liste ) {<br />

168 struct list element ∗switcher , ∗ac<strong>tu</strong>al , ∗previous ;<br />

169 struct EDGE help ;<br />

170 int i , j ;<br />

171<br />

172 for ( i = 0; i< size ( Liste ) ; i++){<br />

173 ac<strong>tu</strong>al = Liste ;<br />

174 previous = Liste ;<br />

175 for ( j =0; j< size ( Liste ) −1; j++){<br />

176 if ( ac<strong>tu</strong>al != NULL && ac<strong>tu</strong>al −>next != NULL) {<br />

177 if (( ac<strong>tu</strong>al −>key . points [ 1 ] > ac<strong>tu</strong>al −>next−>key . points [ 1 ] ) | | ( ac<strong>tu</strong>al −>key . points [ 1 ] ==<br />

ac<strong>tu</strong>al −>next−>key . points [ 1 ] && ac<strong>tu</strong>al −>key . points [ 0 ] > ac<strong>tu</strong>al −>next−>key . points [ 0 ] ) ) {<br />

178 if ( j==0){<br />

179 switcher = ac<strong>tu</strong>al −>next ;<br />

180 ac<strong>tu</strong>al −>next = switcher −>next ;<br />

181 switcher −>next = ac<strong>tu</strong>al ;<br />

182 Liste = switcher ;<br />

183 }<br />

184 else {<br />

185 switcher = ac<strong>tu</strong>al −>next ;<br />

186 ac<strong>tu</strong>al −>next = switcher −>next ;<br />

187 switcher −>next = ac<strong>tu</strong>al ;<br />

188 previous −>next = switcher ;<br />

189 help=get ( Liste , ac<strong>tu</strong>al −>key ) ;<br />

190 previous = previous −>next ;<br />

191 }<br />

192 }<br />

193 else {<br />

194 if ( j != 0) previous = previous −>next ;<br />

195 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

196 }<br />

197 }<br />

198 }<br />

199 }<br />

200 re<strong>tu</strong>rn Liste ;<br />

201 }<br />

202<br />

203 /∗Die Funktion d e f i n e s t a r t s d e f i n i e r t fuer je<strong>de</strong>n Knoten das Element <strong>de</strong>r Linked−List , welches als<br />

204 erstes eine ausgehen<strong>de</strong> Kante <strong>de</strong>s Knotens repraesentiert . ∗/<br />

205 void <strong>de</strong>fine starts ( struct list element ∗ Liste ) {<br />

206 int i ;<br />

207 struct list element ∗pos ;<br />

208<br />

209 pos= Liste ;<br />

210 for ( i =0; ikey . points [ 0 ] == i +1){<br />

212 if ( pos−>key . points [ 0 ] == i+1 && Cells [ i ] . f i r s t == NULL) {<br />

213 Cells [ i ] . f i r s t = pos ;<br />

214 }<br />

215 pos = pos−>next ;<br />

216 }<br />

217 }<br />

218 }<br />

219<br />

220 /∗Die Funktion c o p y l i s t l i e f e r t eine Copie <strong>de</strong>r uebergebenen Liste zurueck . ∗/<br />

221 struct list element ∗ copy list ( struct list element ∗ Liste ) {<br />

222 struct list element ∗ac<strong>tu</strong>al , ∗neustart ;<br />

223 neustart = NULL;<br />

224 ac<strong>tu</strong>al = Liste ;<br />

225 while( ac<strong>tu</strong>al != NULL) {<br />

226 neustart=set ( neustart , ac<strong>tu</strong>al −>key , ac<strong>tu</strong>al −>value ) ;<br />

227 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

121


228 }<br />

229 re<strong>tu</strong>rn neustart ;<br />

230 }<br />

231<br />

232 /∗Die Funktion <strong>de</strong>fine successor bestimmt fuer je<strong>de</strong> Kante ihre Successor −Kante . ∗/<br />

233 struct list element ∗ <strong>de</strong>fine successor ( struct list element ∗ Liste ) {<br />

234 struct list element ∗ac<strong>tu</strong>al , ∗ start ;<br />

235 struct EDGE kante ;<br />

236 int i ;<br />

237<br />

238 start = copy list ( Liste ) ;<br />

239 ac<strong>tu</strong>al = start ;<br />

240 i =0;<br />

241 while( ac<strong>tu</strong>al != NULL) {<br />

242 i++;<br />

243 if ( ac<strong>tu</strong>al −>key . points [ 0 ] == Cells [ i −1]. f i r s t −>key . points [ 0 ] && ac<strong>tu</strong>al −>key . points [ 1 ] == Cells [<br />

i −1]. f i r s t −>key . points [ 1 ] ) {<br />

244 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

245 }<br />

246 while( ac<strong>tu</strong>al != NULL && ac<strong>tu</strong>al −>key . points [ 0 ] == i ) {<br />

247 if ( ac<strong>tu</strong>al −>next != NULL && ac<strong>tu</strong>al −>next−>key . points [ 0 ] ==i ) {<br />

248 kante . points [0]= ac<strong>tu</strong>al −>key . points [ 1 ] ;<br />

249 kante . points [1]= i ;<br />

250 start = set ( start , kante , ac<strong>tu</strong>al −>next−>key ) ;<br />

251 kante . points [ 0 ] = ac<strong>tu</strong>al −>next−>key . points [ 1 ] ;<br />

252 kante . points [ 1 ] = i ;<br />

253 start = set ( start , kante , ac<strong>tu</strong>al −>key ) ;<br />

254 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next−>next ;<br />

255 }<br />

256 else {<br />

257 kante . points [ 0 ] = ac<strong>tu</strong>al −>key . points [ 1 ] ;<br />

258 kante . points [ 1 ] = i ;<br />

259 start = set ( start , kante , Cells [ i −1]. f i r s t −>key ) ;<br />

260 kante . points [ 0 ] = Cells [ i −1]. f i r s t −>key . points [ 1 ] ;<br />

261 kante . points [ 1 ] = i ;<br />

262 start = set ( start , kante , ac<strong>tu</strong>al −>key ) ;<br />

263 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

264 }<br />

265 }<br />

266 }<br />

267 re<strong>tu</strong>rn start ;<br />

268 }<br />

269<br />

270 struct list element ∗ select elements ( struct list element ∗ Liste ) {<br />

271 struct list element ∗ ac<strong>tu</strong>al , ∗ start ;<br />

272 struct EDGE kante , vergleich ;<br />

273 ac<strong>tu</strong>al = Liste ;<br />

274 start = NULL;<br />

275 while( ac<strong>tu</strong>al != NULL) {<br />

276 kante . points [ 0 ] = ac<strong>tu</strong>al −>key . points [ 1 ] ;<br />

277 kante . points [ 1 ] = ac<strong>tu</strong>al −>key . points [ 0 ] ;<br />

278 vergleich = get ( Liste , kante ) ;<br />

279 kante= ac<strong>tu</strong>al −>value ;<br />

280 if ( kante . points [ 0 ] < vergleich . points [ 0 ] | | ( kante . points [ 0 ] == vergleich . points [ 0 ] && kante .<br />

points [ 1 ] < vergleich . points [ 1 ] ) ) {<br />

281 start = set ( start , ac<strong>tu</strong>al −>key , ac<strong>tu</strong>al −>key ) ;<br />

282 }<br />

283<br />

284 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

285 }<br />

286 d e s t r o y l i s t ( Liste ) ;<br />

287 re<strong>tu</strong>rn start ;<br />

288 }<br />

289<br />

290 struct list element ∗ rearange ( struct list element ∗Liste , struct list element ∗Successor ) {<br />

291 struct list element ∗ac<strong>tu</strong>alEdge , ∗ac<strong>tu</strong>alSucc , ∗ start ;<br />

292 struct EDGE succ ;<br />

293 int i ;<br />

294 ac<strong>tu</strong>alEdge = Liste ;<br />

295 ac<strong>tu</strong>alSucc = Successor ;<br />

296 start = NULL;<br />

297 for ( i =0; i < size ( Liste ) ; i++){<br />

298 start = set ( start , ac<strong>tu</strong>alEdge−>key , ac<strong>tu</strong>alSucc −>key ) ;<br />

299 succ = ac<strong>tu</strong>alSucc −>key ;<br />

300 ac<strong>tu</strong>alEdge = Liste ;<br />

301 ac<strong>tu</strong>alSucc = Successor ;<br />

302 while (( ac<strong>tu</strong>alEdge−>key . points [ 0 ] != succ . points [ 0 ] | | ac<strong>tu</strong>alEdge−>key . points [ 1 ] != succ . points<br />

[ 1 ] ) && ac<strong>tu</strong>alEdge != NULL) {<br />

303 ac<strong>tu</strong>alEdge = ac<strong>tu</strong>alEdge−>next ;<br />

304 ac<strong>tu</strong>alSucc = ac<strong>tu</strong>alSucc −>next ;<br />

305 }<br />

306 }<br />

307 d e s t r o y l i s t ( Liste ) ;<br />

308 re<strong>tu</strong>rn start ;<br />

309 }<br />

310<br />

311 /∗Damit <strong>de</strong>r Speicher auch nach mehrmaligem Ausfuehren <strong>de</strong>s Programms nicht zu v o l l ist , wird <strong>de</strong>r<br />

312 r e s e r v i e r t e Speicherbereich hier wie<strong>de</strong>r freigegeben . ∗/<br />

313<br />

314 void d e s t r o y l i s t ( struct list element ∗ Liste ) {<br />

122


315 struct list element ∗help ;<br />

316 while( Liste != NULL && Liste −>next != NULL) {<br />

317 help = Liste −>next ;<br />

318 free ( Liste ) ;<br />

319 Liste = help ;<br />

320 }<br />

321 free ( Liste ) ;<br />

322 }<br />

323<br />

324 struct list element ∗ Euler partition ( struct list element ∗ Liste ) {<br />

325 struct list element ∗ EDGES ,∗SUCCESSORS, ∗help ;<br />

326 int i ;<br />

327 EDGES = Liste ;<br />

328 EDGES = s o r t l i s t (EDGES) ;<br />

329 help = EDGES;<br />

330 <strong>de</strong>fine starts (EDGES) ;<br />

331 SUCCESSORS = NULL;<br />

332 SUCCESSORS = <strong>de</strong>fine successor (EDGES) ;<br />

333 for ( i =0; i < LOGN; i++){<br />

334 EDGES = doubling edge (EDGES,SUCCESSORS) ;<br />

335 SUCCESSORS = doubling succ (SUCCESSORS) ;<br />

336 }<br />

337 EDGES = select elements (EDGES) ;<br />

338 d e s t r o y l i s t (SUCCESSORS) ;<br />

339 SUCCESSORS = copy list (EDGES) ;<br />

340 EDGES = strange sort (EDGES) ;<br />

341 SUCCESSORS = s o r t l i s t (SUCCESSORS) ;<br />

342 EDGES = rearange (EDGES,SUCCESSORS) ;<br />

343 help = EDGES;<br />

344 i =0;<br />

345 while( help != NULL) {<br />

346 help−>key . label=i ;<br />

347 i = ( i == 0) ;<br />

348 help = help−>next ;<br />

349 }<br />

350 d e s t r o y l i s t (SUCCESSORS) ;<br />

351 re<strong>tu</strong>rn EDGES;<br />

352 }<br />

353<br />

354 int No<strong>de</strong>Degree ( int knoten , struct list element ∗ Liste ) {<br />

355 int i ;<br />

356 struct list element ∗ac<strong>tu</strong>al ;<br />

357 i= 0;<br />

358 ac<strong>tu</strong>al = Liste ;<br />

359<br />

360 while( ac<strong>tu</strong>al != NULL) {<br />

361 if ( ac<strong>tu</strong>al −>key . points [ 0 ] == knoten | | ac<strong>tu</strong>al −>key . points [ 1 ] == knoten ) i++;<br />

362 ac<strong>tu</strong>al= ac<strong>tu</strong>al −>next ;<br />

363 }<br />

364 re<strong>tu</strong>rn i ;<br />

365 }<br />

366<br />

367 int MaxDegree( struct list element ∗ Liste ) {<br />

368 int i , <strong>de</strong>gree , temp ;<br />

369 <strong>de</strong>gree = 0;<br />

370 for ( i =0; i <strong>de</strong>gree ) <strong>de</strong>gree = temp ;<br />

373 }<br />

374 re<strong>tu</strong>rn <strong>de</strong>gree ;<br />

375 }<br />

376<br />

377 struct list element ∗ directEdges ( struct list element ∗ Liste ) {<br />

378 struct list element ∗ac<strong>tu</strong>al , ∗ start ;<br />

379 struct EDGE other ;<br />

380 ac<strong>tu</strong>al = Liste ;<br />

381 start = NULL;<br />

382 while( ac<strong>tu</strong>al != NULL) {<br />

383 start = set ( start , ac<strong>tu</strong>al −>key , ac<strong>tu</strong>al −>value ) ;<br />

384 other . points [ 0 ] = ac<strong>tu</strong>al −>key . points [ 1 ] ;<br />

385 other . points [ 1 ] = ac<strong>tu</strong>al −>key . points [ 0 ] ;<br />

386 other . label = ac<strong>tu</strong>al −>key . label ;<br />

387 start = set ( start , other , other ) ;<br />

388 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

389 }<br />

390 d e s t r o y l i s t ( Liste ) ;<br />

391 re<strong>tu</strong>rn start ;<br />

392 }<br />

393<br />

394 struct list element ∗ Euler colour ( struct list element ∗ Liste ) {<br />

395 struct list element ∗ac<strong>tu</strong>al , ∗one , ∗two ;<br />

396 int <strong>de</strong>gree ;<br />

397 ac<strong>tu</strong>al = Liste ;<br />

398 <strong>de</strong>gree = MaxDegree( Liste ) ;<br />

399 if ( <strong>de</strong>gree == 1) {<br />

400 while( ac<strong>tu</strong>al != NULL) {<br />

401 ac<strong>tu</strong>al −>key . label = 1;<br />

402 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

403 }<br />

404 re<strong>tu</strong>rn Liste ;<br />

123


405 }<br />

406 else {<br />

407 Liste = addDummy( Liste ) ;<br />

408 ac<strong>tu</strong>al = directEdges ( Liste ) ;<br />

409 ac<strong>tu</strong>al = Euler partition ( ac<strong>tu</strong>al ) ;<br />

410 ac<strong>tu</strong>al = removeDummy( ac<strong>tu</strong>al ) ;<br />

411 one = NULL;<br />

412 two = NULL;<br />

413 while( ac<strong>tu</strong>al != NULL) {<br />

414 if ( ac<strong>tu</strong>al −>key . label == 0) one = set (one , ac<strong>tu</strong>al −>key , ac<strong>tu</strong>al −>value ) ;<br />

415 else two = set (two , ac<strong>tu</strong>al −>key , ac<strong>tu</strong>al −>value ) ;<br />

416 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

417 }<br />

418 one = Euler colour ( one ) ;<br />

419 two = Euler colour (two) ;<br />

420 ac<strong>tu</strong>al = two ;<br />

421 <strong>de</strong>gree = <strong>de</strong>gree /2;<br />

422 while( ac<strong>tu</strong>al != NULL) {<br />

423 ac<strong>tu</strong>al −>key . label = ac<strong>tu</strong>al −>key . label + <strong>de</strong>gree ;<br />

424 one = set (one , ac<strong>tu</strong>al −>key , ac<strong>tu</strong>al −>value ) ;<br />

425 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

426 }<br />

427 d e s t r o y l i s t (two) ;<br />

428 re<strong>tu</strong>rn one ;<br />

429 }<br />

430 }<br />

431<br />

432 struct list element ∗ addDummy( struct list element ∗ Liste ) {<br />

433 int i ;<br />

434 struct list element ∗ intern ;<br />

435 struct EDGE kante ;<br />

436 intern=Liste ;<br />

437<br />

438 for ( i =0; ikey . points [ 0 ] != (N+1)) && ( ac<strong>tu</strong>al −>key . points [ 1 ] != (N+1)) ) {<br />

454 start = set ( start , ac<strong>tu</strong>al −>key , ac<strong>tu</strong>al −>value ) ;<br />

455 }<br />

456 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

457 }<br />

458 d e s t r o y l i s t ( Liste ) ;<br />

459 re<strong>tu</strong>rn start ;<br />

460 }<br />

461<br />

462 /∗In <strong>de</strong>r Funktion main e r f o l g t die I n i t i a l i s i e r u n g <strong>de</strong>s Graphen und die Koordination <strong>de</strong>s Ablaufs<br />

463 <strong>de</strong>s Algorithmus . ∗/<br />

464 void main () {<br />

465 int i ;<br />

466 struct EDGE key , value ;<br />

467 struct list element ∗help , ∗EDGES;<br />

468 EDGES = NULL;<br />

469 for ( i =0; i


495 EDGES= Euler colour (EDGES) ;<br />

496 d e s t r o y l i s t (EDGES) ;<br />

497 }<br />

C.6. Chinesischer Restsatz<br />

Listing C.6: Implementierung <strong>de</strong>s Chinesischen Restsatzes<br />

1 #inclu<strong>de</strong> <br />

2 #inclu<strong>de</strong> <br />

3 #inclu<strong>de</strong> <br />

4 #inclu<strong>de</strong> <br />

5<br />

6 struct list element {<br />

7 int x ;<br />

8 int mi ;<br />

9 int Mi;<br />

10 int m;<br />

11 int a ;<br />

12 struct list element ∗next ;<br />

13 struct list element ∗ link ;<br />

14 };<br />

15<br />

16 int euklid ( int a , int b , int ∗x , int ∗y){<br />

17 int xPrev , xCur , yPrev , yCur , xNext , yNext , r , q ;<br />

18 int sign = 1;<br />

19 int Sa , Sb ;<br />

20<br />

21 xPrev = 1;<br />

22 xCur = 0;<br />

23 yPrev = 0;<br />

24 yCur = 1;<br />

25 Sa = a ;<br />

26 Sb = b ;<br />

27 while(b != 0) {<br />

28 r = a%b ;<br />

29 q = a/b ;<br />

30 a = b ;<br />

31 b = r ;<br />

32 xNext = q∗xCur + xPrev ;<br />

33 xPrev = xCur ;<br />

34 xCur = xNext ;<br />

35 yNext = q∗yCur + yPrev ;<br />

36 yPrev = yCur ;<br />

37 yCur = yNext ;<br />

38 sign = −sign ;<br />

39 }<br />

40 ∗x = sign ∗ xPrev ;<br />

41 if (∗x < 0) ∗x= Sb + ∗x ;<br />

42 ∗y = −sign ∗ yPrev ;<br />

43 re<strong>tu</strong>rn a ;<br />

44 }<br />

45<br />

46 int chin ( int a , int Mi, int mi){<br />

47 int y , x , help ;<br />

48 help = euklid (Mi,mi,&y,&x) ;<br />

49 printf (”Ergebnis <strong>de</strong>s erweiterten Euklid : %i \n” ,y) ;<br />

50 x = Mi ∗ y ∗ a ;<br />

51 re<strong>tu</strong>rn x ;<br />

52 }<br />

53<br />

54 int size ( struct list element ∗ Liste ) {<br />

55 struct list element ∗ac<strong>tu</strong>al ;<br />

56 int i ;<br />

57 ac<strong>tu</strong>al = Liste ;<br />

58 i = 0;<br />

59 while( ac<strong>tu</strong>al != NULL) {<br />

60 i++;<br />

61 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

62 }<br />

63 re<strong>tu</strong>rn i ;<br />

64 }<br />

65<br />

66 void calc ( struct list element ∗ Liste ) {<br />

67 double logm ;<br />

68 int i ;<br />

69 struct list element ∗ac<strong>tu</strong>al ;<br />

70 ac<strong>tu</strong>al = Liste ;<br />

71 while( ac<strong>tu</strong>al != NULL && ac<strong>tu</strong>al −>next != NULL) {<br />

72 ac<strong>tu</strong>al −>link = ac<strong>tu</strong>al −>next ;<br />

73 (∗ ac<strong>tu</strong>al ) .m = ac<strong>tu</strong>al −>mi ;<br />

74 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

75 }<br />

76 ac<strong>tu</strong>al −>m = ac<strong>tu</strong>al −>mi ;<br />

77 ac<strong>tu</strong>al −>link = NULL;<br />

125


78 logm = log ((double) size ( Liste ) ) ;<br />

79 for ( i =0; ilink != NULL) {<br />

83 ac<strong>tu</strong>al −>m= ( ac<strong>tu</strong>al −>m) ∗ ( ac<strong>tu</strong>al −>link −>m) ;<br />

84 ac<strong>tu</strong>al −>link = ac<strong>tu</strong>al −>link −>link ;<br />

85 }<br />

86 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

87 }<br />

88 }<br />

89 ac<strong>tu</strong>al = Liste ;<br />

90 while( ac<strong>tu</strong>al != NULL) {<br />

91 ac<strong>tu</strong>al −>m = Liste −>m;<br />

92 ac<strong>tu</strong>al −>Mi = ac<strong>tu</strong>al −>m /ac<strong>tu</strong>al −>mi ;<br />

93 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

94 }<br />

95 ac<strong>tu</strong>al = Liste ;<br />

96 while( ac<strong>tu</strong>al != NULL) {<br />

97 ac<strong>tu</strong>al −>x = chin ( ac<strong>tu</strong>al −>a , ac<strong>tu</strong>al −>Mi, ac<strong>tu</strong>al −>mi) ;<br />

98 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

99 }<br />

100 ac<strong>tu</strong>al = Liste ;<br />

101 while( ac<strong>tu</strong>al != NULL && ac<strong>tu</strong>al −>next != NULL) {<br />

102 ac<strong>tu</strong>al −>link = ac<strong>tu</strong>al −>next ;<br />

103 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

104 }<br />

105 ac<strong>tu</strong>al −>link = NULL;<br />

106 for ( i =0; ilink != NULL) {<br />

110 ac<strong>tu</strong>al −>x= ( ac<strong>tu</strong>al −>x + ac<strong>tu</strong>al −>link −>x)%(Liste −>m) ;<br />

111 ac<strong>tu</strong>al −>link = ac<strong>tu</strong>al −>link −>link ;<br />

112 }<br />

113 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

114 }<br />

115 }<br />

116 printf (”Das Ergebnis <strong>de</strong>r simultanen Kongruenz i s t : %i \n” , Liste −>x) ;<br />

117 }<br />

118<br />

119 struct list element ∗ set ( struct list element ∗Liste , struct list element item ) {<br />

120 struct list element ∗help , ∗ac<strong>tu</strong>al ;<br />

121 ac<strong>tu</strong>al = Liste ;<br />

122 while( ac<strong>tu</strong>al != NULL && ac<strong>tu</strong>al −>next !=NULL ) {<br />

123 ac<strong>tu</strong>al = ac<strong>tu</strong>al −>next ;<br />

124 }<br />

125 help = malloc ( sizeof ( struct list element ) ) ;<br />

126 (∗ help ) = item ;<br />

127 help−>next = NULL;<br />

128 if ( Liste == NULL) Liste = help ;<br />

129 else ac<strong>tu</strong>al −>next = help ;<br />

130 re<strong>tu</strong>rn Liste ;<br />

131 }<br />

132<br />

133 void <strong>de</strong>stroyList ( struct list element ∗ Liste ) {<br />

134 struct list element ∗ac<strong>tu</strong>al ;<br />

135 while ( Liste != NULL) {<br />

136 ac<strong>tu</strong>al = Liste −>next ;<br />

137 free ( Liste ) ;<br />

138 Liste = ac<strong>tu</strong>al ;<br />

139 }<br />

140 }<br />

141<br />

142 void main () {<br />

143 struct list element value ;<br />

144 struct list element ∗gca , ∗ac<strong>tu</strong>al ;<br />

145 gca = NULL;<br />

146<br />

147 value . mi= 6;<br />

148 value . a = 2;<br />

149 gca = set ( gca , value ) ;<br />

150<br />

151 value . mi = 19;<br />

152 value . a = 12;<br />

153 gca = set ( gca , value ) ;<br />

154<br />

155 value . mi = 23;<br />

156 value . a = 12;<br />

157 gca = set ( gca , value ) ;<br />

158<br />

159 value . mi = 7;<br />

160 value . a = 4;<br />

161 gca = set ( gca , value ) ;<br />

162<br />

163 ac<strong>tu</strong>al = gca ;<br />

164 calc ( gca ) ;<br />

165 <strong>de</strong>stroyList ( gca ) ;<br />

166 }<br />

126


Litera<strong>tu</strong>rverzeichnis<br />

[ARS71] Alvy <strong>Ra</strong>y Smith, III: Simple Computation-Universal Cellular Spaces. J.<br />

ACM, 18(3):339–353, 1971.<br />

[BA95]<br />

[Buc01]<br />

[CHL99]<br />

[CLC82]<br />

Ben-Amram, Amir M.: What is a ”<br />

Pointer Machine“ ? In: SIGACTN: SI-<br />

GACT News (ACM Special Interest Group on Automata and Computability<br />

Theory), 1995.<br />

Buchmann, Johannes: Einführung in die Kryptographie<br />

2., erweiterte Auflage. Springer, 2001.<br />

Chong, Ka Wong, Yijie Han und Tak Wah Lam: On the parallel time complexity<br />

of undirected connectivity and minimum spanning trees. In: SODA ’99:<br />

Proceedings of the tenth annual ACM-SIAM symposium on Discrete algorithms,<br />

Seiten 225–234, Phila<strong>de</strong>lphia, PA, USA, 1999. Society for Industrial<br />

and Applied Mathematics.<br />

Chin, Francis Y., John Lam und I-Ngo Chen: Efficient parallel algorithms for<br />

some graph problems. Commun. ACM, 25(9):659–665, 1982.<br />

[CLRS01] Cormen, Thomas H., Charles E. Leiserson, Ronald L. Rivest und Clifford<br />

Stein: Introduction to Algorithms, Second Edition. The MIT Press, Cambridge,<br />

London, 2001.<br />

[CNG + 01] Calidonna, Claudia R., Claudia Di Napoli, Maurizio Giordano, Mario Mango<br />

Furnari und Salvatore Di Gregorio: A network of cellular automata for<br />

a landsli<strong>de</strong> simulation. In: ICS ’01: Proceedings of the 15th international<br />

conference on Supercomputing, Seiten 419–426, New York, NY, USA, 2001.<br />

ACM Press.<br />

[DR86]<br />

[GK96]<br />

Dymond, Patrick W. und Walter L. Ruzzo: Parallel RAMs with Owned Global<br />

Memory and Deterministic Context-Free Language Recognition (Exten<strong>de</strong>d<br />

Abstract). In: Automata, Languages and Programming, Seiten 95–104, 1986.<br />

Goodrich, Michael T. und S. <strong>Ra</strong>o Kosaraju: Sorting on a Parallel Pointer<br />

Machine with Applications to Set Expression Evaluation. In: Journal of the<br />

ACM, 1996.<br />

127


[GR98]<br />

[Gü92]<br />

[Hee01]<br />

[Hoc98]<br />

[HVH03]<br />

[JM92]<br />

[KKT01]<br />

Gibbons, Alan und Woiciech Ritter: Efficient Parallel Algorithms. Cambridge<br />

University Press, New York, Port Chester, Melbourne, Sidney, 1998.<br />

Güting, <strong>Ra</strong>lf Hartmut: Datenstruk<strong>tu</strong>ren und <strong>Algorithmen</strong>. B. G. Teubner,<br />

1992.<br />

Heenes, Wolfgang: Globaler Zellularer Automat: <strong>Algorithmen</strong> und Struk<strong>tu</strong>ren.<br />

Diplomarbeit, <strong>Technische</strong> Universität Darmstadt, 2001.<br />

Hochberger, Christian: CDL - Eine Sprache für die Zellularverarbei<strong>tu</strong>ng auf<br />

verschie<strong>de</strong>nen Zielplattformen. Doktorarbeit, <strong>Technische</strong> Universität Darmstadt,<br />

1998.<br />

Hoffmann, Rolf, Klaus-Peter Völkmann und Wolfgang Heenes: GCA: A Massively<br />

Parallel Mo<strong>de</strong>l. In: IPDPS ’03: Proceedings of the 17th International<br />

Symposium on Parallel and Distributed Processing, Seite 270.2, Washington,<br />

DC, USA, 2003. IEEE Computer Society.<br />

Johnson, Donald B. und Panagiotis Metaxas: A parallel algorithm for computing<br />

minimum spanning trees. In: SPAA ’92: Proceedings of the fourth annual<br />

ACM symposium on Parallel algorithms and architec<strong>tu</strong>res, Seiten 363–372,<br />

New York, NY, USA, 1992. ACM Press.<br />

Keller, Jörg, Christoph W. Keßler und Jesper Larsson Träff: Practical PRAM<br />

Programming. John Wiley & Sons, INC., New York, Chichester, Weinheim,<br />

Brisbane, Singapore, Toronto, 2001.<br />

[Kru05] Kruthoff, Bernd: Ausarbei<strong>tu</strong>ng Graph-<strong>Algorithmen</strong>, 2005.<br />

http://www-wi.uni-muenster.<strong>de</strong>/pi/lehre/SS03/Seminar/Graph<br />

Bernd Kruthoff.pdf.<br />

[Maj94]<br />

Majercik, Stephen Michael: Struc<strong>tu</strong>rally Dynamic Cellular Automata. Diplomarbeit,<br />

The University of Southern Maine School of Applied Science,<br />

1994.<br />

[MWIS02] Muzy, Alexandre, Gabriel Wainer, Eric Innocenti und Antoine Aiello Jean-<br />

Franšois San<strong>tu</strong>cci: Comparing Simulation Methods For Fire Spreading Across<br />

A Fuel Bed. In: Proceedings of AIS’2002, Lisbon, Por<strong>tu</strong>gal, 2002.<br />

[Nat90]<br />

[Sar00]<br />

Natvig, Lasse: Logarithmic time cost optimal parallel sorting is not yet fast<br />

in practice! In: Supercomputing ’90: Proceedings of the 1990 ACM/IEEE<br />

conference on Supercomputing, Seiten 486–494, Washington, DC, USA, 1990.<br />

IEEE Computer Society.<br />

Sarkar, Palash: A brief history of cellular automata. ACM Comput. Surv.,<br />

32(1):80–107, 2000.<br />

128


[Sch01]<br />

[Sig89]<br />

Schöning, Uwe: Theoretische Informatik - kurzgefasst. Spektrum Aka<strong>de</strong>mischer<br />

Verlag, 2001.<br />

Signorini, J.: How a SIMD machine can implement a complex cellular automata?<br />

a case s<strong>tu</strong>dy: von Neumann’s 29-state cellular automaton. In: Supercomputing<br />

’89: Proceedings of the 1989 ACM/IEEE conference on Supercomputing,<br />

Seiten 175–186, New York, NY, USA, 1989. ACM Press.<br />

[Web05] Webseite über <strong>de</strong>n Cell-Prozessor: 2005.<br />

http://www.blachford.info/computer/Cell/Cell0 v2.html.<br />

129

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!