Parallele Algorithmen im Sommersemester 2016 - Dr. Oliver Schaudt - Universität zu Köln Mathematisches Institut 28. August 2016 Inhaltsverzeichnis Inhaltsverzeichnis 1 1 Einleitung & Grundbegriffe 1.1 Die Klasse N C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Elementare Techniken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Brents Gesetz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 3 5 2 Graphenalgorithmen 2.1 Kürzeste Wege . . . . . . . . . . 2.2 Zusammenhangskomponenten . 2.3 Minimale Spannbäume . . . . . 2.4 2-Zusammenhangskomponenten 2.5 Eulertouren . . . . . . . . . . . . 2.6 Maximale Matchings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 6 7 11 13 15 21 3 Paralleles Sortieren 24 3.1 Einfache Sortieralgorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.2 Sortieren in O (log n) bzw. Coles Algorithmus . . . . . . . . . . . . . . . . . . . 28 4 P-Vollständigkeit 4.1 Erzeugbarkeit ist P-vollständig . . . 4.2 Weitere P-vollständige Probleme . . 4.2.1 Auswertungsproblem (CVP) . 4.2.2 Tiefensuche (DFS) . . . . . . 4.2.3 Maximum Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 35 38 38 39 41 Abbildungsverzeichnis 44 Liste der Algorithmen 45 Mitschrift der Vorlesung von Dario Antweiler. Keine Garantie auf Richtigkeit oder Vollständigkeit. Lob, Kritik, Kommentare an [email protected]. Lizensiert unter (CC BY-SA 4.0). Stand 28. August 2016. Verwendete Software: LaTeX, TikZ, yEd & Ipe = hier fehlt noch etwas 1 Einleitung & Grundbegriffe 11.04.16 VL01 Bemerkung. Fragen: • Welche algorithmischen Probleme lassen sich ”gut” parallelisieren? • Was bedeutet ”gut” genau? • Welche Probleme lassen dagegen nicht gut parallelisieren? Definition. Wir arbeiten mit dem P-RAM-Modell. Dabei teilen sich mehrere Prozessoren einen gemeinsamen Speicher. Die Prozessoren greifen in Konstantzeit auf beliebige Speicherstellen zu und führen elementare Operationen, wie z.B. Addition oder der Vergleich zweier Zahlen durch. Bemerkung. Wir gehen von Einheitskosten für diese Operation aus. Das Hauptprogramm gibt den Prozessoren Anweisungen. Diese führen die gleiche Anweisung gleichzeitig aus. Dabei greifen sie auf unterschiedliche Stellen im Speicher zu. Abbildung 1: Das P-RAM-Modell Definition. Innerhalb des P-RAM-Modells betrachten wir das SIMD-Modell (SingleInstruction-Multiple-Datastream). Bemerkung. Ein verallgemeinerter Pseudocode hat folgende Form: forall the x ∈ X in parallel do instruction (x) end Algorithmus 1 : Grundgerüst eines parallelen Algorithmus Dabei ist X typischerweise eine Liste oder ein Array z.B. von Zahlen. Die Anweisung instruction (x) wird für jedes x ∈ X von einem eigenen Prozessor ausgeführt. Das Hauptprogramm wartet auf die Beendigung aller Prozessoren, bevor es fortfährt. Wir gehen von einer ausreichend großen Anzahl Prozessoren aus. Definition. Es gilt folgende P-RAM-Modelle: • CREW (Concurrent-Read-Exclusive-Write) • EREW (Exclusive-Read-Exclusive-Write) • CRCW (Concurrent-Read-Concurrent-Write) 1 3 EINLEITUNG & GRUNDBEGRIFFE Im Folgenden betrachten wir das CREW-Modell. 9 9 8 9 9 7 3 7 8 4 8 2 5 1 2 Abbildung 2: Maximumsbestimmung mit einem Baum Fragen: • Wieviele Prozessoren haben wir benötigt? • Wieviele Schritte wurden ausgeführt? Dieser Algorithmus benötigt ⌈ n2 ⌉ viele Prozessoren, wenn das Array n Einträge hat. Also im Wesentlichen O (n) viele. Wir machen ⌈log n⌉ viele parallele Schritte, also O (log n) viele. Sei A das Array mit n = 2m vielen Einträgen. Sei B ein Array mit 2n vielen Plätzen mit B [n] = A [1] , B [n + 1] = A [2] , . . . , B [2n − 1] = A [n]. Am Ende ist das Maximum von A in B [1]. Pseudocode: for k = m − 1 bis 0 do for all j ∈ [2k , 2k+1 − 1] in parallel do B [j] ← max {B [2j] , B [2j + 1]} end end return B [1] Algorithmus 2 : Parallele Berechnung des Maximums in einem Array 1.1 Die Klasse N C Satz 1.1. Es gilt N C ⊆ P. Beweis. Jeder parallele Schritt lässt sich mit einem Prozessor in O (nO(1) ) vielen Schritten sequentiell bearbeiten. Wir haben nur O (logO(1) n) viele parallele Schritte. Insgesamt brauchen wir O (nO(1) ⋅ logO(1) n) = O (nO(1) ) viele sequentielle Schritte. Vermutung. N C ≠ P, dazu später mehr. 1.2 Elementare Techniken Verdoppeln Wenn wir ein Array von Zahlen gegeben haben, dann können wir effizient parallel ein Maximum bestimmen. Was passiert, wenn wir eine (verkettete) Liste gegeben haben? L ∶ L1 → L2 → ⋅ ⋅ ⋅ → Ln ⟲ 13.04.16 VL02 1 4 EINLEITUNG & GRUNDBEGRIFFE for all k ∈ [1, . . . , n] in parallel do succ (k) ← next (k) if succ (k) = k then dist (k) ← 0 else dist (k) ← 1 end end repeat ⌈log n⌉ times for all k ∈ [1, . . . , n] in parallel do if succ (k) ≠ succ (succ (k)) then dist (k) ← dist (k) + dist (succ (k)) succ (k) ← succ (succ (k)) end end end Algorithmus 3 : Paralleles Durchmustern einer (verketteten) Liste 1 1 1 1 0 2 2 2 1 0 4 3 2 1 0 Abbildung 3: Die Werte von dist und die Zeiger von SUCC zu Beginn und nach 1. bzw. 2 Schritt Sei next (k) der Nachfolgeindex von Lk in L. Algorithmus 3 bestimmt die Abstände der Einträge der Liste zum Ende, d.h. zu Ln . Wir brauchen O (n) viele Prozessoren und O (log n) Schritte, das Problem liegt also in N C. Anstatt dist können wir auch abstrakt einen Wert value (k) berechnen, mit einer Vorschrift unserer Wahl. Wir ersetzen dist (k) ← dist (k) + dist (succ (k)) durch value (k) ← value (k) ⊕ value (succ (k)) wobei ”⊕” eine beliebige binäre Verknüpfung ist. Zum Beispiel kann mit i⊕j ∶= max {i, j} und der Initialisierung von value (k) mit dem Wert des Eintrags im Listenelement das Maximum aller Zahlen im Array parallel berechnet werden. Diese Methode nennen wir verdoppeln. Dynamische Programmierung Im nächsten Beispiel benutzen wir dynamische Programmierung um einen effizienten parallelen Algorithmus zu bauen. Wir wollen Polynome der Form n p (x) = ∑ ai xi i=0 an einer Stelle x0 auswerten. Sei OBdA n = 2k − 1, ansonsten fülle mit Nullen auf. Schreibe p (x) = r (x) + x n−1 2 ⋅ s (x) 1 5 EINLEITUNG & GRUNDBEGRIFFE wobei r (x) und s (x) Polynome vom Grad 2k−1 −1 sind. Rekursiv berechnen wir r (x0 ) , s (x0 ) und dann p (x0 ). Zunächst berechnen wir sequentiell die Folge n−1 x0 , x20 , x40 , . . . , x0 2 in O (log n) vielen Schritten. Der Rekursionsbaum ist dann n+1 r (x0 ) + x0 2 · s (x0 ) n+1 ∗ r0 (x0 ) + x0 4 · s0 (x0 ) :: : :: n+1 x0 4 : s (x0 ) a0 + a1 x0 ∗ a0 a1 x0 Abbildung 4: Rekursionsbaum für die parallele Auswertung von Polynomen 1.3 Brents Gesetz Das optimale Verhältnis von der Anzahl der Prozessoren zur Laufzeit kann auch nachgelagert werden. Als Beispiel nehmen wir die Maximumsbestimmung in einem Array. Im ersten Schritt brauchen wir n2 viele Prozessoren, danach n4 , n8 ,usw. Nach kurzer Zeit sind also viele Prozessoren ”arbeitslos”. Angenommen wir haben p < n2 viele Prozessoren. Wir lassen jeden Prozessor ⌈ np ⌉ viele Einträge des Arrays sequentiell vergleichen. A = A1 , A2 , . . . , A⌈ n ⌉ , A⌈ n ⌉+1 , . . . , A2⋅⌈ n ⌉ , . . . , An p p p ´¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¸¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¶ ´¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹¸¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¶ p1 p2 Das kostet O ( np ) viele Schritte. Nach diesem Schritt haben wir ein Array der Länge p, welches wir mit Hilfe von p vielen Prozessoren in O (log p) vielen Schritten bearbeiten können. Insgesamt haben wir p Prozessoren gebraucht und machen O ( np + log p) viele Schritte. Für p = ⌈ logn n ⌉ ergibt sich eine Laufzeit von O (log n). Mit O ( logn n ) Prozessoren und einer Laufzeit von O (log n) werden O( n ⋅ log n) = O (n) log n viele einzelne Arbeitsschritte gemacht. Das ist optimal! Satz 1.2. Sei A ein paralleler Algorithmus mit Laufzeit t und sei m die Anzahl der Arbeitsschritte. Dann lässt sich A mit p Prozessoren in Zeit O ( m + t) implementieren. p Beweis. Sei mi die Anzahl der Arbeitsschritte, die im parallelen Schritt i gleichzeitig ausgeführt werden. Es gilt t ∑ mi = m i=0 In ⌈ mpi ⌉ Schritten simulieren wir Schritt i von A und es gilt t m mi +1= +t p p i=1 ∑ 18.04.16 VL03 2 6 GRAPHENALGORITHMEN Bemerkung. In der Praxis lassen sich die mi schwer abschätzen. Wir betrachten n = 2k Elemente in einer linearen Liste und die Methode des Verdoppelns. In jedem Schritt halbiert sich die Distanz eines Elements zum Ende der Liste. Insbesondere haben wir nach l Schritten alle Elemente der Distanz < 2l abgearbeitet. Im letzten Schritt bleiben die Elemente, die dist ≥ 2n−1 = n2 zum Ende haben übrig. Davon gibt es n2 viele. Insbesondere brauchen wir n log n Arbeitsschritte. D.h. eine Maximumsbestimmung im Array lässt sich ”einfach” optimal parallelisieren, in einer linearen Liste dagegen nicht. 2 Graphenalgorithmen 2.1 Kürzeste Wege Wir wollen den kürzesten Weg zwischen jedem Paar von Knoten eines Graphen finden (APSP, All-Pairs-Shortest-Paths). Eingabe: Adjazenzmatrix M mit Mij geben die Kosten der Kante von i nach j an, bzw. Mij = ∞, falls die Kante (i, j) nicht existiert. Wir setzen voraus, dass der Graph keine Kreise negativer Länge enthält, sonst sind die kürzeste Wege nicht wohldefiniert. 1 2 1 -3 2 3 3 ⎛0 M =⎜1 ⎝∞ ∞ 2⎞ 0 −3⎟ 3 0⎠ Abbildung 5: Ein Graph G und seine Adjazenzmatrix M Idee: Berechne Q (i, k, j) als die Länge des kürzesten Weges von i nach k + die Länge des kürzesten Weges von k nach j mit maximal 2l Kanten. Algorithmus 4. for all i, j ∈ [n] in parallel do M ′ (i, j) ← M (i, j) end repeat ⌈log n⌉ times for all i, k, j ∈ [n] in parallel do Q (i, k, j) = M ′ (i, k) + M ′ (k, j) end for all i, j ∈ [n] in parallel do M ′ (i, j) ← mink∈[n] Q (i, k, j) end end Algorithmus 4 : Parallele Berechnung kürzester Wege in einem Graphen Analyse: Die Initialisierung geht mit O (n2 ) Prozessoren in O (1) Zeit. Q kann mit O (n3 ) Prozessoren in Zeit O (1) berechnet werden. Eine Minimumsbildung geht mit O (n/ log n) Prozessoren in O (log n) Zeit. Wir müssen n2 viele Minima berechnen und benötigen dafür also insgesamt O (n3 / log n) viele Prozessoren und O (log n) Zeit. Daher ergibt sich: 2 GRAPHENALGORITHMEN 7 Satz 2.1. APSP geht mit O (n3 / log n) Prozessoren in Zeit O (log2 n). Insbesondere gilt APSP ∈ N C. Bemerkung. Der beste bekannte sequentielle Algorithmus braucht O (n3 ) Zeit (FloydWashall), insbesondere ist der obige Algorithmus nicht optimal. 2.2 Zusammenhangskomponenten Der nächste Algorithmus bestimmt die ZHK eines Graphen. Im sequentiellen Fall geht das in O (m + n). Das Problem bei der Parallelisierung: Tiefensuchbäume lassen sich (vermutlich) nicht effizient parallel berechnen! Dazu später mehr. Nehmen wir an, der Eingabegraph habe die n Knoten {1, . . . , n}. Wir haben als Eingabe die Adjazenzmatrix A von G gegeben, wobei A (i, j) ⇔ i, j sind benachbart in G, sonst A (i, j) = 0. Die Ausgabe ist ein Vektor C in n Dimensionen. Es gilt C (i) = C (j) ⇔ i, j liegen in der gleichen ZHK. Dabei ist C (i) = k genau dann, wenn k der numerisch kleinste Index in der ZHK von i ist. Zu Beginn setze C (i) = i für alle Knoten i. Der Algorithmus verschmilzt Knoten zu Pseudoknoten. Diese Pseudoknoten bilden eine Teilmenge der Knoten. Ist C (i) = j, dann sagen wir, dass j der Pseudoknoten zu i ist. 25.04.16 VL04 Abbildung 6: Graph G und Digraph D In jeder Iteration wird die Anzahl der Pseudoknoten innerhalb jeder ZHK halbiert. Der Algorithmus führt ⌈log n⌉ oft folgende Schritte durch. Wir benutzen einen n-dimensionalen Hilfsvektor T . for all i ∈ [n] in parallel do if ∃j ∈ [n] mit A (i, j) = 1 und C (i) ≠ C (j) then T (i) ← min {C (i) ∣ A (i, j) = 1, C (i) ≠ C (j)} else T (i) ← C (i) end end Algorithmus 5 : Berechnung der ZHK, Schritt 1 Betrachte den ger. Graphen D = ({1, . . . , n} , {(i, T (i)) ∣ 1 ≤ i ≤ n}) wie in Abbildung 7. Nach Schritt 2 gilt: • alle Nicht-Pseudoknoten i zeigen in D auf ihren Pseudoknoten C (i) • Jeder Pseudoknoten i zeigt auf den günstigsten ”benachbarten” Pseudoknoten j, d.h. es existieren k, l mit A (k, l) = 1 und C (k) = i, C (l) = j 27.04.16 VL05 2 8 GRAPHENALGORITHMEN Abbildung 7: Digraph D for all i ∈ [n] in parallel do if ∃j ∈ [n] mit C (j) = i und T (j) ≠ i then T (i) ← min {T (j) ∣ C (j) = i, T (j) ≠ i} else T (i) ← C (i) end end Algorithmus 6 : Berechnung der ZHK, Schritt 2 Definition. Eine schwache ZHK eines gerichteten Graphen ist eine ZHK im zugrundeliegenden ungerichteten Graphen. In jeder schwachen ZHK gibt es genau einen Kreis, da jeder Knoten genau einen Nachfolger hat. Dieser Kreis hat Länge 1 (Schlinge) oder 2 (antiparallele Kanten). Wichtig: Ein Kreis der Länge 1 existiert genau dann, wenn die gesamte ZHK, in der dieser Pseudoknoten liegt, in i zusammengefasst ist. In den Schritten 3-5 werden die Pseudoknoten einer jeden schwachen ZHK von D zu je einem einzigen Pseudoknoten verschmolzen: for all i ∈ [n] in parallel do B (i) ← T (i) end Algorithmus 7 : Berechnung der ZHK, Schritt 3 Im gerichteten Graphen D′ = ({1, . . . , n} , {(i, B (i)) ∣ 1 ≤ i ≤ n}) zeigt jeder Knoten auf einen Pseudoknoten aus dem Kreis seiner schwachen ZHK in D. Lemma 2.2. Der Algorithmus berechnet die ZHK des Eingabegraphen (bzw. den korrekten C-Vektor. Beweis. Alle Knoten mit demselben Pseudoknoten liegen in derselben ZHK des Eingangssystems. Wie vorher erklärt halbieren wir die Anzahl der Pseudoknoten in jeder ZHK, außer sie ist schon gleich 1. Daher besteht nach ⌈log n⌉ Schritten jede ZHK aus genau einem Pseudoknoten. Laufzeitanalyse Insgesamt haben wir ⌈log n⌉ viele Iterationen der Schritte 1-5. Schritt 4 besteht wiederum aus ⌈log n⌉ vielen Iterationen, also ist die Laufzeit Ω (log2 n). Wir wollen bei O (log2 n) vielen parallelen Schritten bleiben. Dazu müssen wir die Schritte 1 bzw 2 in O (log n) viel Zeit abarbeiten. Wir diskutieren Schritt 1, das implementieren wir so: 2 9 GRAPHENALGORITHMEN repeat ⌈log n⌉ times for all i ∈ [n] in parallel do B (i) ← B (B (i)) end end Algorithmus 8 : Berechnung der ZHK, Schritt 4 for all i ∈ [n] in parallel do C (i) ← min {B (i) , T (B (i))} end Algorithmus 9 : Berechnung der ZHK, Schritt 5 Schritt 1.1 implementieren wir so, dass je ⌈log n⌉ viele Paare (i, j), festes i, vom selbem Prozessor abgearbeitet werden. Schritt 1.2 können wir mit O (n ⋅ logn n ) vielen Prozessoren in O (log n) Schritten implementieren. Schritt 2 geht analog. Insgesamt im Algorithmus machen wir O (log2 n) viele 2 n Schritte und brauchen O ( log ) viele Prozessoren. Das ist gut. Es geht noch besser: n Satz 2.3. Wir können die ZHK eines Graphen mit O (n2 / log2 n) Prozessoren in O (log2 n) Zeit bestimmt werden. 2 2 n Beweis. Wir müssen von O ( log ) Prozessoren wieder auf O ( logn2 n ) viele Prozessoren. Nehn men wir an wir haben n−k viele Prozessoren. Angenommen wir verschmelzen Pseudoknoten (nach jedem Schritt 5 ) tatsächlich zu einem einzelnen Knoten und löschen die Pseudoknoten, welche eine ZHK umfassen. Später. Dann wird durch jede Iteration des Algorithmus die Anzahl der Knoten des Graphen halbiert. Nach k Schritten haben wir nk ≤ 2nk , wobei nk die Anzahl der Knoten nach Iteration k bezeichnet. Wir müssen nur 1 und 2 diskutie- ren, aber eigentlich nur 1 . Zur Minimumsbildung: In einem Array mit nk vielen Einträgen können wir das Maximum in ⎧ ⎪ ⎪⌈ nk ⌉ − 1 + ⌈log k⌉ k < ⌊ n2k ⌋ Tk = ⎨ k ⎪ ⌈log nk ⌉ k ≥ ⌊ n2k ⌋ ⎪ ⎩ vielen Schritten bestimmen, siehe Kapitel 1. Es gilt k ≥ ⌊ n2k ⌋, sobald k ≥ ⌈log n − log k⌉ =∶ t, weil nk ≤ 2nk ≤ k. Somit geht die Minimumsbildung summiert über alle Iterationen in T = ... = ... = O (log2 n) Wir müssen noch die Hausarbeit erledigen. Die aktuell relevanten Pseudoknoten müssen n nachgehalten werden. Dafür benutzen wir einen n-dimensionalen Vektore pseudo ∈ {0, 1} . Dabei soll gelten pseudo (i) = 1 ⇔ i ist ein Pseudoknoten, welcher keine ganze ZHK umfasst Zu Beginn ist pseudo (i) = 1 für alle Knoten i. Zu Beginn jeder Iteration sortieren wir die Knoten gemäß ihrem Pseudo-Wert absteigend. Das geht in O (log n) Schritten mit O (n) Prozessoren, das sehen wir später. Die ersten Stelle des sortierten Arrays enthalten die relevanten Pseudoknoten. Es sei P = {i ∶ pseudo (i) = 1}. Die Adjazenzmatrix modifizieren wir so, dass für je zwei Knoten i, j ∈ P gilt A (i, j) = 1 ⇔ ∃l, k mit C (l) = i, C (k) = j, (l, k) ∈ E 02.05.16 VL06 2 10 GRAPHENALGORITHMEN for all i, j, 1 ≤ i, j ≤ n in parallel do // Schritt 1.1 if A (i, j) = 1 und C (i) ≠ C (j); then Temp (i, j) ← C (i) else Temp (i, j) ← ∞ end end for all i ∈ [n] in parallel do // Schritt 1.2 Temp (i, 1) ← min {Temp (i, j) ∣ 1 ≤ j ≤ n} end for all i ∈ [n] in parallel do // Schritt 1.3 if Temp (i, 1) ≠ ∞; then T (i) ← Temp (i, 1) else T (i) ← C (i) end end Algorithmus 10 : Berechnung der ZHK, Nachbesserungen in Schritt 1 Schritt 1 & 2 werden dann zu for all i ∈ P in parallel do if ∃j ∈ P mit A (i, j) = 1 then T (i) ← min {j ∶ j ∈ P, A (i, j) = 1} else T (i) ← C (i) end pseudo (i) ← 0 end Die Schritte 3 bis 5 passen wir an, in dem wir ”forall vertices i” durch ”forall i ∈ P ” ersetzen. Am Ende der Iteration müssen wir 1. die Vektoren C und pseudo aktualisieren und 2. die Adjazenzmatrix aktualisieren Für ersteres setzen wir pseudo (i) auf 0, falls C (i) ≠ i. Die C-Werte der Knoten aus P sind nach Ende der Iteration korrekt. Für alle anderen Knoten j gilt möglicherweise C (C (j)) ≠ C (j), auf jeden Fall aber C (C (j)) = C (C (C (j))), also ist C (C (j)) der korrekte Pseudoknoten von j. P V nP Abbildung 8: Pseudoknoten P am Ende der Iteration und Knoten, welche nach der Iteration keine Pseudoknoten mehr sind 2 11 GRAPHENALGORITHMEN Wir setzen folgende Zeile ein: for all vertices i in parallel do C (i) ← C (C (i)) end Danach sind alle C-Werte aktuell. Zu letzterem: Wir machen for all i ∈ P in parallel do for all j ∈ P mit C (j) = j in parallel do A (i, j) ← max {A (i, k) , k ∈ P, C (k) = j} end end Danach gilt A (i, j) = 1 genau dann, wenn es einen Knoten k im Pseudoknoten j gibt mit A (i, k) = 1. Dann machen wir for all j ∈ P mit C (j) = j in parallel do for all i ∈ P mit C (i) = i in parallel do A (i, j) ← max {A (k, j) , k ∈ P, C (k) = i} end end Danach gilt A (i, j) = 1 genau dann, wenn A (k, j) = 1 für ein k mit C (k) = i. i k j Abbildung 9: blablabla Diese Schritte lassen sich, analog zu oben, in der gewünschten Komplexität implementieren. Damit ist auch die Hausarbeit erledigt. 2.3 Minimale Spannbäume Unser Algorithmus kann wesentlich mehr, als nur die ZHKs zu bestimmen. Zum Beispiel können wir einen kostenminimalen aufspannenden Wald bestimmen. Die Idee ist, den gerichteten Graphen D als ungerichteten Wald auf den Pseudoknoten zu verstehen und daraus rekursiv einen Spannbaum zu berechnen. Abbildung 10: Rekursive Berechnung eines Spannbaums 2 12 GRAPHENALGORITHMEN Wir müssen (a) die Kanten der schwachen ZHK von D identifizieren und (b) jeder dieser Kanten eine Kante aus dem Eingangsgraph zuordnen. Schritt (a) ist klar und für (b): i j i j Abbildung 11: (b) Wir merken uns im Zuge jeder Aktualisierung von A eine repräsentative Kante aus dem Eingangsgraphen als Stellvertreter. Die Kante merken wir uns im Eintrag A (i, j). Das kostet uns keine zusätzlichen Prozessoren oder Rechenschritte (bis auf Konstante). Auch die Kostenminimierung funktioniert ähnlich. Wir haben als Eingabe die symmetrische Matrix M ∈ Qn×n >0 der Kantengewichte des Graphen. Wir suchen einen Spannbaum mit minimalen Kosten. Dieses Problem lässt sich mittels folgender Modifikation des vorherigen SpannbaumAlgorithmus lösen. Anstatt des benachbarten Pseudoknoten mit dem numerisch kleinsten Index zu wählen, wählen wir den Pseudoknoten, welcher über die günstigste ausgehende Kante erreichbar ist. 7 9 3 Abbildung 12: Iteration in der Berechnung eines minimalen Spannbaums Nach Pertubation der Kantengewichte wissen wir, dass es keine Uneindeutigkeiten mehr gibt. Alternativ können wir Kantenindizes vergeben. Analog zum alten Algorithmus kann man zeigen, dass auf den relevanten schwachen ZHK von D nur Kreise der Länge 2 entstehen. Die Komplexität bleibt erhalten. Lemma 2.4. Der so berechnete Spannbaum hat minimale Kosten. Beweis. Angenommen die Annahme ist falsch. Dann sei k die erste Iteration, in der wir einen Fehler gemacht haben, es also keine Spannbaum minimaler Gesamtkosten gibt, welcher die bisher ausgewählten Kanten enthält. Die in der Iteration k hinzugefügten Kanten seien in der Menge Ek gesammelt. Ohne die Kanten aus Ek haben wir bisher einen Spannbaum innerhalb der Pseudoknoten aufgebaut. Diese sind insgesamt kostenminimal. Fügen wir die Kanten der Menge Ek sukzessive ein, dann vollziehen wir Schritte des Prim-Algorithmus. Dieser ist korrekt. Somit ist die Menge Ek korrekt. Das ist ein Widerspruch zur Annahme. 04.05.16 VL07 2 GRAPHENALGORITHMEN 13 Satz 2.5. Kostenoptimale Spannbäume lassen sich mit O (n2 / log2 n) vielen Prozessoren in O (log2 n) vielen Schritten berechnen. 2.4 2-Zusammenhangskomponenten Als nächstes berechnen wir die 2-Zusammenhangskomponenten des Graphen G. Definition. Eine 2-ZHK ist eine inklusionsmaximale Knotenmenge U ⊆ V (G) mit folgender Eigenschaft: Zwischen je zwei Knoten u, v ∈ U existieren zwei Wege zwischen u und v, welche sich keinen inneren Knoten teilen. Abbildung 13: Ein Graph G und seine 2-ZHK Lemma 2.6. Zwei Kanten eines Graphen liegen in derselben 2-ZHK genau dann, wenn es einen Kreis durch beide Kanten gibt. Beweis. GT1. Idee: Kantendisjunkte u, v-Wege. Zuerst berechnen wir einen Spannbaum T von G in einem beliebigen Knoten r. Wir berechnen zu diesem Knoten die Präordnung von T . Abbildung 14: Ein gewurzelter Baum T und markiert der Teilbaum gewurzelt in u Diese Präordnung (=Tiefensuch-Struktur) lässt sich bei Bäumen effizient parallel berechnen. Wir wollen eine Graphen G′ konstruieren. Dafür reicht es aus, die Fundamentalkreise von G zu betrachten. Seien u, v ∈ V (G). Dann heißt v Nachfolger von u bzw. u ≤ v, wenn v im Teilbaum gewurzelt in u vorkommt. Zum Testen von ”u ≤ v” reicht es, zu fragen: • Gilt preorder (u) ≤ preorder (v) und • Gilt preorder (v) < preorder (u) + Anzahl Knoten im Teilbaum von u 09.05.16 VL08 2 14 GRAPHENALGORITHMEN Abbildung 15: Anwendung von Schritt 2 for all (u, v) ∈ E (G) ∖ E (T ) mit u ≤ v in parallel do // Regel 1 add ((u, v) , (v, father (v))) to G′ end for all (u, v) ∈ E (G) ∖ E (T ) mit u ≰ v, v ≰ u in parallel do // Regel 2 add ((u, v) , (u, father (u))) to G′ add ((u, v) , (v, father (v))) to G′ end for all (u, v) ∈ E (T ) , u ≤ v, u ≠ r, ∃ Kante e joining a desc. of v to a non-desc. of u in parallel do add ((u, v) , (u, father (u))) to G′ // Regel 3 end Algorithmus 11 : Parallele Berechnung von 2-Zusammenhangskomponenten Dabei ist preorder (v) die Präordnungszahl von v und die Anzahl der Knoten im Teilbaum von u können wir effizient parallel berechnen (vgl. Übung). Wir starten mit G′ = (E (G) , ∅) und fügen sukzessive Kanten gemäß Algorithmus 15 ein. Damit sind alle Fundamentalkreise abgedeckt. 3 3 3 3 3 1 3 3 3 2 2 3 Abbildung 16: Konstruktion des Graphen G′ Lemma 2.7. Sind zwei Kanten im selben Fundamentalkreis, dann liegen sie in der gleichen ZHK von G′ . Beweis. Übung. Idee: Zeige per Fallunterscheidung und Induktion, dass die Regeln der Konstruktion ausreichen. Lemma 2.8. Ist C ein Kreis in G und sind e ≠ f Kanten auf C (d.h. e, f liegen in der gleichen 2-ZHK), dann liegen e und f in der gleichen ZHK von G′ . 2 GRAPHENALGORITHMEN 15 Beweis. Sei C = C1 ⊕ ⋅ ⋅ ⋅ ⊕ Ck , wobei Ci Fundamentalkreise sind. Ist k minimal, dann gilt folgendes für alle i, j mit 1 ≤ i, j ≤ k: Es gibt Indizes r1 , . . . , rs mit r1 = i und rs = j so, dass Crt mit Crt+1 mindestens eine Kante gemeinsam hat. Nach Lemma 2.7 liegen alle Kanten eines Fundamentalkreises in der gleichen ZHK von G′ . Seien e ∈ E (Ci ) , f ∈ E (Cj ) und seien Cr1 , . . . , Crs wie oben gewählt. Alle Kanten von Crt liegen in der gleichen ZHK von G′ wie alle Kanten von Crt+1 , t = 1, . . . , s − 1. Damit liegen auch e, f in der gleichen ZHK von G′ . Mit Lemma 2.6 folgt, dass zwei Kanten aus der gleichen 2-ZHK von G in der gleichen ZHK von G′ liegen. Die Rückrichtung gilt ebenfalls: Lemma 2.9. Liegen zwei Kanten e, f von G in der gleichen ZHK von G′ , dann liegen e, f in der gleichen 2-ZHK von G. Beweis. Ist (g, h) ∈ E (G′ ), dann liegen g, h auf einem gemeinsamen Fundamentalkreis in G nach Konstruktion. Ist (e1 , . . . , ek ) ein Weg in G′ mit e1 = e und ek = f , dann liegen jeweils ei und ei+1 in der gleichen 2-ZHK von G, nach Lemma 2.6. Somit liegen alle Kanten aus {e1 , . . . , ek } in der gleichen 2-ZHK von G, da die Eigenschaft in der gleichen 2-ZHK zu liegen eine Äquivalenzrelation auf den Kanten ist. Satz 2.10. Die Berechnung der 2-ZHK eines Graphen ist in der Klasse N C. 2.5 Eulertouren Als nächstes zeigen wir, wie man Eulertouren berechnet. Dabei starten wir mit dem Fall gerichteter Graphen. Satz 2.11. Ein zusammenhängender gerichteter Graph hat eine Eulertour genau dann, wenn es in jedem Knoten gleich viele eingehende wie ausgehende Kanten gibt. Beweis. Graphentheorie I. Bemerkung. Die obige Bedingung lässt sich effizient parallel testen (ohne Beweis). Wir nehmen an, der Eingangsgraph G = (V, E) ist gerichtet und erfüllt die Bedingungen des Satzes. Wir nehmen an, dass die Kanten des Graphen in einem Array EDGE gespeichert sind. Jetzt sortieren wir EDGE auf zwei verschiedene Arten. EDGE sortieren wir gemäß folgender Ordnung: (i, j) < (k, l) wenn j < l oder j = l, i < k Das geht mit O (n) Prozessoren in O (log n) Schritte. Ähnlich definieren wir ein Array SUCC (Successor), welches aus EDGE durch Sortieren gemäß folgender Ordnung entsteht: (i, j) < (l, k) wenn i < l oder i = l, j < k EDGE und SUCC zusammen ergeben eine Partition von E in geschlossene gerichtete Kantenzüge. Definition. Ein geschlossener, gerichteter Kantenzug heißt Zykel. Für den Graphen aus Abbildung 17 ergibt sich: EDGE = [(2, 1) (4, 1) (3, 2) (7, 2) (1, 3) (6, 3) (3, 4) (5, 4) (1, 5) (6, 5) (2, 6) (4, 6) (5, 7)] SUCC = [(1, 3) (1, 5) (2, 1) (2, 6) (3, 2) (3, 4) (4, 1) (4, 6) (5, 4) (5, 7) (6, 3) (6, 5) (7, 2)] 11.05.16 VL09 2 16 GRAPHENALGORITHMEN 2 3 6 1 7 4 5 Abbildung 17: Ein eulerscher Graph G Das funktioniert immer! (→ Übung). Zum leichteren Zugriff speichern wir im Eintrag SUCC (i) einen Zeiger P (i) mit P (i) = j für das j, wo SUCC (i) = EDGE (j). Den Zeiger P können wir beim Sortieren von EDGE zu SUCC gewinnen. Nun haben wir den Graphen schon implizit in disjunkte Zykel zerlegt. Diese müssen wir nun geschickt zu einer Eulertour zusammensetzen. In Schritt 2 des Algorithmus konstruieren wir einen Hilfsgraphen G′ = (V ′ , E ′ ) mit V ′ = V ∪ {Zykel in der Partition von E}. Dazu repräsentieren wir jeden Zykel durch eine Kante daraus. Dies wird die lexikografisch kleinste Kante des Zykels sein. Im Beispiel wird der erste Zykel durch (1, 3) repräsentiert, der zweite durch (1, 5). Dazu definieren wir ein Array REP, wobei REP (i) die Kante ist, die den Zykel repräsentiert, auf dem die Kante EDGE (i) liegt. Im Beispiel ergibt sich REP = [(1, 3) (1, 5) (1, 3) (1, 5) (1, 3) (1, 5) (1, 5) (1, 5) (1, 5) (1, 5) (1, 5) (1, 5) (1, 5)] Das Array REP rechnen wir mittels Verdoppeln aus: for all i ∈ [m] in parallel do REP (i) ← SUCC (i) end repeat ⌈log m⌉ times for all i ∈ [m] in parallel do REP (i) ← min {REP (i) , REP (P (i))} P (i) ← P (P (i)) end end Algorithmus 12 : Berechnung von REP Dafür brauchen wir mit O (log m) Prozessoren O (log n) viele Schritte. Sei C die Menge der Kanten, die Zykel repräsentieren. Wir definieren G′ = (V ′ , E ′ ) als V ′ = V ∪ C und E ′ = {(u, c) ∶ u ∈ V, c ∈ C, Knoten u kommt im von C repräsentierten Zykel vor} Zur Konstruktion von G′ erstellen wir ein Array EDGE′ , welches die Kanten von E ′ enthält. Wir berechnen das Folgende, wobei (u, v) = EDGE (i): for all i ∈ [m] in parallel do EDGE′ (2i − 1) ← (u, REP (i)) EDGE′ (2i) ← (v, REP (i)) end Algorithmus 13 : Konstruktion von EDGE′ Dies geht mit O (m) Prozessoren in O (1) vielen Schritten. 23.05.16 VL10 2 17 GRAPHENALGORITHMEN 1 2 (1; 3) 3 4 5 6 7 (1; 5) Abbildung 18: Der Graph G′ zu G for all i ∈ {1, . . . , 2m − 1} in parallel do if EDGE′ (i) = EDGE′ (i + 1) then EDGE′ (i) ← ∞ end end Algorithmus 14 : Entfernung der Mehrfachkanten In EDGE′ kommen Kanten evtl. mehrfach vor. Um dies zu beheben, sortieren wir EDGE′ lexikografisch. Danach kommen Mehrfachkanten konsekutiv im Array vor. Wir überschreiben alle bis auf eine dieser Mehrfachkanten: Nach einer weiteren Sortierung enthalten die ersten Einträge von EDGE′ genau die Kanten aus E ′ in einfacher Ausfertigung. Sei EDGE′ (i) = (v, w) ∈ E ′ . Wir brauchen ein ”Zertifikat” für das Vorkommen des Knotens v aus dem durch w repräsentierten Zykel Zw . Dies soll eine Kante der Form (x, v) auf Zw sein. Eine solche Kante finden wir beim Erstellen von EDGE′ : Beispiel. CERTIFICATE (1, (1, 3)) ← (2, 1) CERTIFICATE (2, (1, 3)) ← (3, 2) Eine Wahl haben wir nur bei den Knoten 4, 5, 6 im Zykel zu (1, 5). Wir wählen CERTIFICATE (4, (1, 5)) ← (3, 4) CERTIFICATE (5, (1, 5)) ← (1, 5) CERTIFICATE (6, (1, 5)) ← (2, 6) Wir brauchen O (m) viele Prozessoren und O (log n) viel Zeit. In Schritt 3 des Algorithmus berechnen wir einen Spannbaum T von G′ . Das geht mit O (n′2 / log2 n′ ) vielen Prozessoren in O (log2 n′ ) vielen Schritten, wobei n′ = ∣V ′ ∣ = O (m + n). Dazu berechnen wir eine gerichteten Baum T ′ , wobei wir jede Kante von T durch ein Paar antiparallele Kanten ersetzen. Wir suchen eine spezielle Eulertour auf T ′ . Dazu berechnen wir in Schritt 4 einen ”Zykel” L auf den Kanten von G und T ′ gemeinsam. Der Zykel alterniert zwischen Wegen in T der Länge 2 und Wegen in G entlang den entsprechenden Zyklen in G. Sei w ∈ C und seien (v0 , w) , (v1 , w) , . . . , (vα−1 , w) die in T ′ nach w eingehenden Kanten. Es sei (iα , vα ) = CERTIFICATE (vα , w) für alle α ∈ {0, . . . , α − 1}. Wir fügen jetzt zu EDGE und SUCC die Kanten aus T ′ hinzu. Für jedes (vα , w) und (w, vα ) aus T ′ fügen wir einen entsprechenden Eintrag zu EDGE hinzu. Wir setzen SUCC (vα , w) ← (vα , jα ) 2 18 GRAPHENALGORITHMEN (iα ; vα ) (vα ; jα ) vα W Abbildung 19: und SUCC (iα , vα ) ← (w, vα ) Es bleiben die Einträge aus SUCC (w, vα ) zu bestimmen. Es seien (vα , w0 ) , . . . , (vα , wk−1 ) die in T ′ von vα ausgehenden Kanten. Dann setze SUCC (wi , vα ) ← (vα , wi+1(mod k) ) EDGE und SUCC zusammen definieren einen Zykel L auf den Kanten in E (T ′ ) ∪ E (G), wie gewünscht. Dieser ist wie folgt aufgebaut: Benutzt L eine Kante der Form (wi , vα ), dann folgt die Kante (vα , wi+1(mod k) ). Dann gehen wir entlang des Zykels wi+1 mod k weiter, wobei wir mit der Kante (vα , jα ) = SUCC (CERTIFICATE (vα , wi+1(mod k) )) starten. Wir laufen entlang wi+1 bis zu nächsten Kante der Form (iβ , vβ ). Dann gehen wir über (wi+1 , vβ ) zu vβ . Nachtrag: Benutzt L eine Kante der Form (wi , vα ), dann folgt die Kante (vα , wi+1(mod k) ). Dann geht es in wi+1(mod k) weiter, startend mit (vα , jα ). Sobald wir auf eine Kante der Form (iβ , vβ ) = CERTIFICATE (vβ , wi+1(mod k) ) treffen mit (vβ , wi+1(mod k) ) ∈ E (T ′ ), gehen wir zu vβ usw. 2 5 1 4 6 5 (1; 5) (1; 3) 7 2 3 3 4 6 Abbildung 20: G und T ′ zusammen Für jeden Knoten aus T ′ haben wir eine zyklische Ordnung auf den ausgehenden Kanten definiert. Für die Knoten aus V ist das klar: Nach Definition ist SUCC (wi , vα ) = (vα , wi+1(k) ) Für die Knoten w aus C folgt auf das Einsteigen in den Zykel über (wα , wi+1(k)? ) das Aussteigen über (w, vβ ). Dadurch wird eine zyklische Ordnung auf den ausgehenden Kanten in T ′ definiert. Damit ist für jede Kante (v, w) bzw. (w, v) , v ∈ V, c ∈ C genau eine Kante 25.05.16 VL11 2 19 GRAPHENALGORITHMEN next (v, w) bzw. next (w, v) definiert, so wie in der Übung. Somit liefert L eine Eulertour auf T ′ ab, wie in der Übung bewiesen. In G läuft L eine Menge von Teilstücken von Zykeln ab; jeweils zwischen zwei Kanten vα , vβ . Dabei steigen wir jeweils mit der Kante (vα , jα ) ein und enden mit der Kante (iβ , vβ ). Da L eine Eulertour auf T ′ beschreibt geht L jedes Teilstück genau einmal ab. Somit beschreibt L auch auf G eine Eulertour. Schritt 4 geht offenbar effizient parallel. Im letzten Schritt 5 löschen wir die Kanten aus T ′ aus der Tour L. Dazu nutzen wir, dass Kanten aus T ′ in L immer die Form (w, v) , (v, w′ ) haben, wobei v ∈ C, w, w′ ∈ C. Wir berechnen repeat 2 times for all e ∈ EDGE in parallel do if SUCC (e) ∈ E (T ′ ) then SUCC (e) ← SUCC (SUCC (e)) end end end Algorithmus 15 : Schritt 5: Entfernen der Kanten aus T ′ in L Schritt 5 geht mit O (m) Prozessoren in O (1) Schritten und nun definiert das SUCCArray eine tournext-Funktion, also Satz 2.12. Die Berechnung von Eulertouren in gerichteten Graphen ist in N C. Jetzt zur ungerichteten Variante: Wir haben einen ungerichteten eulerschen Graphen G gegeben. Wir müssen eine Orientierung G′ berechnen, für die jeder Knoten genauso viele eingehende wie ausgehende Kanten hat. G G0 Abbildung 21: Eulersche Orientierung G′ von G Dann gilt: Jede Eulertour in G′ ist eine in G, aber nicht umgekehrt. Zunächst spalten wir die Kanten in G zu je zwei antiparallelen Kanten auf. Das gibt uns wieder ein EDGE-Array. Diesmal sortieren wir EDGE lexikografisch: (i, j) < (k, l) ⇔ i < k oder i = k, j < l Jede Kante (u, v) statten wir mit einem Label aus: (u, v0 ) , . . . , (u, vd−1 ) wobei u d-viele Nachfolger hat. Dann setzen wir ein Array SUCC auf: for all v und i ∈ {0, . . . , d (v) − 1} ungerade in parallel do SUCC (vi , v) ← (v, vi+1 mod d(v) ) SUCC (vi+1 mod d(v) , v) ← (v, vi ) end Algorithmus 16 : Initialisierung von SUCC 2 20 GRAPHENALGORITHMEN SUCC vi+1 vi v SUCC Abbildung 22: Konstruktion des Arrays SUCC Durch EDGE und SUCC sind wieder Zykel definiert. Dabei gibt es (ohne Beweis) zu jedem Zykel genau einen anderen Zykel, der umgekehrt läuft. Wir löschen je einen antiparallelen Zykel pro Paar. Dafür kopieren wir das EDGE-Array in ein Array ”REP”. Wir verdoppeln gemäß Algorithmus 21. repeat ⌈log m⌉ times for all i ∈ {1, . . . , 2m} in parallel do REP (i) ← min {REP (i) , REP (SUCC (i))} SUCC (i) ← SUCC (SUCC (i)) end end Algorithmus 17 : Berechnung von REP mit Verdoppeln Dabei ist min das lexikografische Minimum. Anschließend steht in REP(i) die lex. kleinste Kante aus dem Zykel, der die Kante EDGE(i) enthält. Dann löschen wir gemäß Algorithmus 22. for all e = (i, j) in parallel do if REP (i, j) > REP (j, i) then EDGE (i, j) ← ∞ end end Algorithmus 18 : Löschen von antiparallelen Zykeln Danach sind alle Kanten aus dem jeweils lex. größerem Zykel mit ∞ überschrieben. Sortieren und abschneiden nach dem m-ten Eintrag von EDGE liefert einen gerichteten Graphen mit der gewünschten Eigenschaft (eulersch). Jetzt können wir den Algorithmus für den gerichteten Fall anwenden. 30.05.16 VL12 2 GRAPHENALGORITHMEN 2.6 21 Maximale Matchings Definition. Ein Matching M in G ist eine Menge von Kanten, welche paarweise keinen gemeinsamen Knoten haben. Ein Matching heißt maximal, falls keine weitere Kante aus G hinzugefügt werden kann. Abbildung 23: Zwei (inklusions-)maximale Matchings Der Algorithmus besteht aus der logarithmisch oft iterierten Anwendung der Subroutine ’SPLIT’, welche auf der Berechnung von Eulertouren basiert. Sei G unser Eingabegraph. Wir bezeichnen mit Gi den Graphen vor Iteration i, also G1 = G. In der Iteration i wird ein Matching Mi von Gi berechnet. Es sei k die kleinste ganze Zahl mit 2k ≤ ∆ (G) ≤ 2k+1 +1. Die Knoten v mit Grad 2k ≤ d (v) ≤ 2k+1 + 1 nennen wir aktiv. Dieses k und die aktiven Knoten können wir effizient parallel bestimmen. Der Graph, auf den die j-te innere Iteration von SPLIT angewendet wird, nennen wir Gji . • In Schritt 1 von SPLIT berechnen wir d (v) für v ∈ V (Gji ), k, ∆ (G) und die Menge der aktiven Knoten. Dies geht effizient parallel • In Schritt 2 berechnen wir den Graphen Hij . Dieser entsteht aus Gji durch Löschen aller Kanten, die nicht zu einem aktiven Knoten inzident sind. In Hij gibt es womöglich Knoten mit ungeradem Grad. Davon gibt es aber gerade viele, nach dem Handschlaglemma. Wir verbinden diese mit einem neuen Knoten v. Im Anschluss haben alle Knoten geraden Grad • In Schritt 3 berechnen wir für jede Komponente von Hij eine Eulertour • In Schritt 4 des Algorithmus geben wir jeder Kante von Hij ein Label 0 oder 1. Dazu alternieren wir die Label entlang der Eulertour, startend mit Label 0 • In Schritt 5 löschen wir die Kanten mit Label 0 aus Gji . Ist Gji jetzt ein Matching, dann geben wir dies als Mi aus, sonst wende SPLIT auf Gj+1 ← Gji an. Das Labeln i machen wir mit Rangbestimmung in einer verketteten Liste Lemma 2.13. Nach höchstens 3 + ⌈log ∆ (Gi )⌉ vielen inneren Iterationen ist Mi gefunden. Beweis. Es sei 2k ≤ ∆ (G1i ) ≤ 2k+1 + 1, wobei k kleinstmöglich ist. Ist u ein aktiver Knoten in G1i , dann gilt dG1 (u) dG1 (u) ⌊ i ⌋ ≤ dG2i (u) ≤ ⌊ i ⌋+1 2 2 Also ist 2k−1 ≤ ∆ (G2i ) ≤ 2k + 1 und jeder Knoten, der in G1i aktiv war, ist es auch in G2i . Allgemein: aktive Knoten bleiben aktiv. Somit ist für j = ⌈log ∆ (Gi )⌉+1, dass 1 ≤ ∆ (Gji ) ≤ 3. Dies gilt, da 01.06.16 VL13 2 22 GRAPHENALGORITHMEN G11 : v H11 : v G21 : v G31 : M1 = f(1; 2) ; (5; 6) ; (8; 10) ; (11; 12)g Abbildung 24: Eine Iteration von SPLIT 1 2k+1−⌈log ∆(Gi )⌉+1 + 1 ≤ 2 ⋅ 2k ⋅ 2−⌈log ∆(Gi )⌉+1 + 1 ≤ 2 ⋅ ∆ (G11 ) ⋅ 2−⌈log ∆(Gi )⌉ + 1 ≤3 In höchstens zwei weiteren Iterationen sind wir fertig. Nach der Berechnung von Mi löschen wir Mi und alle inzidenten Kanten aus Gi . Dies gibt uns Gi+1 . Ist Gi+1 ohne Kanten, dann geben wir ⋃ij=1 Mj aus. Ansonsten wende SPLIT auf Gi+1 an. Sofern der Algorithmus terminiert, geben wir ein maximales Matching aus. Das zeigen wir im nächsten Lemma. Definition. Eine Knotenüberdeckung ist eine Teilmenge der Knoten, so dass jede Kante mindestens einen Knoten daraus enthält. 2 23 GRAPHENALGORITHMEN Abbildung 25: Ein maximales Matching für G Es bleibt zu zeigen, dass wir SPLIT nur logarithmisch oft anwenden. Ist Gi+1 nicht leer, dann existiert eine Knotenüberdeckung Ci+1 von Gi+1 mit ∣Ci+1 ∣ ≤ 2 ∣Ci ∣ 3 dabei ist C1 = V . Dann gilt: Es gibt ein j ≤ ⌈log3/2 V ⌉ für das Gj+1 leer ist. Wir folgern: Lemma 2.14. Nach der Anwendung von höchstens ⌈log3/2 ∣V ∣⌉ äußeren Iterationen ist ein maximales Matching gefunden. Beweis. Seien Ai die Knoten, welche in der Anwendung von SPLIT auf Gi aktiv werden. Dies ist eine Knotenüberdeckung von Gi . Andernfalls gäbe es eine Kante (x, y) mit x, y ∉ Ai . Dies ist ein Widerspruch, da x oder y spätestens bei ∆ (Gji ) ≤ 3 aktiv werden. Wir zeigen jetzt, dass (nach einer kleinen Modifikation) mindestens die Hälfte der Knoten aus Ai von Mi gematcht werden. Das bedeutet ∣Ai ∖ V (Mi )∣ ≤ ∣Ai ∣ 2 Wir greifen ein, sobald ∆ (Gji ) ≤ 3. Wenn wir statt der Menge der Kanten mit Label 0, die Kanten mit dem Label löschen, durch welches wir mehr Kanten übrig lassen, dann haben wir ∣Mi ∣ ≥ ∣E (Gji )∣ /2 Dies liegt daran, dass wir höchstens zwei Iterationen auf Gji anwenden. Wir sind fertig, wenn ∣E (Gji )∣ ≥ ∣Ai ∣, da ∣Mi ∣ ≥ ∣Ai ∣ /4 und diese Kanten aus Mi jeweils zwischen Knoten aus Ai verlaufen. 06.06.16 VL14 Satz 2.15. Die Berechnung von maximalen Matchings liegt in N C. 3 24 PARALLELES SORTIEREN 3 Paralleles Sortieren Gegeben ein Arra ’Key’ von n vielen Schlüsseln, z.B. ganze Zahlen. Wir wollen durch sukzessives Vertauschen der Werte erreichen, dass Key (i) ≤ Key (j) für 1 ≤ i ≤ j ≤ n Eine wichtige Anweisung ist compare-exchange(i, j) für i ≤ j. Dabei werden die Werte Key (i) und Key (j) getauscht, sofern Key (i) > Key (j). Das geht in O (1). Wegen des CREW-Modells können compare-exchange (i, j) und compare-exchange (j, k) nicht parallel angewendet werde. Aus dem Grundstudium wissen wir, das jeder Algorithmus Ω (n log n) viele Vergleiche machen muss. Mit O (n) vielen Prozessoren brauchen wir also im Worst-Case O (log n) viele Schritte. Wir entwickeln einen solchen Algorithmus, basierend auf merge-sort. 3.1 Einfache Sortieralgorithmen Die Algorithmen basieren auf dem Binärbaumprinzip. Der Baum hat die Tiefe O (log n). merge merge key(1) merge key(2) key(3) key(4) Abbildung 26: Aufrufbaum eines merge-Algorithmus Also ist die Gesamtlaufzeit des Algorithmus O (log n ⋅ Laufzeit von merge) Die effiziente Gestaltung von merge ist Thema dieses Kapitels. Odd-Even-Merge Der erste Algorithmus wendet maximal viele parallele compare-exchange Operationen an: odd-even-merge. Ist S ein Array von Schlüsseln, dann sei ODD (S) das Teilarray der Einträge von S mit ungeradem Index. Analog ist EVEN (S) definiert. Sind R und S zwei Arrays gleicher Länge, dann sei interleave (R, S) = (R (1) , S (S) , R (2) , S (2) , . . . ) Wir definieren die Operation odd-even(S): for all i ∈ {1, . . . , ⌊ ∣S∣−1 ⌋} in parallel do 2 compare-exchange (2i, 2i + 1) end Algorithmus 19 : odd-even (S) welche parallel Nachbarpaare vergleicht und ggf. vertauscht. Der erste Eintrag wird ausgelassen. 08.06.16 VL15 3 25 PARALLELES SORTIEREN Beispiel. odd-even (3, 2, 1, 4, 7, 9, 6, 8) = (3, 1, 2, 4, 7, 6, 9, 8) Die Operation join (R, S) ist definiert als join (R, S) ← odd-even (interleave (R, S)) wobei R, S zwei gleichlange Arrays sind. Wir brauchen für join O (1) viele parallele Schritte bei O (n) vielen Prozessoren. (n = ∣S∣ = ∣R∣) Jetzt können wir odd-even-merge (R, S) definieren. Dabei sind R, S gleichlang und vorsortiert. Wir verschmelzen R und S zum sortierten Array M : if ∣S∣ = ∣R∣ = 1 then M ← (R (1) , S (1)) compare-exchange (1, 2) else Modd ← odd-even-merge (odd (R) , odd (S)) Meven ← odd-even-merge (even (R) , even (S)) M ← join (Modd , Meven ) end Algorithmus 20 : odd-even-merge (R, S) Die Rekursion hat O (log n) Tiefe. Eventuell müssen wir das Eingabearray (des gesamten Algorithmus) auf eine Größe der Form 2k bringen. Beispiel. Seien R = (2, 6, 10, 15) , S = (3, 4, 5, 8). Dann ist odd (R) = (2, 10) , even (R) = (6, 15) , odd (S) = (3, 5) , even (S) = (4, 8). Außerdem Modd = (2, 3, 5, 10) , Meven = (4, 6, 8, 15). Dann gilt: M = join (Modd , Meven ) = odd-even (interleave (Modd , Meven )) = odd-even (2, 4, 3, 6, 5, 8, 10, 15) = (2, 3, 4, 5, 6, 8, 10, 15) Zum Beweis der Korrektheit nutzen wir folgendes Lemma. Danach können wir uns auf den Fall beschränken, in dem das Array 0/1-wertig ist. Lemma 3.1. Wenn jedes 0/1-wertige Array korrekt sortiert wird, dann auch jedes Z-wertige. Beweis. Sei f ∶ Z → Z eine monotone Funktion, d.h. f (z) ≤ f (z + 1) für alle z ∈ Z. Es gilt folgende Aussage: Wird ein Array A = (a1 , . . . , an ) von unserem Algorithmus zu B = (b1 , . . . , bn ) sortiert, dann wird das Array (f (a1 ) , . . . , f (an )) zu (f (b1 ) , . . . , f (bn )) sortiert. (→ Übung) Also gilt: Ist bi > bi+1 für ein i, dann ist f (bi ) ≥ f (bi+1 ). D.h. ist f (bi ) < f (bi+1 ), dann wurde auch f (a1 ) , . . . , f (an ) falsch sortiert. Wir setzen nun f ∶ Z → {0, 1} auf ⎧ ⎪ ⎪0 falls z ≤ bi+1 f (z) = ⎨ ⎪ 1 sonst ⎪ ⎩ Damit gilt f (bi ) = 1, aber f (bi+1 ) = 0, also wurde das 0/1-wertige Array (f (a1 ) , . . . , f (an )) fehlerhaft sortiert. Somit gilt: Wenn alle 0/1-wertigen Arrays korrekt sortiert werden, dann auch alle ganzzahligen. 3 26 PARALLELES SORTIEREN Das Lemma gilt offenbar nicht nur für den auf odd-even-merge basierenden Algorithmus, sondern für alle Sortieralgorithmen. Satz 3.2. Der Algorithmus odd-even-merge sortiert korrekt. Beweis. Nach Lemma 3.1 können wir uns auf 0/1-wertige Arrays beschränken. Zudem reicht es, die Korrektheit von odd-even-merge induktiv zu verifizieren. Seien 0/1-wertige Arrays R, S der Länge 2k = n gegeben. R, S seien vorsortiert, also von der Form R = 0 . . . 0 1 . . . 1 und S = 0 . . . 0 1 . . . 1 ²² ²² r n−r s n−s Ist k = 0, dann ist M offenbar korrekt sortiert. Sei also k ≥ 1 und off-even-merge korrekt für alle Paare von kürzeren Arrays der Länge 2i , 0 ≤ i ≤ k − 1. Somit werden Modd und Meven korrekt berechnet. Danach ist Modd = 0 . . . 0 1 . . . 1 und Meven = 0 . . . 0 1 . . . 1 ² ² ⌈ r2 ⌉+⌈ 2s ⌉ ⌊ r2 ⌋+⌊ 2s ⌋ Es sei δ = ⌈ 2r ⌉ + ⌈ 2s ⌉ − (⌊ 2r ⌋ + ⌊ 2s ⌋). Offenbar gilt δ ∈ {0, 1, 2}. 1. δ = 0. Es gilt interleave (Modd , Meven ) = (0o , 0e , . . . , 0o , 0e , 1o , 1e , . . . , 1o , 1e ) wobei 0o , 1o aus Modd und 1e , 0e aus Meven stammen. Da keine Vertauschungen durch odd-even stattfinden, ist M von der Form (0, . . . , 0, 1, . . . , 1), also korrekt sortiert. 2. δ = 1. Es gilt interleave (Modd , Meven ) = (0o , 0e , . . . , 0o , 0e , 0o , 1e , 1o , . . . , 1o , 1e ) Wieder ist M von der korrekten Form, da odd-even nicht vertauscht. 3. δ = 2. Es gilt interleave (Modd , Meven ) = (0o , 0e , . . . , 0e , 0o , 1e , 0o , 1e , . . . , 1o , 1e ) Wegen δ = 2 sind r, s beide ungerade, also ist r + s gerade. Somit wird Position r + s und r + s + 1 verglichen und vertauscht. Also ist M korrekt sortiert. Literatur: K.E. Batcher : Sorting Networks and their Applications. Proc. AFIPS Spring Joint Comput. Conf., Vol. 32, 307-314 (1968) Bitonic-Merge Das zweite Verfahren bitonic-merge funktioniert nach der gleichen Idee, d.h. maximal viele parallele compare-exchange Anwendungen. Das Verfahren kommt (in einer geschickten Implementierung) mit O (n/ log n) Prozessoren in O (log2 n) Laufzeit aus. Dies ist optimal! Das Verfahren basiert auf einer join-Operation, diese basiert wiederum auf der shuffle-Operation. Ist S ein Array gerader Länge n = 2k , dann ist shuffle (S): S1 ← (S (1) , S (2) , . . . , S ( n2 )) S2 ← (S ( n2 + 1) , S ( n2 + 2) , . . . , S (n)) S ← interleave (S1 , S2 ) Algorithmus 21 : shuffle (S) 13.06.16 VL16 3 27 PARALLELES SORTIEREN shuffle (S) for all i ∈ {1, . . . , n2 } in parallel do compare-exchange (2i − 1, 2i) end Algorithmus 22 : join (S) M ← R ∣ S −1 repeat log 2n times M ← join (M ) end return M Algorithmus 23 : bitonic-merge (R, S) Dies kann in O (1) Schritten bei O (n) Prozessoren berechnet werden. Das join (S) ist so definiert: Jetzt können wir bitonic-merge definieren. Seien R und S zwei vorsortierte Arrays der Länge n = 2k . Es sei S −1 das Array S in umgekehrter Reihenfolge und sei R ∣ S −1 die Konkatenation von R und S −1 . Das Array R ∣ S −1 ist monoton steigend bis zum n-ten Eintrag und fallend vom (n + 1)ten bis zum letzten Eintrag. Es ist also biton. Beispiel. Sei R = (1, 5, 9, 12) und S = (2, 3, 4, 11). Dann ist R ∣ S −1 = (1, 5, 9, 12, 11, 4, 3, 2) Wir wenden log ∣M ∣ = 3 Mal join an: shuffle (M ) = (1, 11, 5, 4, 9, 3, 12, 2) join (M ) = (1, 11, 4, 5, 3, 9, 2, 12) shuffle (M ) = (1, 3, 11, 9, 4, 2, 5, 12) join (M ) = (1, 3, 9, 11, 2, 4, 5, 12) shuffle (M ) = (1, 2, 3, 4, 9, 5, 11, 12) join (M ) = (1, 2, 3, 4, 5, 9, 11, 12) Die Anzahl der Iterationen ist fest, jede weitere Iteration verhindert die korrekte Sortierung. Warum genau log M viele Iterationen gebraucht werden, wird im 0/1-Fall deutlich: join (00001111) = (01010101) join (01010101) = (00110011) join (00110011) = (00001111) Der Korrektheitsbeweis wird auf die Übung verlagert. 3 28 PARALLELES SORTIEREN 3.2 Sortieren in O (log n) bzw. Coles Algorithmus Sind zwei sortierte Arrays R, S gegeben, dann wollen wir diese in Zeit O (1) verschmelzen zu merge (R, S). Dazu brauchen wir mehr Informationen über R, S, welche wir uns beim Aufbau des Binärbaums generieren. OBdA gilt: • das zu sortierende Array besteht aus ganzen Zahlen • keine zwei Zahlen daraus sind gleich Zur Berechnung von merge benutzen wir Sampler. Informell: Ein Array R ist ein Sampler für ein Array S, wenn die Einträge von R ”gleichmäßig” in dem Array merge (R, S) vorkommen. Formal: Definition. R ist ein Sampler für S, wenn für alle i, j gilt, dass zwischen dem i-ten und dem (i + j)-ten Wert aus −∞ ∣ R ∣ ∞ höchstens 2j + 1 viele Werte aus S liegen. Dabei sagen wir für x, y, z ∈ Z mit x < y ≤ z, dass y zwischen x und z liegt. Beispiel. R = (1, 8, 12) ist ein Sampler für S = (0, 1, 7, 9, 11), aber keiner für S = (0, 2, 7, 8, 10, 11, 12) Insbesondere liegen zwischen je zwei benachbarten Werten aus R höchstens drei Werte aus S. Nun können wir die merge-Operation definieren. Dafür sei R ein Sampler für die zu verschmelzenden Arrays S, T . Diese seien gleichlang und vorsortiert. Dann ist: for all i ∈ [∣R∣ + 1] in parallel do Si ← Array der Einträge aus S zwischen R (i − 1) und R (i) Ti ← Array der Einträge aus T zwischen R (i − 1) und R (i) Mi ← merge (Si , Ti ) end return M1 ∣ M2 ∣ . . . ∣ Mi+1 Algorithmus 24 : merge-with-help (S, T, R) Dabei sei R (0) = −∞ und R (∣R∣ + 1) = ∞. Als Schema: R (i) trennt jeweils S (i) von S (i + 1) und T (i) von T (i + 1). Beispiel. R = (5, 10, 12, 17) S = (1, 4, 6, 9, 11, 12, 13, 16, 19, 20) T = (2, 3, 7, 8, 10, 14, 15, 17, 18, 21) Dann gilt S1 = (1, 4) , S2 = (6, 9) , S3 = (11, 12) , S4 = (13, 16) , S5 = (19, 20) T1 = (2, 3) , T2 = (7, 8, 10) , T3 = ∅, T4 = (14, 15, 17) , T5 = (18, 21) M1 = (1, 2, 3, 4) , M2 = (6, 7, 8, 9, 10) , M3 = (11, 12) , M4 = (13, 14, 15, 16, 17) M5 = (18, 19, 20, 21) 15.06.16 VL17 3 PARALLELES SORTIEREN 29 Wir geben folgendes zurück (1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21) Wir wollen, dass merge-with-help in O (1) Schritten bei O (n) Prozessoren läuft. Zunächst klären wir die Berechnung der Si , Ti , Mi und von M1 ∣ . . . ∣ M∣R∣+1 . Dafür benutzen wir folgendes Hilfsarray: Sei R ein Array und x ∈ Z ein Schlüsselwert. Eventuell gilt x ∉ R. Definition. Wir definieren den Rang eines Elements in einem Array als rank (x, R) ∶= #Werte aus R, welche echt kleiner als x sind. Sind R, S zwei sortierte Arrays, dann sei das Array rank (R, S) definiert als rank (R, S) ∶= (rank (R [1] , S) , rank (R [2] , S) , . . . , rank (R [∣R∣] , S)) Die i-te Stelle von rank (R, S) ist also der Rang von R (i) in S. Über diese Hilfsarrays lässt sich merge-with-help realisieren. Lemma 3.3. Seien S, T, R wie vorausgesetzt. Dann lässt sich merge-with-help (S, T, R) in O (1) Schritten mit O (∣S∣ + ∣T ∣) Prozessoren realisieren, sofern rank (R, S) , rank (S, R) , rank (R, T ) und rank (T, R) gegeben sind. Beweis. Übung. Nun zeigen wir, wie wir die Sampler konstruieren. Für zwei sortierte Arrays R, S sei merge (R, S) die sortierte Verschmelzung von R und S. Lemma 3.4. Es sei R ein Sampler für R′ und S ein Sampler für S ′ , alle sortiert. Dann ist M = merge (R, S) ein Sampler für R′ und für S ′ . Beweis. Für alle i, j gibt es i′ , j ′ mit j ′ ≤ j, R (i′ ) ≤ M (i) und R (i′ + j ′ ) ≥ M (i + j). Somit liegen höchstens 2j ′ + 1 viele Werte aus R′ zwischen R (i′ ) und R (i′ + j ′ ). Also liegen höchstens 2j ′ + 1 ≤ 2j + 1 Werte R′ zwischen M (i) und M (i + j). Daher ist M ein Sampler für R′ , genauso für S ′ . Bemerkung. Unter den Voraussetzungen des Lemma gilt nicht unbedingt dass M ein Sampler für M ′ = merge (R′ , S ′ ) ist. Setze z.B. R = (2, 7) , R′ = (2, 5, 6, 7) , S = (1, 8) , S ′ = (1, 3, 4, 8) Dann gilt M = (1, 2, 7, 8) , M ′ = (1, 2, 3, 4, 5, 6, 7, 8) und M sampelt M ′ nicht. Lemma 3.5. Unter den Voraussetzungen gilt: Zwischen dem i-ten und dem (i + j)-ten Element aus (−∞ ∣ M ∣ ∞) liegen höchtens 2j + 4 Werte aus M ′ . 3 30 PARALLELES SORTIEREN Beweis. Sei M ′′ = −∞ ∣ M ∣ ∞, R′′ = −∞ ∣ R ∣ ∞ und S ′′ = −∞ ∣ S ∣ ∞. OBdA ist M ′′ (i) aus R′′ . Es seien r viele Werte des Teilarrays M ′′ (i) , . . . , M ′′ (i + j) aus R′′ , entsprechend sei s analog. 1. M ′′ (i + j) ist ein Wert aus R′′ . Da R ein Sampler für R′ ist, liegen höchstens 2 (r − 1)+1 viele Werte aus R′ zwischen M ′′ (i) und M ′′ (i + j). Wähle i′ maximal und darunter j ′ minimal so, dass S ′′ (i′ ) ≤ M ′′ (i) und S ′′ (i′ + j ′ ) ≥ M ′′ (i + j). Dann gilt j ′ ≤ s + 1, da im Teilarray M ′′ (i) , . . . , M ′′ (i + j) nur genau s viele Elemente aus S ′′ liegen. Somit liegen höchstens 2 (s + 1) + 1 viele Elemente aus S ′ zwischen S ′′ (i′ ) und S ′′ (i′ + j ′ ) und somit zwischen M ′′ (i) und M ′′ (i + j). Insgesamt höchstens 2 (r − 1) + 1 + 2 (s + 1) + 1 viele Werte aus M ′ zwischen M ′′ (i) und M ′′ (i + j). Wegen 2 (r − 1) + 1 + 2 (s + 1) + 1 = 2 (r + s) + 2 = 2j + 1 gilt die Behauptung. 2. 20.06.16 VL18 Definition. Sei R ein beliebiges Array. Das Array reduce (R) sei das Array, welches jedes vierte Element aus R enthält. Lemma 3.6. Unter den Voraussetzungen des vorherigen reduce (merge (R, S)) ist ein Sampler für reduce (merge (R′ , S ′ )). Lemmas gilt: Beweis. Sei T = reduce (merge (R, S)) und T ′ = reduce (merge (R′ , S ′ )). Zudem sei T ′′ = −∞ ∣ T ∣ ∞. Seien i, j so, dass 1 ≤ i < i + j ≤ ∣T ′′ ∣. In dem Teilarray T ′′ (i) bis T ′′ (i + j) liegen j + 1 Elemente, somit liegen zwischen T ′′ (i) und T ′′ (i + j) höchstens 4j + 1 viele Elemente aus M ′′ = −∞ ∣ merge (R, S) ∣ ∞, inklusive T ′′ (i) selber. Seien also i′ und j ′ so, dass M ′′ (i′ ) = T ′′ (i) und M ′′ (i′ + j ′ ) = T ′′ (i + j) Dann ist j ′ ≤ 4j. Nach Lemma 3.5 liegen zwischen M ′′ (i′ ) und M ′′ (i′ + j ′ ) höchstens 2j ′ + 4 ≤ 8j + 4 viele Werte aus M ′ = merge (R′ , S ′ ). Höchstens 2j + 1 viele dieser Werte kommen in T ′ vor. Somit ist T ein Sampler für T ′ . Wir beschreiben den Algorithmus zunächst ohne die Berechnung der rank (X, Y )-Arrays, d.h. wir nehmen an, dass wir merge-with-help in O (1) Schritten berechnen können. In jedem inneren Knoten des merge-Baums führen wir eine Reihe von merge-with-help und reduceOperationen aus. Die Aufgabe eines Knoten v ist das Sortieren der Schlüsselwerte unter v. Das Array dieser Schlüsselwerte sei mit listv bezeichnet. Jeder innere Knoten macht dies: Der linke Sohn u und der rechte Sohn w senden jeweils ein Array R bzw. S an v. Dabei ist R bzw. S ein sortiertes Array mit Werten aus listu bzw. listw . Zudem gilt ∣R∣ = ∣S∣. v berechnet daraus ein sortiertes Array T = merge (R, S). Dann gibt v das Array reduce (T ) an seinen Vater weiter. Genauer: Ab einem Zeitschritt erhält v die Arrays R1 , S1 , im nächsten Schritt 3 31 PARALLELES SORTIEREN v u a w b c d Abbildung 27: Schema im Aufruf-Baum mit listv = (a, b, c, d) R2 , S2 usw. Die Länge dieser Arrays verdoppelt sich in jedem Schritt. Als letzte Elemente dieses Datenstroms werden Rk und Sk zu v hochgeschickt, dabei ist ∣Rk ∣ = ∣listu ∣ = ∣Sk ∣ = ∣listw ∣ Dann ist Rk die Sortierung von listu und Sk die Sortierung von listw . Ist Ri und Si die Eingabe, dann berechnet v ein Array Ti und gibt dies an seinen Vater weiter. Genauer: Ab einem Zeitschritt erhält v die Arrays R1 und S1 . Im folgenden Schritt erhält v die Arrays R2 und S2 usw. Die Länge dieser Arrays verdoppelt sich in jedem Schritt. Als letzte Elemente dieses Datenstroms werden Rk und Sk zu v hochgeschickt, dabei ist ∣Rk ∣ = ∣listu ∣ und ∣Sk ∣ = ∣listw ∣. Dann ist Rk die Sortierung von listu und Sk die Sortierung von listw . Ist Ri und Si die Eingabe, dann berechnet v ein Array Ti und gibt dies an seinen Vater weiter. v berechnet valuev = merge-with-help (Ri , Si , valv ) • Ist i ≤ k, dann gibt v das Array Ti = reduce (valv ) an seinen Vater weiter • Ist i = k + 1, dann senden wir reduce′ (valv ) an den Vater von v, wobei reduce′ (valv ) jedes zweite Element aus valv enthält • Ist i = k + 2, dann senden wir Tk+2 = valv an den Vater von v hoch Die Arrays Ri und Si verdoppeln stets die Länge, ebenso die Ti . Nach Lemma 3.6 ist Ti ein Sampler für Ti+1 , da Ri ein Sampler für Ri+1 und Si ein Sampler für Si+1 ist. Diese Invariante gilt in jedem inneren Knoten des merge-Binärbaums. Definition. Ist ∣valv ∣ = ∣listv ∣, dann nennen wir v vollständig. Werden Ri , Si an einen Knoten v übergeben, wird in der nächsten Runde Ti an den Vater von v weitergegeben. Somit ist der Vater von v vollständig drei Schritte, nachdem v vollständig ist. Schematisch: input(v) input(f ather(v)) R1 , S 1 - R2 , S2 T1 ... ... Rk , S k Tk−1 Tk Tk+1 Tk+2 Die Blätter des merge-Baums sind nach der ersten Runde vollständig. Demnach ist die Wurzel des Baumes nach 3 ⋅ log n = O (log n) Schritten vollständig. Die verwendeten mergewith-help-Operationen laufen in O (1) Schritten. Dafür brauchen wir O (∣valv ∣) viele Prozessoren in jedem Knoten v, welcher noch nicht vollständig ist. Es gilt das Folgende: 22.06.16 VL19 3 32 PARALLELES SORTIEREN 1 T1 = ; valv = (7; 8) R1 = (7) 3 valv = (3; 5; 7; 8) S1 = (8) 4 T2 = (8) 2 R2 = (3; 7) T4 = (2; 4; 6; 8) S2 = (5; 8) 5 valv = (1; 2; 3; 4; 5; 6; 7; 8) R4 = ; S4 = ; T3 = (4; 8) valv = (1; 2; 3; 4; 5; 6; 7; 8) R3 = (1; 3; 4; 7) S3 = (2; 5; 6; 8) T5 = (1; 2; 3; 4; 5; 6; 7; 8) valv = (1; 2; 3; 4; 5; 6; 7; 8) R4 = ; S4 = ; Abbildung 28: Beispiel der Sortierung des Arrays (1, 3, 4, 7 ∣ 2, 5, 6, 8) durch den Algorithmus von Cole. Betrachtet wird der Input, aktuelle Wert und der Output des festen Knoten v. In Schritt 3 wird v vollständig. Lemma 3.7. Es reichen O (n) viele Prozessoren aus, da ∑ ∣valv ∣ = O (n) v unvollständig Beweis. Übung. Nun schauen wir uns merge-with-help nochmal genau an. Wir wissen aus der Invariante, dass Ri ein Sampler für Ri+1 (Si für Si+1 ) ist. Nach Lemma 3.4 ist valv = merge (Ri , Si ) ein Sampler für Ri+1 und Si+1 . Zur Ausführung von merge-with-help(Ri+1 , Si+1 , valv ) in O (1) brauchen wir noch die Ränge rank (valv , Ri+1 ) , rank (Ri+1 , valv ) , rank (valv , Si+1 ) , rank (Si+1 , valv ) Wir nehmen dazu an, dass wir für jedes berechnete Array X das Array rank (X, X) kennen. Lemma 3.8. Zu einem sortierten Array R kann der Rang rank [x, R] für ein Schlüsselwort x mit O (∣R∣) Prozessoren in O (1) Schritten berechnet werden. Gilt auch, wenn x ∉ R. Beweis. Wir setzen R′′ = (−∞ ∣ R ∣ ∞). Dann berechnen wir for all i ∈ [∣R∣ + 1] in parallel do if R′′ (i − 1) < x ≤ R′′ (i) then rank (x, R) ← i − 1 end end Algorithmus 25 : merge-with-help (S, T, R) Die if-Bedingung ist genau für ein i wahr, somit wird CREW nicht verletzt. 3 PARALLELES SORTIEREN 33 Lemma 3.9. Sind R, S sortierte Arrays und T = merge (R, S) mit R∩S = ∅, dann können die Arrays rank [R, S] und rank [S, R] mit O (∣T ∣) Prozessoren in O (1) Schritten bestimmt werden. Beweis. Nach der Grundannahme kennen wir rank [R, R], rank [S, S] und rank [T, T ]. Sei x ∈ R beliebig. Dann ist rank [x, S] = rank [x, T ] − rank [x, R] Somit können die Werte aus rank [R, S] in O (1) mit O (∣T ∣) Prozessoren berechnet werden. Analog für rank [S, R]. Lemma 3.10. Sind R, S, R′ , S ′ und M = merge (R, S) sortierte Arrays, wobei R für R′ und S für S ′ ein Sampler ist und kennen wir die Rangarrays rank [R′ , R] und rank [S ′ , S], dann können wir rank [R′ , M ], rank [S ′ , M ], rank [M, R′ ] und rank [M, S ′ ] in Zeit O (1) mit O (∣R′ ∣ + ∣S ′ ∣) vielen Prozessoren berechnen. Beweis. • rank [R′ , M ] , rank [S ′ , M ]: Dafür bestimmen wir die Teilarrays Ri′ . Dabei ist R1′ das Array der Werte aus R′ , welche zwischen −∞ und R (1) liegen. R2′ sind die Werte aus R′ , welche zwischen R (1) und R (2) usw. Das geht in O (1) Schritten mit O (∣R′ ∣) vielen Prozessoren, da wir rank [R, R′ ] kennen. (siehe Beweis von Lemma 3.3) Für jedes i mit 1 ≤ i ≤ ∣R∣+1 sei Mi das Arrays der Werte aus M , die zwischen R (i − 1) und R (i) liegen. Dabei lassen wir R (i) selber aus Mi raus. Zudem definieren wir R (0) = −∞ und R (∣R∣ + 1) = ∞. Somit ist jedes Mi eine Teilmenge aus S. Beachte: Mi lässt sich mit O (∣Mi ∣) vielen Prozessoren berechnen. Dafür benutzen wir rank [M, M ]. Nun berechnen wir: for all i ∈ [∣R∣ + 1] in parallel do for all x ∈ Ri′ compute rank[x, Mi ] rank [x, M ] ← rank [x, Mi ] + rank [R (i − 1) , M ] + 1 end end Algorithmus 26 : merge-with-help (S, T, R) Dies läuft in O (1), da wir rank [M, M ] kennen und rank [x, Mi ] nach Lemma 3.8 in O (1) Schritten mit O (∣Mi ∣) Prozessoren berechnen können. Da R ein Sampler für R′ ist, gilt zudem ∣Ri ∣ = O (1). Analog können wir rank [S ′ , M ] berechnen. • rank [R, R′ ] , rank [S, S ′ ]: Sei x ∈ R, sagen wir x = R (i). Wir berechnen zuerst y = ′ Ri+1 (1), dann rank [y, R′ ]. Letzteres geht in O (1), da wir rank [R′ , R′ ] kennen. Sei j = rank [y, R′ ]. Ist x = R′ (j), dann ist rank [x, R′ ] = rank [R′ (j) , R′ ] = j − 1 Andernfalls ist R′ (j) < x < y, also rank [x, R′ ] = j. Das geht in O (1) Schritten mit O (∣R′ ∣) vielen Prozessoren. Analog für rank [S, S ′ ]. Wir brauchen noch rank [R, S ′ ] und rank [S, R′ ]. Mittels Lemma 3.9 können wir rank [R, S] bzw. rank [S, R] ausrechnen. Zusammen mit rank [R, R′ ] bzw. rank [S, S ′ ] gibt uns das in O (1) Schritten rank [R, S ′ ] und rank [S, R′ ], da R ein Sampler für R′ und S ein Sampler für S ′ ist. Aus rank [R, R′ ] und rank [S, R′ ] können wir rank [M, R′ ] berechnen, da M = merge (R, S). Analog für rank [M, S ′ ]. 27.06.16 VL20 3 PARALLELES SORTIEREN 34 Abbildung 29: Ein primitives Sortiernetzwerk Als Invariante des Algorithmus erhalten wir uns folgendes: Zum Datenstrom Z1 , . . . , Zk wird stets rank [Zi+1 , Zi ] zeitgleich mit Zi+1 gesendet. Angenommen wir kennen (im Knoten v) rank [Ri+1 , Ri ] und rank [Si+1 , Si ]. Zur Berechnung von merge-with-help (Ri+1 , Si+1 , merge (Ri , Si )) benötigen wir, nach Lemma 3.3 die Arrays rank [Mi , Ri+1 ], rank [Mi , Si+1 ], rank [Ri+1 , Mi ] und rank [Si+1 , Mi ]. Nach den Invarianten können wir Lemma 3.10 auf R = Ri , S = Si , R′ = Ri+1 , S ′ = Si+1 anwenden. Wir erhalten in O (1) Schritten die gewünschten Rangarrays. Dafür brauchen wir O (∣R′ ∣ + ∣S ′ ∣) = O (∣valv ∣) viele Prozessoren. Aus rank [Ri+1 , Mi ] und rank [Si+1 , Mi ] können wir leicht rank [Ri+1 , reduce (Mi )] und rank [Si+1 , reduce (Mi )] errechnen. Daraus können wir rank [Mi+1 = merge (Ri+1 , Si+1 ) , reduce (Mi )] berechnen, also auch rank [reduce (Mi+1 ) , reduce (Mi )] = rank [Ti+1 , Ti ] und damit gilt die Invariante weiterhin. Satz 3.11. Ein Array mit n Einträgen können wir mit O (n) Prozessoren in O (log n) Schritten sortieren. Im Gegensatz zu diesem Algorithmus hängen die Positionen, die mit compare-exchange verglichen werden, bei den Algorithmen odd-even-merge und bitonic-merge nicht vom Input ab. Definition. Solche Algorithmen nennt man Sortiernetzwerke. Bemerkung. Es gibt Sortiernetzwerke, die eine Folge mit O (n) Prozessoren in O (log n) Schritten sortieren. Diese nutzen sogenannte Expander-Graphen. 4 P-VOLLSTÄNDIGKEIT 35 P-Vollständigkeit 4 Wir wissen schon, dass N C ⊆ P. Zudem glaubt man, dass folgende Vermutung gilt: Vermutung 4.1. N C ≠ P. Ähnlich wie bei der Vermutung P ≠ N P ist unklar, wie ein Beweis aussehen könnte. Wir wollen Probleme identifizieren, welche in P ∖ N C liegen, sofern die Vermutung gilt. Dafür brauchen wir einen geeigneten Reduktionsbegriff. Wir ziehen uns auf Entscheidungsprobleme zurück. Definition. Seien A, B zwei Entscheidungsprobleme aus P. Eine N C-Reduktion von A auf B ist eine Abbildung f mit folgenden Eigenschaften: 1. Ist x eine Instanz von Problem A, dann ist f (x) eine Instanz von B 2. x ist eine Ja-Instanz für A ⇔ f (x) ist eine Ja-Instanz für B 3. Es gibt einen Algorithmus, welcher f effizient parallel berechnet Dann gilt B ∈ N C ⇒ A ∈ N C. Definition. Ein Problem A ∈ P nennen wir P-vollständig, wenn es zu jedem Problem B ∈ P eine N C-Reduktion von B auf A gibt. Wir schreiben auch B ≤N C A wenn es eine N C-Reduktion von B auf A gibt. Es gilt: Ist A ein P-vollständiges Problem, dann gilt A ∈ N C genau dann, wenn N C = P. 4.1 Erzeugbarkeit ist P-vollständig Als ’erstes’ P-vollständiges Problem wählen wir das Erzeugbarkeits-Problem. Definition. Das Erzeugbarkeitsproblem besteht aus folgendem Input: • eine Grundmenge X und Y ⊆ X • ein binärer Operator ○ ∶ X × X → X (den wir in O (1) auswerten können) • ein ausgezeichnetes Element x ∈ X, das Zielelement Wir müssen entscheiden, ob wir x aus der Menge Y durch sukzessives Anwenden von ○ erhalten können. Wir sollen also entscheiden, ob x im Abschluss der Menge Y unter ○ ist. Zunächst zeigen wir, dass das Erzeugbarkeitsproblem in P liegt. Dies sieht man über folgenden Lösungsalgorithmus: Da ∣Z∣ , ∣Z ′ ∣ ≤ ∣X∣ und die while-Schleife höchstens ∣X∣ Mal durchlaufen wird, läuft der Algorithmus in O (∣X 3 ∣), also polynomiell. Somit liegt Erzeugbarkeit in P. Zur Vereinfachung führen wir das Problem 3-Erzeugbarkeit ein. Definition. Das 3-Erzeugbarkeitsproblem ist das Erzeugbarkeitsproblem wobei wir an- 04.07.16 VL21 4 P-VOLLSTÄNDIGKEIT 36 Z←Y Z ′ ← {r ○ s ∶ r, s ∈ Z} while Z ≠ Z ′ do Z ← Z′ Z ′ ← {r ○ s ∶ r, s ∈ Z} end return x ∈ Z? Algorithmus 27 : Lösungsalgorithmus für das Erzeugbarkeitsproblem statt dem binären Operator ○ ∶ X × X → X einen ternären Operator next ∶ X × X × X → X betrachten. Es gilt: Lemma 4.2. 3-Erzeugbarkeit ≤N C Erzeugbarkeit. Beweis. Übung. Nun sei A ∈ P. Wir wollen zeigen, dass A ≤N C 3-Erzeugbarkeit. Sei M eine Turingmaschine, welche A in Polynomialzeit entscheidet. Wir modellieren die Rechenschritte von M als ternären Operator next. Die Startmenge Y entspricht dem Input von M . Das Zielelement entspricht der akzeptierenden Konfiguration der Maschine M . Es gibt ein Polynom p, welches die Anzahl der Rechenschritte von M in Abhängigkeit von der Größe des Inputs n beschränkt. Demnach liest die Maschine auch nur höchstens p (n) viele Felder ein. Wir machen noch folgende Annahmen: • Auf der Stelle 0 und p (n) + 1 des Bands steht jeweils ein $ • Der Input steht an den Stellen 1 bis n • Am Ende der Rechnung schreibt M entweder 0 oder 1 auf Stelle 1 • Die Maschine überschreibt die $-Symbole nicht und wandert nie auf die Stellen −1 oder p (n) + 1 • Das Alphabet von M sei Γ = (◻, $, 0, 1) • Die Zustandsmenge von M sei Q. Dabei sei qs der Start- und qe der Endzustand Den Inhalt der Stelle i des Bands zum Zeitpunkt t bezeichnen wir mit B (i, t) ∈ Γ. Der Input ist also B (1, 0) ∣ B (2, 0) ∣ . . . ∣ B (n, 0) und B (0, 0) = B (p (n) + 1, 0) = $ Der Output der Maschine bzw. dessen Entscheidung ist B (1, p (n) + 1). Satz 4.3. Erzeugbarkeit ist P-vollständig. Beweis. Nach Lemma 4.2 reicht es zu zeigen, dass 3-Erzeugbarkeit P-vollständig ist. Wir simulieren die Schritte der Turingmaschine M , welches unser Problem A ∈ P löst, über die next-Operation. 06.07.16 VL22 4 P-VOLLSTÄNDIGKEIT 37 Bk−1;t Bk;t Bk+1;t α; ; β; q γ; ; ::: ::: β 0; ; Bk;t+1 Abbildung 30: Ein Beispiel einer Bewegung der Turingmaschine nach rechts Zu dem in einer Stelle gespeicherten Buchstaben merken wir uns auch den aktuellen Zustand. Diesen setzen wir auf ∅, falls der Lesekopf gerade nicht auf das betrachtete Feld schaut. Wir setzen also X = {0, . . . , p (n) + 1} × {0, . . . , p (n) + 1} × Γ × (Q ∪ {∅}) ® ´¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¸ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¶ ´¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¸ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¶ Alphabet ´¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¸ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¶ Ort Zeit Zustand Wir definieren next ∶ X × X × X → X wie folgt: next (a, b, c) = d ⇔ a = (k − 1, t, α, qa ) , b = (k, t, β, qb ) , c = (k + 1, t, γ, qc ) wobei mindestens zwei von qa , qb , qc gleich ∅ sind. Dazu soll d = (k, t + 1, δ, qd ) sein, wobei die Turingmaschine lokal aus a, b, c im nächsten Schritt d berechnen würde. Wir fügen ein Dummy-Element D ein, welchen den Wert von next zu den inkompatiblen Eingabewerten ist. Wir setzen Y wie folgt zu einer Inputmenge I: Y = {(i, 0, I (i) , ∅) ∶ i ∈ {2, . . . , n}} ∪ {(1, 0, I (1) , qs )} ∪ {(i, 0, ◻, ∅) ∶ i ∈ {n + 1, . . . , p (n)}} ∪ {(0, 0, $, ∅)} ∪ {(p (n) + 1, 0, $, ∅)} Zudem ist x = (1, p (n) + 1, 1, qe ) das Zielelement. Somit liegt x im Abschluss von Y unter next genau dann, wenn die Turingmaschine die Ausgabe true zum Input I berechnet. Es gilt 2 ∣X∣ , ∣Y ∣ ∈ O (p (n) ) 2 Es reichen also O (p (n) ) viele Prozessoren zur Berechnung der Menge aus. Die Berechnung 3 von next benötigt höchstens O (∣X∣ ) viele Prozessoren, also auch nur polynomiell viele. Das ganze geht in polylogarithmischer Zeit, also haben wir eine N C-Reduktion gefunden und damit A ≤N C 3-Erzeugbarkeit Bemerkung. Erzeugbarkeit liegt in N C, wenn der Operator ○ assoziativ ist, also a○(b ○ c) = (a ○ b) ○ c für alle a, b, c ∈ X. Dies sieht man leicht: Lässt sich x aus Y ableiten, dann gilt x = (((y1 ○ y2 ) ○ y3 ) ⋅ ⋅ ⋅ ○ yk ) für geeignete y1 , . . . , yk ∈ Y . Wir konstruieren einen Graphen G = (X, E). Dabei haben wir eine Kante (a, b), wenn es ein c ∈ Y gibt, mit a ○ c = b. Gibt es einen gerichteten Weg von einem y ∈ Y zu x, dann und nur dann ist x im Abschluss von Y . 4 P-VOLLSTÄNDIGKEIT 4.2 4.2.1 38 Weitere P-vollständige Probleme Auswertungsproblem (CVP) Definition. Das Auswertungsproblem (engl. Circuit-Value-Problem, CVP) fragt nach der Ausgabe eines Schaltkreises (d.h. DAG) bei gegebenen Input. Dabei bilden Knoten ohne Vorgänger den Input und Knoten ohne Nachfolger den Output. Jeder innere Knoten repräsentiert eine der logischen Verknüpfungen ∧, ∨ oder ¬. 1 1 1 _ ^ 1 : : _ ^ Abbildung 31: Ein Schaltkreis Definition. Ein Schaltkreis heißt monoton, falls alle inneren Knoten nur die Verknüpfungen ∧ oder ∨ enthalten. Satz 4.4. Das monotone Auswertungsproblem ist P-vollständig. Beweis. Die Idee ist, den Operator ○ durch ∧-Operatoren zu ersetzen. Für jedes Element v ∈ X ∖ Y bestimmen wir alle Paare (u1 , w1 ) , . . . , (uk , wk ) mit ui , wi ∈ X und ui ○ wi = v für alle i = 1, . . . , k. Wir definieren Fv = (u1 ∧ w1 ) ∨ (u2 ∧ w2 ) ∨ ⋅ ⋅ ⋅ ∨ (uk ∧ wk ) Dazu bestimmen wir einen Schaltkreis mit Input u1 , w1 , . . . , uk , wk und Output v. ^ ^ ^ _ _ Abbildung 32: Ein Gadget in der Konstruktion für das monotone Auswertungsproblem Diese Teile konkatenieren wir zu einem großen Schaltkreis S. Input sind die Knoten aus Y , Output ist x. Die Kreisfreiheit bekommen wir aus der Reduktion von oben, d.h. aus dem Beweis von Satz 4.3. 4 P-VOLLSTÄNDIGKEIT 4.2.2 39 Tiefensuche (DFS) In der ”sequentiellen Welt” ist die Tiefensuche (DFS) ein effizientes Verfahren, das in Graphenalgorithmen als Teilschritt benutzt wird. DFS berechnet die Funktion dfi ∶ V (G) → N wobei G ein gerichteter Graph ist wie folgt: counter ← 0 dfs (v0 ) , v0 ∈ V (G) procedure dfs(v) if dfi (v) is not assigned yet then counter++ dfi (v) ← counter for v → u ∈ E (G) do dfs (u) end end end Algorithmus 28 : Sequentielle Tiefensuche im gerichteten Graphen G Beachte: Die Reihenfolge, in der die Nachbarn besucht werden, muss fixiert werden, damit dfi eindeutig ist. Leider gilt selbst für dieses grundlegende Verfahren: Satz 4.5. DFS ist P-vollständig. Beweis. Wir reduzieren CVP auf DFS. OBdA (siehe Übung) können wir annehmen, dass V (C) topologisch sortiert ist, d.h. V (C) = {v1 , . . . , vn }, wobei C eine Instanz von CVP ist. Weiterhin beschränken wir die zulässigen Gatter auf • konstante 1-Input • x ⋅ y ∶= ¬ (x ∨ y) = (¬x) ∧ (¬y) Dies ist erlaubt, da 1 ⋅ 1 = 0, ¬x = x ⋅ 0, x ∨ y = (x ⋅ y) ⋅ 0 sowie x ∧ y = (x ⋅ 0) ⋅ (y ⋅ 0). Wir müssen einen gerichteten Graphen G konstruieren, sodass das Auswerten der CVP-Knoten einer Tiefensuche in G entspricht. G konstruieren wir als Vereinigung kantendisjunkter Teilgraphen mit Knotenmenge • first (v) , last (v) , s (v) , t (v) für alle v ∈ V (C) • e (v, u) für alle v → u ∈ E (C) • anonyme Hilfsknoten, lokal zu jedem Teilgraphen Beschrifte die Kanten v → u, welche von einem Knoten v ausgehen mit natürlichen Zahlen, welche die Reihenfolge angeben, in der die Nachbarn von v besucht werden. Fehlende Label entsprechen implizit einer 1. G besteht aus den folgenden Teilgraphen: a) Grundstruktur. Hier ist die topologische Sortierung relevant b) Struktur für jeden Input-Knoten v mit Kindern u1 , . . . , uk c) Struktur für jeden inneren Knoten v mit Eltern-Knoten w, w′ und Kindern u1 , . . . , uk Der Beweis des Satzes folgt aus dem nächsten Lemma. 08.07.16 VL23 4 P-VOLLSTÄNDIGKEIT f irst(v) last(v1 ) 40 s(v) e(w; v) f irst(v) 2 e(w0 ; v) 2 f irst(v2 ) ::: 2 s(v) 2 2 e(v; u1 ) e(v; u1 ) .. . 2 .. . last(vn−1 ) : : : f irst(vn ) last(v) .. . e(v; u2 ) ... e(v; uk ) t(v) last(v) e(v; u2 ) ... 2 e(v; uk ) t(v) Abbildung 33: Struktur a), b) & c) Lemma 4.6. Sei G konstruiert wie oben. Dann gilt v ∈ V (C) erhält in C den Wert 1 ⇔ dfi (s (v)) < dfi (t (v)) startend mit dem Knoten first (v1 ). Beweis. Zeige per Induktion: Bis das erste Mal last (vn ) besucht wird, wird dfi genau in folgender Reihenfolge vergeben: • Struktur (b): first (v) → s (v) → ⋅ → e (v, u1 ) → ⋅ → ⋅ ⋅ ⋅ → e (v, uk ) → t (v) → last (v) • Struktur (c) und v erhält in C den Wert 1, d.h. der grüne Weg in Abbildung 33: first (v) → e (w, v) → e (w′ , v) → s (v) → ⋅ → e (v, u1 ) → ⋅ → ⋅ ⋅ ⋅ → e (v, uk ) → t (v) → last (v) • Struktur (c) und v erhält in C den Wert 0, d.h. der rote Weg in Abbildung 33: first (v) → t (v) → ⋅ → ⋅ ⋅ ⋅ → s (v) → last (v) Induktion: • I.A.: v1 ist ein 1-Input. (Struktur b) Alle e (v1 , ui ) sind unbesucht. Damit ist die geforderte Aussage klar. • I.S.: – Falls v ∈ C ein Input ist (Struktur b), sind per I.V. alle e (v, ui ) noch nicht besucht und die Aussage ist klar – Falls v ein ⋅-Gatter ist und in C den Wert 1 erhält, dann erhalten w, w′ jeweils in C den Wert 0, denn nur dafür gilt 0 ⋅ 0 = 1. Nach I.V. wurden alle anonymen Knoten in den zu w bzw. w′ gehörenden Struktuen bereits besucht, e (w, v) , e (w′ , v) wurden allerdings noch nicht besucht. Damit ist der DFS-Durchlauf aus der Beh. die einzige Möglichkeit die Struktur von first (v) beginnend zu durchmustern – Ansonsten ist v ein ⋅-Gatter, das in C mit 0 belegt wird. D.h. mindestens eines von w, w′ erhält in C eine 1, OE w′ . Per I.V. werden alle e (w′ , ⋅) bereits besucht und alle anonymen Knoten in den zu w′ , w gehörenden Strukturen. Damit bleibt nur noch die Durchmusterung aus der Behauptung Man beachte, dass die obige Konstruktion in polylogarithmischer Zeit auf einer PRAM berechenbar ist. Bemerkung. Durch eine Modifikation des Verfahrens lässt sich auch die P-Vollständigkeit von DFS in ungerichteten Graphen zeigen. 13.07.16 VL24 4 P-VOLLSTÄNDIGKEIT 4.2.3 41 Maximum Flow Als letztes Problem betrachten wir max-flow. Dabei haben wir einen gerichteten Graphen mit ganzzahligen Kapazitäten auf den Kanten. Wir suchen einen Fluss von einem Knoten s zu einem Knoten t. In jedem Knoten außer s, t soll die Flusserhaltung gelten. Die Kapazität einer Kante e nennen wir c (e). Satz 4.7. Die Bestimmung des maximalen Flusswerts ist P-vollständig. Beweis. Wir reduzieren vom Problem ’Auswertung’. Sei also G = (V, E) ein boolscher Schaltkreis. Wir können annehmen, dass G monoton ist, es gibt also keine Negations-Knoten. Zudem soll Folgendes gelten: 1. Jeder innere Knoten hat höchstens zwei Nachfolger 2. Jeder Inputknoten hat genau einen Nachfolger 3. Der Ausgabeknoten ist eine ∨-Verknüpfung Andernfalls können wir den Graphen effizient parallel so umformen, dass diese Eigenschaften gelten. Diese Version ist immer noch P-vollständig. Wir nehmen an, dass V = {0, 1, 2, . . . , n} und es gebe nur Kanten der Form (i, j) mit i > j (d.h. eine topologische Sortierung der Knoten). Wir führen zwei neue Knoten s, t ein. Unser Flussnetzwerk ist dann G′ = (V ∪ {s, t} , E ′ ). Dabei ist G′ auf den Knoten aus V gerade gleich G. Für jede Kante (i, j) ∈ E setzen wir c (i, j) = 2i . 1 1 0 0 64 32 _ 16 128 8 8 G ^ ^ 4 2 _ Abbildung 34: Der Ursprungs-Graph G Wir konstruieren den Graphen folgendermaßen: • für jeden Knoten (außer 0) mit Vorgängern j und k setzen wir diff (i) = 2j + 2k − d+ (i) ⋅ 2i • für jeden ∧-Knoten i fügen wir die Kante (i, t) mit c (i, t) = diff (i) hinzu • für jeden ∨-Knoten i fügen wir die Kante (i, s) mit c (i, s) = diff (i) hinzu • wir fügen die Kante (0, s) hinzu mit c (0, s) = ∑ c (j, 0) − 1 (j,0)∈E • wir fügen die Kante (0, t) mit c (0, t) = 1 hinzu 4 P-VOLLSTÄNDIGKEIT 42 s 16 0 0 32 1 1 64 32 _ 16 128 8 8 5 0 0 ^ ^ 4 2 _ 132 22 1 t Abbildung 35: Der Graph G′ konstruiert aus G • für jeden Inputknoten i setzen wir ⎧ ⎪ ⎪2i c (s, i) = ⎨ ⎪ 0 ⎪ ⎩ falls i ein true-Knoten ist sonst Damit ist G′ komplett. Abbildung 35 zeigt die Konstruktion an einem Beispielgraphen. Die Kodierung der Kapaziäten kann pro Kante O (n) Bits brauchen. Somit reichen O (n3 ) viele Prozessoren aus, um alle Kapazitäten in polylogarithmischer Zeit aufzuschreiben. Dies ist also eine N C-Reduktion. Als nächstes definieren wir einen Fluss F mit maximalem Flusswert. Dabei gilt der maximale Flusswert von F ist ungerade ⇔ das Outputbit des Schaltkreises ist true Wir definieren einen maximalen Fluss F auf G′ wie folgt: 1. F (s, i) = c (s, i) für alle Inputknoten i ∈ V 2. Ist i ein Inputknoten und (i, j) ∈ E, dann setzen wir F (i, j) = F (s, i) 3. Für jede Kante (i, j) mit i, j ∈ V setze F (i, j) = c (i, j) = 2i , falls i den Wert true im Schaltkreis hat, sonst F (i, j) = 0 4. Setze F (0, t) = 1, wenn 0 den Wert true im Schaltkreis hat, sonst 0 5. Für jeden ∧-Knoten i gleichen wir den überschüssigen Fluss über die Kante (i, t) aus 6. Für jeden ∨-Knoten j gleichen wir den überschüssigen Fluss über die Kante (j, s) aus Wir zeigen nun, dass der Fluss F maximalen Wert hat. Wir betrachten das Residualnetzwerk bzgl. F . Angenommen es gibt einen gerichteten s − t-Weg P . Dann muss P nach Konstruktion von F mit einer Rückwärtskante (s, i) starten, wobei i ein ∨-Knoten ist. Die letzte Kante von P muss eine Vorwärtskante sein, da es in G′ keine ausgehende Kante von t gibt. Es gibt sicherlich drei Knoten u, v, w ∈ V (G′ ) mit • (u, v) , (v, w) ∈ P und • (u, v) ist eine Rückwärtskante, also F (v, u) > 0 und • (v, w) ist eine Vorwärtskante, also F (v, w) < c (v, w) und 18.07.16 VL25 4 P-VOLLSTÄNDIGKEIT 43 • w ≠ s, u ≠ t Schauen wir uns den Knoten v an. Es sind (v, u) , (v, w) ∈ E (G′ ), also ist d+G′ (v) ≥ 2, somit ist v kein Inputknoten. • Fall 1: v ist ein ∧-Knoten. Dann ist F (v, u) > 0 und u ≠ t, also ist der Wert von v im Schaltkreis gleich true. Dann gilt aber F (v, t) = diff (v) = c (v, t). Somit sind alle ausgehenden Kanten von v in G′ maximal ausgelastet durch F . Dies ist ein Widerspruch zu F (v, w) < c (v, w). • Fall 2: v ist ein ∨-Knoten. Dann ist F (v, w) < c (v, w) und w ≠ s impliziert, dass v den Wert false im Schaltkreis hat. Dann sind aber auch die eingehenden Kanten mit Flusswert 0 besetzt. Nach der Flusserhaltung hat dann jede an v anliegenden Kante Flusswert 0. Dies ist ein Widerspruch zu F (v, u) > 0. Insgesamt ist F ein maximaler Fluss. Die eingehenden Kanten nach t in G′ bestimmen den Wert von F . Jede dieser Kanten, mit Ausnahme von (0, t), hat geraden Flusswert. Somit gilt: F hat ungeraden Flusswert genau dann, wenn F (0, t) = 1, also 0 den Wert true im Schaltkreis hat. Somit ist die Bestimmung der Parität des maximalen Flusswerts bereits P-vollständig. Bemerkung. Daher ist insbesondere unklar, ob Fragen der linearen Programmierung effizient parallel lösbar sind. Ende der Vorlesung 44 ABBILDUNGSVERZEICHNIS Abbildungsverzeichnis 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Das P-RAM-Modell; Quelle: Alan Kaminsky . . . . . . . . . . . . . . . . . . . Maximumsbestimmung mit einem Baum . . . . . . . . . . . . . . . . . . . . . . Die Werte von dist und die Zeiger von SUCC zu Beginn und nach 1. bzw. 2 Schritt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rekursionsbaum für die parallele Auswertung von Polynomen . . . . . . . . . Ein Graph G und seine Adjazenzmatrix M . . . . . . . . . . . . . . . . . . . . Graph G und Digraph D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Digraph D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pseudoknoten P am Ende der Iteration und Knoten, welche nach der Iteration keine Pseudoknoten mehr sind . . . . . . . . . . . . . . . . . . . . . . . . . . . . blablabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rekursive Berechnung eines Spannbaums . . . . . . . . . . . . . . . . . . . . . (b) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Iteration in der Berechnung eines minimalen Spannbaums . . . . . . . . . . . Ein Graph G und seine 2-ZHK . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein gewurzelter Baum T und markiert der Teilbaum gewurzelt in u . . . . . Anwendung von Schritt 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Konstruktion des Graphen G′ . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein eulerscher Graph G . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Graph G′ zu G . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . G und T ′ zusammen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eulersche Orientierung G′ von G . . . . . . . . . . . . . . . . . . . . . . . . . . Konstruktion des Arrays SUCC . . . . . . . . . . . . . . . . . . . . . . . . . . . Zwei (inklusions-)maximale Matchings . . . . . . . . . . . . . . . . . . . . . . . Eine Iteration von SPLIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein maximales Matching für G . . . . . . . . . . . . . . . . . . . . . . . . . . . Aufrufbaum eines merge-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . Schema im Aufruf-Baum mit listv = (a, b, c, d) . . . . . . . . . . . . . . . . . . . Beispiel der Sortierung des Arrays (1, 3, 4, 7 ∣ 2, 5, 6, 8) durch den Algorithmus von Cole. Betrachtet wird der Input, aktuelle Wert und der Output des festen Knoten v. In Schritt 3 wird v vollständig. . . . . . . . . . . . . . . . . . . . . . Ein primitives Sortiernetzwerk; Quelle: Oskar Sigvardsson für Wikipedia . . Ein Beispiel einer Bewegung der Turingmaschine nach rechts . . . . . . . . . Ein Schaltkreis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein Gadget in der Konstruktion für das monotone Auswertungsproblem . . . Struktur a), b) & c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Ursprungs-Graph G . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Graph G′ konstruiert aus G . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 . . . . . 4 5 6 7 8 . . . . . . . . . . . . . . . . . . . . 10 11 11 12 12 13 13 14 14 16 17 18 18 19 20 21 22 23 24 31 . . . . . . . . 32 34 37 38 38 40 41 42 . . . . . . . . . . . . . 2 3 4 6 7 8 8 9 9 10 14 16 16 Liste der Algorithmen 1 2 3 4 5 6 7 8 9 10 11 12 13 Grundgerüst eines parallelen Algorithmus . . . . . . . . . . Parallele Berechnung des Maximums in einem Array . . . Paralleles Durchmustern einer (verketteten) Liste . . . . . Parallele Berechnung kürzester Wege in einem Graphen . Berechnung der ZHK, Schritt 1 . . . . . . . . . . . . . . . . Berechnung der ZHK, Schritt 2 . . . . . . . . . . . . . . . . Berechnung der ZHK, Schritt 3 . . . . . . . . . . . . . . . . Berechnung der ZHK, Schritt 4 . . . . . . . . . . . . . . . . Berechnung der ZHK, Schritt 5 . . . . . . . . . . . . . . . . Berechnung der ZHK, Nachbesserungen in Schritt 1 . . . . Parallele Berechnung von 2-Zusammenhangskomponenten Berechnung von REP . . . . . . . . . . . . . . . . . . . . . . Konstruktion von EDGE′ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 LISTE DER ALGORITHMEN 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Entfernung der Mehrfachkanten . . . . . . . . . . . . Schritt 5: Entfernen der Kanten aus T ′ in L . . . . Initialisierung von SUCC . . . . . . . . . . . . . . . . Berechnung von REP mit Verdoppeln . . . . . . . . Löschen von antiparallelen Zykeln . . . . . . . . . . odd-even (S) . . . . . . . . . . . . . . . . . . . . . . . odd-even-merge (R, S) . . . . . . . . . . . . . . . . . shuffle (S) . . . . . . . . . . . . . . . . . . . . . . . . . join (S) . . . . . . . . . . . . . . . . . . . . . . . . . . bitonic-merge (R, S) . . . . . . . . . . . . . . . . . . . merge-with-help (S, T, R) . . . . . . . . . . . . . . . . merge-with-help (S, T, R) . . . . . . . . . . . . . . . . merge-with-help (S, T, R) . . . . . . . . . . . . . . . . Lösungsalgorithmus für das Erzeugbarkeitsproblem Sequentielle Tiefensuche im gerichteten Graphen G . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 19 19 20 20 24 25 26 27 27 28 32 33 36 39