3.3 Valiants -Merge (und warum es nicht ganz korrekt ist)
Leslie Valiant fand 1975 eine trickreiche Methode, zwei Arrays der Länge
mit in zu mergen. Der Algorithmus ist rekursiv und verwendet
folgendes Lemma als Grundbaustein:
Lemma 3.3.1
Sei ein sortiertes
Arrays mit und gegeben. Dann können wir
mit Prozessoren in Schritten berechnen.
Beweis von Lemma 3.3.1.
Wir nehmen an, dass alle Listenelemente verschieden sind, und auch dass .
Wir haben Prozessoren .
Prozessor überprüft, ob
gilt, wobei wir und setzen. Falls es gilt, weiß ,
dass
ist und schreibt diesen Wert in die Ergebniszelle. Da es immer genau
ein gibt, das () erfüllt, entstehen keine Schreibkonflikte.
Um nun zu berechnen, wählen wir jedes -te Element aus und
jedes
-te Element aus aus
und speichern sie in einem Array:
Mittels Lemma 3.3.1
bestimmen wir mit Prozessoren in Zeit
den Rang für jedes . Für bestimmtes
bezeichnen wir diesen Rang mit . Es gilt also
Bildlich gesprochen heißt dass, wir wissen, in welches "Teilstück" von das Element
gehört.
In einem nächsten Schritt bestimmen wir, wieder mit Hilfe von Lemma 3.3.1,
den Rang von in "seinem Interval" .
Wir tun dies parallel
für jedes und brauchen dafür wieder Prozessoren und
Zeit. Mit Prozessoren und Zeit haben wir also folgendes berechnet:
Wir kennen also für jedes . So können wir in Teilarrays
zerlegen und ebenso in , so dass
gilt. Mit der Schreibweise für eine Liste meinen wir, dass für jedes gilt.
Wir können nun also
wobei jedes rekursiv gelöst wird.
Zeitkomplexität
Anfangs haben unsere Listen und die Längen und . Nach einem Rekursionsschritt
haben wir Teilprobleme mit . Die Länge der "linken" Liste
schrumpft also in Zeitschritten von auf . Nach einem weiteren Rekursionsschritt
haben die linken Listen Länge und ganz allgemein nach Rekursionsebenen
Länge . Für jedes Teilproblem gehen wir davon aus, dass uns
Prozessoren zur Verfügung stehen.
Nach Ebenen haben wir Listen der Länge
erreicht. Das verbleibende Problem für können wir mit
Prozessoren
in Schritten berechnen. Insgesamt benötigen wir also Schritte,
um zu berechnen.
Anzahl der Prozessoren
Um die rechte Seite von () zu berechnen, müssen wir
für jedes berechnen.
Dies tun wir rekursiv. Dafür statten wir das Teilproblem
mit Prozessoren aus. Sei und .
Wir
wissen,
dass (bis eventuell auf das letzte Array); allerdings kann bis zu
Elemente erhalten;
wir wissen nur, dass ist. Wir können daher die benötigte Anzahl an Prozessoren
mit der Cauchy-Schwarz-Ungleichung abschätzen:
Wenn und anfangs also jeweiles Elemente haben, so können wir mit Prozessoren
verfahren und diese dann auch auf die Teilprobleme aufteilen - wir haben für jedes
Teilproblem ausreichend viele Prozessoren.
Was wir unterschlagen haben
Die obige Laufzeitanalyse unterschlägt ein wichtiges Detail: die Ungleichung
() sagt uns zwar, dass wir unsere hinreichend viele sind,
um jedes Teilproblem adäquat auszustatten, sagt uns aber nicht, welcher Prozessor
auf welchem Teilproblem arbeiten muss. Betrachten wir den -ten unserer
Prozessoren
für . Soll er zugeordnet werden?
Für die ersten Teilprobleme benötigen wir
Prozessoren. Prozessoren arbeiten also an ;
Prozessoren an und so weiter.
Der Haken hierbei: bei () handelt es sich um eine Präfixsumme.
Und um die zu berechnen bräuchten wir selbst wieder Zeit ,
wie wir in Kapitel 2.3 gelernt haben.
Valiant erkennt dieses Problem und unterscheidet zwischen Schritten, in denen
tatsächlich Elemente aus der zu sortierenden Menge verglichen werden und "Overhead" und
erwähnt, dass wachsender Overhead die Effizienzgewinne wieder zunichte macht.
Auf der einen Seite ist das unbefriedigend, weil wir ja alle Berechnungsschritte zählen
wollen, nicht nur die, in denen ein Vergleich ausgeführt wird; andererseits
ist es vielleicht ganz interessant, zu untersuchen, was man alles machen kann,
wenn man nur Vergleichsoperationen zählt.
Maximum in parallelel Vergleichsschritten
Lassen wir uns also auf Valiants Gedankenexperiment ein, nur Schritte zu zählen,
in denen wirklich Vergleichsoperationen durchgeführt werden. Wir nennen solche
Schritte parallele Vergleichsschritte. Alle anderen Operationen, also
Ergebnisse addieren, kopieren betrachten wir als kostenlos.
Theorem 3.3.2
Mit Prozessoren kann man in einem Array von Elementen in parallelen
Vergleichsschritten das Maximum bestimmen.
Dies ist also deutlich schneller als der traditionelle binärbaumartige Algorithmus,
der so vorgeht wie in der K.o.-Runde einer Weltmeisterschaft und
Schritte braucht.
Beweis. Verallgemeinern wir kurz und nehmen an, wir haben
Prozessoren und Elemente. Im Falle des Theorems wäre , da wir ja
Elemente haben. Wenn wir die Elemente in Blöcke
von je Elementen unterteilen und in jedem Block alle paarweisen
Vergleiche ausführen, dann sind das
Vergleichsoperationen. Für sind das Operationen, und wir können
sie alle parallel mit unseren Prozessoren ausführen. Wir haben also nach einem
Vergleichsschritt genug Information, um in jedem Block das Maximum zu bestimmen.
Achtung. Es braucht
dann immer noch Schritte, um pro Block das Maximum zu ermitteln.
Nur müssen wir hier nur die Ergebnisse bereits erfolgte Vergleichsoperationen
betrachten und keine weiteren Vergleiche tätigen. Daher zählen wir diese Schritte
nicht als Vergleichsschritte mit, sondern betrachten sie in diesem Zusammenhang
als kostenlos.
Mit einem Vergleichsschritt haben wir also pro Block das Maximum ermittelt.
Das globale Maximum aller Elemente muss nun eines der
verbleibenden Kandidaten, der Blockmaxima sein. Wir fahren rekursiv fort, nun mit .
Wie groß ist also die Menge der verbleibenden Kandidaten nach Vergleichsschritten?
Wir definieren
Dann verbleiben nach Schritten nur noch Kandidaten.
Wenn erreicht ist (z.B. n/k_i = 1 oder 2), dann beenden wir die Rekursion
und ermitteln das Maximum in verbleibenden Vergleichsschritten.
Um uns die Rechnung zu erleichtern, definieren wir
Per Induktion sieht man, das für alle gilt und außerdem
. Es verbleiben nach Schritten also
Kandidaten. Nach Schritten verbleibt noch
Kandidat und wir sind fertig (bzw. wir sind schon vor
fertig, weil die Zahl der Kandidaten schon zuvor erreicht haben wird).
Übungsaufgabe 3.3.1
Wir sind stillschweigend davon ausgegangen, dass man die Elemente
in Blöcke der Größe aufteilen kann. Das geht natürlich nur,
wenn durch teilbar ist (und selbst eine natürliche Zahl ist).
Passen Sie die Analyse so an, dass Sie diesen Teilbarkeitsschwierigkeiten Rechnung tragen.