6.2 Sortieren

Die Eingabe besteht aus einer Menge X von Items x aus einem geordneten Universum. Das Ziel ist es, diese Menge zu sortieren. Was heißt das im MPC-Kontext? Wir betrachten die Menge X als sortiert, wenn jeder Item x durch einen Item (i,x) ersetzt worden ist, wobei i=rank(x,X) ist. Dann hätten wir X in ein Array im Sinne von Übungsaufgabe 6.1.2 umgewandelt und somit in jedem vernünftigen Sinne sortiert. In Worten: am Ende des Algorithmus soll jedes Element seinen Rang kennen.

Unser Algorithmus ist eine MPC-Version von Quicksort. Die Idee ist es, nicht ein Pivot, sondern eine ganze Menge Y von Pivots zu wählen. Die Pivots Y zerteilen die Menge X in mehrere Teilintervalle oder Unterprobleme. Über einen Baum ähnlich wie in Theorem 6.1.2 senden wir dann alle Pivots zu einer zentralen Maschine, welche sie lokal sortiert (was als ein Schritt zählt) und als sortierte Menge wieder an alle Maschinen aussendet. Jede Maschine bestimmt dann (lokal, also in einem Schritt) für jedes ihrer Items, zu welchem Unterproblem es gehört. Nun lösen wir rekursiv jedes Unterproblem.

Eine Schwierigkeit besteht darin, dass wir die Maschinen irgendwie den Unterproblemen zuordnen müssen; also (1) bestimmen, an welchem Unterproblem j die Maschine i arbeiten soll und (2) erreichen müssen, dass alle Items, die zu Unterproblem j gehören, auf den zuständigen Maschinen landen. Problem (1) wird einen weiteren "Baum-Broadcast" erfordern; Problem (2) lösen wir mit Randomisierung.

MPC-Quicksort

Als erstes wollen wir eine Menge Y von S=Nϵ/2 zufällig Pivots wählen, und zwar in einem Schritt, also ohne Kommunikation. Dies geht näherungsweise und mit Randomisierung: wenn wir jeden Item x mit Wahrscheinlich p markieren, dann gilt für die Menge Y von markierten Items:

E[|Y|]=xXPr[x ist markiert]=pN .

Wir wollen E[|Y|]=S2 erreichen: die Menge Y der Pivots soll auf keinen Fall mehr als S Elemente enthalten, und der Faktor 1/2 dient als Sicherheitsabstand. Wir wählen also p=S2N.

Behauptung 6.2.1 Sei p=S2N. Wenn wir jeden Item mit Wahrscheinlichkeit p markieren, dann haben wir mit großer Wahrscheinlichkeit höchstens S Items markiert.

Warum die Behauptung gilt und was überhaupt "mit großer Wahrscheinlichkeit" heißt, das schauen wir uns weiter unten genauer an. Im Moment gehen wir einfach davon aus, dass |Y|S gilt. Die Pivots Y liegen über alle Maschinen verteilt. Wir wollen sie auf einer bestimmte Maschine, zum Beispiel Maschine 1, sammeln.

Übungsaufgabe 6.2.1 Zeigen Sie, wie man in 2λ Runden (merke: λ=logNlogS) erreichen kann, dass alle Items Y auf Maschine 1 liegen. Gehen Sie hierbei davon aus, dass jede Maschine anfangs noch mindestens S freien Speicherplatz hat.

Maschine 1 hält nun (neben ihren "normalen" Eingabe-Items) die Menge Y. Sie sortiert sie lokal, bestimmt somit für jedes yY seinen Rang j=rank(y,Y) und bildet den "indizierten" Item (j,y). Wir bezeichnen diese indizierte Menge {(rank(y,Y),y) | yY} als Y^. Dann sendet Maschine 1 die Menge Y^ an alle Maschinen; dies geht wiederum in 2λ Runden, analog zu Übungsaufgabe 6.2.1. Alle Maschinen kennen nun also die Pivots y1<y2<<yk in aufsteigender Reihenfolge (mit k:=|Y|). Dies unterteilt X in Teilintervalle / Unterprobleme. Formal: das Teilproblem Xj ( für 0jk) ist die Menge Xj:={xX | rank(x,Y)=j} . Jede Maschine bestimmt nun rank(x,Y) für jeden ihrer Items x und weiß nun, zu welchem Teilproblem x gehört; ganz konkret ersetzt sie x durch x,rank(x,Y). Dies geht lokal, also in einer Runde. Jede Maschine i kennt nun die Anzahl Ni,j ihrer Items, die zu Teilproblem j gehören. Wir wollen nun

