Das Prinzip vom Teilen und Herrschen Parameter eines Divide&Conquer–Algorithmus Bei einem Ansatz mit Divide and Conquer wird das Gesamtproblem in kleinere Teilprobleme aufgeteilt, diese dann (in der Regel rekursiv) gelöst und danach die Gesamtlösung aus den Lösungen der Teilprobleme zusammengesetzt. Um zu einer effizienten Lösung zu kommen, sollte bei jeder Aufspaltung die Anzahl der neuen Teilprobleme konstant sein (im Standardfall zwei, allgemein a ∈ N+ ) und die Größe der neuen Teilprobleme um einen konstanten Faktor c < 1 abnehmen (z.B. Halbierung der Größe, allgemein c = 1b für ein b > 1 aus Q). Ist n die ursprüngliche Problemgröße und stellt man die Aufspaltungen der betrachteten Probleme bis zu den Rekursionsankern in einem Baum dar, dann hat dieser Baum eine Höhe h ≤ dlogb ne und jeder innere Knoten hat höchstens a Kinder. Damit hat man bereits eine Übersicht, wie viele Teilungsaufrufe für welche Problemgrößen erfolgen, aber um die Gesamtlaufzeit abschätzen zu können fehlt noch ein wesentlicher Aspekt, nämlich die Kosten f (n), um ein Problem der Größe n zuerst in a Teilprobleme der Größe n/b aufzuteilen und dann aus den a Teillösungen die Lösung des ursprünglichen Problems zu berechnen. Betrachten wir dazu die zwei bekannten Beispiele: 1. Beim Merge–Sort ist a = 2 und b = 2 (eigentlich reicht das nicht ganz, denn bei ungeradem n hat eine Teilliste die Länge 2n > n2 , aber man kann zeigen, dass solche Rundungseffekte vernachlässigbar sind). Das Aufteilen erfolgt in konstanter Zeit, das Mischen der zwei sortierten Teillisten in linearer Zeit, d.h. insgesamt f (n) ∈ Θ(n). 2. Bei der Binärsuche unterteilt man in zwei Bereiche, aber sucht entweder nur auf der linken oder nur auf der rechten Seite weiter, behandelt also ein Teilproblem halber Größe. Daraus folgt a = 1, b = 2 und f (n) ∈ Θ(1), denn das Aufteilen (also die Entscheidung für die richtige Seite) erfordert konstante Zeit und das Ergebnis des rekursiven Aufrufs muss nur nach oben weitergereicht werden. Die folgende Abbildung zeigt den Rekursionsbaum mit den Parametern a = 3 und b = 2 für eine Eingabe der Größe n = 8. Die Zahlen in den Knoten des Baums repräsentieren die Problemgrößen. 8 4 2 2 1 1 4 1 1 1 2 1 1 1 2 2 1 1 1 4 1 1 1 2 1 1 1 2 2 1 1 1 1 1 1 2 1 1 1 1 Sind die Parameter a, b und f (n) eines Divide&Conquer–Algorithmus bekannt, dann ergibt sich für die Gesamtlaufzeit (unter der vereinfachenden Annahme, dass n/b ganzzahlig ist) die Rekursion T (n) ≤ a · T (n/b) + f (n). Wünschenswert wäre eine direkte (nichtrekursive) Beschreibung der Laufzeit in Abhängigkeit von den drei Parametern. Der folgende Satz zeigt auf, wie man diese Rekursion in drei wichtigen Fällen 1 auflösen kann. Da diese drei Fälle die meisten realen Anwendungssituationen abdecken, hat der Satz dem ehrenvollen Namen ‘Mastertheorem’ bekommen. Wir werden sehen, dass sich die bekannten Laufzeiten T1 (n) ∈ Θ(n log n) für Merge–Sort und T2 (n) ∈ Θ(log n) für die Binärsuche unmittelbar aus den Satz ableiten lassen. Offen bleiben nur solche Rekursionen, in denen die Funktion f (n) ein sehr konfuses Verhalten aufweist. Das Mastertheorem Satz: Seien a ≥ 1 und b > 1 Konstanten, f (n) eine Funktion mit nichtnegativen Werten und gelte die Rekursion T (n) = a · T (n/b) + f (n), wobei n/b für bn/bc oder dn/be stehen kann, und sei T(1) eine positive Konstante. Dann gelten für T (n) die folgenden asymptotischen Schranken: 1. Ist f (n) ∈ O nlogb a−ε für ein ε > 0, dann ist T (n) ∈ Θ nlogb a 2. Ist f (n) ∈ Θ nlogb a , dann ist T (n) ∈ Θ nlogb a · log n 3. Ist f (n) ∈ Ω nlogb a+ε für ein ε > 0 sowie a · f (n/b) ≤ d · f (n) für eine Konstante 0 < d < 1 und geügend große n, dann ist T (n) ∈ Θ ( f (n)) Beweisidee: Der vollständige Beweis dieses Satzes ist sehr umfangreich allein durch die Fallunterscheidung (drei obere und drei untere Schranken) und darüber hinaus technisch aufwändig in den Details (durch das Auf- und Abrunden bei der Division). Deshalb wollen wir hier nur die Grundidee vorstellen. Zur Vereinfachung setzen wir a und b ganzzahlig voraus und nehmen an, dass n eine ganzzahlige Potenz von b ist (oder anders gesagt, dass logb n eine natürliche Zahl ist). In diesem Fall kann man die Rekursion durch wiederholte Anwendung vollständig ‘ausrollen’ bis man bei der Verankerung für n = 1 angekommen ist: T (n) = f (n) + a · T (n/b) = f (n) + a · f (n/b) + a2 T (n/b2 ) = . . . = f (n) + a · f (n/b) + a2 f (n/b2 ) + a3 f (n/b3 ) + . . . + a(logb n)−1 · f (b) + alogb n · T (1) (logb n)−1 = ∑ a j · f (n/b j ) +Θ nlogb a j=0 | {z =g(n) } Da man den zweiten Summanden Θ nlogb a in allen drei Fällen bereits gut in die Behauptung einbinden kann, und in dieser im Fall 1 bereits die untere Schranke begründet, kommt es nur noch darauf an, Begründungen für den ersten Summanden (den wir kurz g(n) genannt haben) zu finden. Es bleibt also Folgendes zu zeigen: • In Fall 1 ist g(n) ∈ O nlogb a ; • In Fall 2 ist g(n) ∈ Θ nlogb a · log n ; • In Fall 3 ist g(n) ∈ Θ( f (n)). 2 Das werden wir nicht im Detail machen. Der Trick besteht darin, zu Ungleichungen überzugehen, in logb a +− ε denen man f (n/b j ) je nach Fall durch einen Ausdruck der Form C · bnj substituieren kann. Mit Hilfe der Summenformel für geometrische Reihen kommt man dann letztlich zu den Behauptungen. Zurück zu unseren zwei Standardbeispielen. Wir können nun leicht nachrechnen, dass für Merge-Sort der zweite Fall vorliegt: Wegen logb a = log2 2 = 1 und f (n) ∈ Θ(n) = Θ(n1 )) folgt T1 (n) ∈ Θ(n log n). Für die Binärsuche liegt mit logb a = log2 1 = 0 und f (n) ∈ Θ(1) = Θ(n0 )) auch der zweite Fall vor. Somit erhalten wir T1 (n) ∈ Θ(n0 log n) = Θ(log n). Unser nächster Algorithmus ist ein Anwendungsbeispiel für einen anderen Fall des Mastertheorems. Schnelle Multiplikation von n-stelligen Zahlen Multipliziert man zwei n-stellige Zahlen mit der der Schulmethode (wie nebenstehend an einem Beispiel demonstriert), so sind dazu n2 elementare Multiplikationen und n2 elementare Additionen notwendig. 2 3 6 1 4 5 1 8 3 7 1 2 0 4 2 5 · 2 5 3 7 5 5 2 5 Es stellt sich die Frage, ob (und wenn ja, dann wie) man dieses Problem auch mit kleinerem Aufwand lösen kann. Der Einwand, dass diese Frage uninteressant ist, weil ein moderner Prozessor mit 64–Bit– Architektur auch zwei jeweils 9-stellige Dezimalzahlen in einem Takt multiplizieren kann, mag für diese Standardanwendungen berechtigt sein, verliert aber sein Gewicht, wenn man an kryptographische Anwendungen mit 100 und mehr Dezimalstellen denkt. In diesem Fall würde man die großen Zahlen in kleinere Einheiten mit z.B. 32 Bit zerlegen und den Begriff der elementaren Operation auf diese Einheiten beziehen. Zunächst ist klar, dass die Addition von zwei n-stelligen Zahlen mit O(n) elementaren Additionen möglich ist (durch die O-Notation wird auch die Addition der Überträge berücksichtigt). Für die Multiplikation wollen wir mit einem rekursiven Divide&Conquer–Ansatz arbeiten und gehen zunächst davon aus, dass n eine gerade Zahl ist, und dass wir bereits über eine Methode zur Multplikation von zwei (n/2)–stelligen Zahlen verfügen. Sei d die Basis der Darstellung (also z.B. 2, 10 oder 232 ) und x, y die beiden n-stelligen Zahlen. Wir betrachten die (n/2)–stelligen Zahlen x0 , x1 , y0 , y1 der Zerlegung von x und y, d.h. x = x1 · d n/2 + x0 und y = y1 · d n/2 + y0 . Daraus folgt z = x · y = x1 · d n/2 + x0 · y1 · d n/2 + y0 = x1 · y1 ·d n + (x1 · y0 + x0 · y1 ) ·d n/2 + x0 · y0 | {z } | {z } | {z } =z2 =z1 =z0 Da die Multiplikation mit den Potenzen von d höchstens lineeare Kosten verursacht (einfach Nullen anhängen), kann man die Laufzeit T (n) durch vier Multiplikationen von (n/2)–stelligen Zahlen und drei Additionen von Zahlen mit höchstens 2n Stellen abschätzen, d.h. T (n) ≤ 4 · T (n/2) + f (n) mit f (n) ∈ O(n). Wir können das Mastertheorem mit a = 4 und b = 2 anwenden. Wenn wir ε = 1 setzen, ist f (n) ∈ O(nlogb a−ε ). Das ist also der erste Fall des Mastertheorems und ergibt T (n) ∈ O(nlogb a ) = O(n2 ), 3 womit man die Schulmethode offensichtlich noch nicht verbessert hat. Der entscheidende Trick besteht darin, eine der vier Multiplikationen einzusparen auf Kosten von zusätzlichen, aber billigeren Additionen bzw. Subtraktionen. Wir setzen x0 = x1 + x0 und y0 = y1 + y0 und berechnen rekursiv z0 = x0 · y0 und z2 = x1 · y1 (wie vorher) sowie z0 = x0 · y0 . Folgendes kann nun leicht nachgerechnet werden: z = x · y = x1 · d n/2 + x0 · y1 · d n/2 + y0 = z2 · d n + (z0 − z2 − z0 ) · d n/2 + z0 . Daraus ergibt sich T (n) ≤ 3 · T (n/2) + f (n) mit f (n) ∈ O(n) als neue Laufzeitrekursion. Wegen log2 3 = 1.58 . . . kann man mit ε = 0.1 die obere Schranke f (n) ∈ O(nlogb a−ε ) ableiten. Somit können wir wieder den ersten Fall des Mastertheorems anwenden und erhalten T (n) ∈ O(nlog2 3 ) und damit auch T (n) ∈ O(n1.59 ). Der hier beschriebene Trick war nur ein erster Schritt zu einem schnellen Multiplikationsverfahren. Durch Nutzung der schnellen diskreten Fourier–Transformation und dem Einsatz von Methoden aus der Zahlentheorie gelang es Schönhage und Strassen, die Laufzeit noch einmal entscheidend zu verbessern auf O(n log n log log n). Mit ähnlichen Ideen wurden auch schnelle Verfahren zur Multiplikation von Matrizen entwickelt: Der erste Schritt dazu war die Reduktion der Anzahl von elementaren Multiplikationen bei der Multiplikation von zwei 2 × 2–Matrizen von 8 auf 7. 4