Nj:=i=1PNi,j=|Xj| .

bestimmen, also die Größe des Teilproblems j.

Übungsaufgabe 6.2.2 Zeigen Sie, wie wir in 4λ Runden erreichen können, dass jede Maschine alle Zahlen N0,,Nk kennt.

Wir wollen nun die P Maschinen den k Unterproblemen zuteilen und dann Xj auf die zuständigen Maschinen aufteilen. Hierfür brauchen wir mindestens |Xj|/S Maschinen. Um etwas "Sicherheitsabstand" zu haben, setzen wir Pj:=2|Xj|S. Jede Maschine kennt die Zahlen Pj und weiß somit, welche Maschinen für Teilproblem j zuständig sind: mit tj:=P1++Pj1 sind das die Maschinen mit Index in

Ij:={tj+1,tj+2,,tj+Pj}

Beachten Sie, dass die Berechnung der Präfixsummen P1++Pj1 kein Präfixsummenproblem im MPC-Sinne darstellt, weil es lokal ausgeführt werden kann: jede Maschine kennt alle Zahlen P1,,Pk. Wir müssen nun alle Items, die zum Teilproblem j gehöhren, auf die Maschinen mit Index in Ij aufteilen. Damit dies in einer Runde gelingt, verwenden wir wiederum Randomisierung: Maschine i schickt jeden ihrer Items x,j an eine Maschine i, wobei sie i zufällig aus Ij wählt.

Wieviele Items empfängt eine Maschine i in dieser Runde? Die Maschinen in Menge Ij empfangen insgesamt |Xj| Items; da die Items zufällig veretilt werden, erhält Maschine i im Erwartungswert |Xj|Pj=S2 viele Items.

Behauptung 6.2.2 Mit hoher Wahrscheinlichkeit gibt es keine Maschine, die in diesem Schritt mehr als S Items empfängt.

Wenn bisher kein "Unfall" geschehen ist (also keines der schlimmen Ereignisse von kleiner Wahrscheinlichkeit eingetreten ist), dann hält nun jede Maschine eine Menge von Items, die alle zu einem Teilproblem gehören, und jede Maschine weiß auch, an welchem Teilproblem sie arbeiten soll und welche anderen mit ihr. Wir können nun also rekursiv auf jedem Teilproblem weiterarbeiten.

Laufzeitanalyse

Die gerade beschriebene Phase von MPC-Quicksort benötigt O(λ) Runden. Es bleibt zu untersuchen, wie viele Phasen der rekursive Aufruf erzeugt. Hierzu untersuchen wir, wie groß die Teilprobleme werden. Da wir X in k+1 Teilprobleme X0,,Xk unterteilen (mit k=|Y| die Anzahl der Pivots), sollte jedes Teilproblem "im Durchschnitt" Nk+1 Elemente enthalten. Die Anzahl k der Pivots ist wiederum im Erwartungswert k~:=S2, so dass wir Nk+1 wohl bei ungefährt 2NS=2N1ϵ/2 erwarten.

Warnung. Es ist nicht korrekt, Erwartungswerte zu dividieren, also (im Allgemeinen falsch!)E[N1+|Y|]=N1+E[|Y|] zu rechnen. Dennoch ist es legitim, quasi als Bierdeckelrechnung mal zu schauen, was dabei rauskommt, um zu sehen, ob man auf dem richtigen Weg ist.

Wir "erwarten" also, dass die Größe eines Teilproblems bei ungefähr 2N1ϵ/2 liegt. Nach dieser Rechnung würde in jedem rekursiven Level die Anzahl N der Elemente ungefähr durch k~=S2 geteilt. Nach logNlogk~=O(1/ϵ) Rekursionsleveln wären wir fertig; sogar noch etwas früher: sobald ein Teilproblem kleiner als Nϵ ist, können wir es auf einer Maschine sortieren.

Behauptung 6.2.3 Mit hoher Wahrscheinlichkeit gilt Nj4NS für jedes j.

Wahrscheinlichkeitsrechnung: Beweise der Behauptungen

Schneller sortieren in O(λ) Runden

Ich beschreibe nun einen schnelleren Sortieralgorithmus, der auch auf Quicksort beruht. Er stammt aus dem Paper Sorting, Searching, and Simulation in the MapReduce Framework von Michael T. Goodrich, Nodari Sitchinava und Qin Zhang.