Analyse von Algorithmen (WS 2009/2010) 1. Grundlegende Begriffe und einführende Beispiele Eine Vorlesung wie diese sollte eigentlich mit einer „Definition“ des Begriffs Algorithmus beginnen, was jedoch alles andere als einfach, wenn nicht unmöglich ist. Intuitiv versteht man unter einem Algorithmus jedenfalls eine Folge von Anweisungen zur Lösung eines Problems, wobei in jedem Augenblick klar feststehen muss, welche Operation als nächstes auszuführen ist, welche Operanden daran beteiligt sind und was als nächster Schritt kommt. Entscheidend ist auch, dass der Algorithmus auf jeden Fall nach endlich vielen Schritten (wenngleich deren Anzahl oft nicht vorhersagbar ist) terminiert, wonach dann das Ergebnis der Berechnung feststeht. In erster Näherung können wir einen Algorithmus mit einem Computerprogramm gleichsetzen, wobei dies nachstehend noch präzisiert wird. Zunächst aber die Frage: Was wird denn nun eigentlich berechnet? In allgemeinster Form sind sowohl die Eingabegrößen eines Problems, als auch dann die möglichen Ausgaben als Wörter, d.h. endlichen Zeichenketten, über einem endlichen Alphabet A gegeben. Bezeichnet man die Menge dieser Wörter über A (einschließlich des leeren Worts ε) wie üblich mit A*, so gibt es stets eine sog. Gödelisierung γ , d.h. eine injektive und algorithmisch berechenbare Funktion γ : A*→ N (N bezeichnet dabei hier und im folgenden die Menge der natürlichen Zahlen inklusive 0), die jedem solchen Wort w ∈ A* in umkehrbar eindeutiger Weise eine natürliche Zahl γ ( n ) , seine sog. Gödelnummer, zuordnet. Auch die Frage, ob ein n ∈ N eine Gödelnummer eines Wortes w ∈ A* ist und wie dieses w dann gegebenenfalls berechnet wird, kann für eine Gödelisierung algorithmisch gelöst werden. Es bedeutet somit keine wesentliche Einschränkung der Allgemeinheit, wenn wir im Folgenden davon ausgehen, dass die Funktionswerte einer s-stelligen Funktion f : N s → N berechnet werden. Funktionen dieser Bauart, deren sämtliche Funktionswerte mit Hilfe eines Algorithmus (in obigen intuitiven Sinne), berechnet werden können, werden als (algorithmisch) berechenbar bezeichnet. Hierbei werden wir auch oft den Fall zulassen, dass f nicht überall auf N s definiert ist, d.h. dass sie nicht, wie man sagt, eine totale, sondern nur eine sog. partielle Funktion ist. Dort wo f nicht definiert ist, soll dabei der der Berechnung zugrunde liegende Algorithmus nicht stoppen. Nach dem sog. 2. Cantorschen Diagonalverfahren gibt es nun aber insgesamt überabzählbar viele Funktionen f : N s → N , andererseits jedoch aber sicher nur abzählbar viele Algorithmen formuliert in irgendeiner festgewählten Sprache. Um zu einer solchen Abzählung zu gelangen, betrachte man z.B. einfach alle zunächst der Länge nach und innerhalb gleicher Länge dann lexikographisch geordneten Wörter über dem verwendeten Alphabet und streiche davon diejenigen, die keinen Algorithmus darstellen. So gesehen ist es also eine Ausnahme, dass eine Funktion f : N s → N in obigem Sinne berechenbar ist! (Gottseidank verhält es sich aber für die in der Praxis betrachteten Funktionen gerade umgekehrt!) 2 Um nun die Berechenbarkeit einer Funktion f : N s → N mittels eines Computerprogramms zu präzisieren hat A. Turing 1936 seine berühmten und heute nach ihm benannten Turingmaschinen (TM) eingeführt. Eine Turingmaschine ist bestimmt durch • Eine endliche Menge Z von sog. Zuständen. Darunter sind zwei Zustände ausgezeichnet, der Anfangszustand z 0 ∈ Z und ein Endzustand H, bei dessen Erreichen die Maschine sofort anhält. • Ein Eingabealphabet A. • Ein Bandalphabet B. Dieses enthält ein spezielles Bandvorbelegungszeichen, das im folgenden mit # bezeichnet wird und es gilt A ⊆ B \{#} • Eine sog. Überführungsfunktion δ : Z × B → Z × B × {L, R} . In der Praxis ist sie oft zunächst nur als partielle Funktion gegeben und wird durch die Festsetzung δ (z,a) = (H,a,R) für nicht definierte δ (a,z) zu einer totalen Funktion vervollständigt. (Die Tabelle der Wert von δ wird oft auch die Maschinentafel oder das Programm der TM genannt.) Eine Turingmaschine kann jeden der endlichen Zustände in Z annehmen. Als Ein- und Ausgabegerät steht ein beidseitig unendliches linear in Felder unterteiltes Band zur Verfügung, wobei mittels eines kombinierten Schreib-Lese-Kopfs stets nur ein Feld gelesen bzw. beschrieben werden kann. Die gelesenen und geschriebenen Symbole entstammen dabei dem Bandalphabet B, wobei man im Prinzip mit nur zwei Symbolen, ⎮und # (letzteres für das Bandvorbelegungs- oder Leerzeichen) auskommen könnte. Eine natürliche Zahl n kann dann z.B. durch einen Block von n+1 Strichen codiert werden (Unäre Codierung). Am Beginn ist die TM im Zustand z 0 und der Schreib-Lese-Kopf befindet sich auf dem ersten Zeichen des auf dem Band befindlichen Eingabewortes geschrieben in dem Eingabealphabet A. In der Folge werden jeweils in Abhängigkeit von dem Zustand z, in dem sich die Maschine befindet und von dem gelesenen Bandsymbol a ∈ B die folgenden Aktionen durchgeführt. Ist δ(z, a ) = (z1 , a 1 , b1 ) , so wird die Maschine in den Zustand z1 übergeführt, das Symbol a 1 auf das Band geschrieben und die Bewegung b1 des SchreibLese-Kopfes ausgeführt. Dabei bedeuten b1 = L eine Bewegung um ein Feld nach links, und b1 = R eine Bewegung um ein Feld nach rechts. Wir wollen nun annehmen, dass zu Beginn der Berechnung eines Funktionswerts f ( x 1 ,..., x s ) von f : N s → N die Argumente x 1 ,..., x s sich (z.B. in unärer Codierung) durch jeweils ein Leerzeichen getrennt auf dem Band stehen und der Schreib-Lese-Kopf auf dem ersten Zeichen davon. Das Ergebnis der Berechnung soll dann jene natürliche Zahl sein, welche bei der gewählten Codierung dem Block entspricht, in dem die Maschine nach dem Abarbeiten des Programms zum Halten kommt. Wenn die TM nicht stoppt, so ist f für dieses s-Tupel ( x 1 , x 2 ,..., x s ) undefiniert. Sind alle Funktionswerte einer (i.allg. partiellen) Funktion f auf seinem Definitionsbereich für eine geeignete TM in dieser Weise berechenbar, so wollen wir f Turing-berechenbar nennen. 3 Die bisher besprochenen Turingmaschinen können nur jeweils ein spezielles Problem lösen. Es gibt aber auch sog. Universelle Turingmaschinen, welche alle Turingmaschinen zur Lösung von speziellen Problemen emulieren können. Hierzu müssen auf dem Eingabeband dann nicht nur die Daten, sondern auch die Maschinentafel (in codierter Form) der betrachteten speziellen Turingmaschine vorliegen. Allerdings gibt es, wie man zeigen kann, keinen Algorithmus um festzustellen, ob eine solche Universelle Turingmaschinen für einen beliebigen Input immer stoppt, d.h. das sog. Halteproblem ist nicht entscheidbar. Es ist dies also ein Beispiel für ein sinnvolles Problem, für dessen Lösung es keinen Algorithmus gibt. Ergänzend sei noch erwähnt, dass Turingmaschinen nicht nur bei der Berechnung von Funktionen, sondern auch in der Spracherkennung eine sehr wichtige Rolle spielen. Hierzu wird eine Teilmenge E von Z als Menge von sog. Endzuständen ausgezeichnet, und ein Wort w über dem Eingabealphabet A wird von der TM erkannt bzw. akzeptiert, wenn sie für diese Eingabe in einem Endzustand zu halten kommt. Sprachen über A, d.h. Teilmengen von A*, die genau die von einer gewissen Turingmaschine erkannten Wörter beinhalten, werden rekursiv aufzählbar oder semientscheidbar genannt. In der sog. Chomsky-Hierarchie sind sie die sog. Sprachen vom Typ 0, d.h. der allgemeinste Typ von Sprachen, welche durch eine sog. Grammatik definiert werden können (→ Theorie der formalen Sprachen). Ein anderer Zugang zur Berechenbarkeit von arithmetischen Funktionen benützt das Konzept der Rekursivität. Man geht dazu aus von den folgenden Grundfunktionen, welche auf jeden Fall als berechenbar angesehen werden: 1. Die Nachfolgerfunktion s: N → N mit s(x)=x+1. 2. Die konstanten Funktionen C nk : N n → N ( k ∈ N ) mit C nk ( x 1 ,..., x n ) = k für alle x 1 ,..., x n ∈ N . 3. Die Projektionen Pkn : N n → N ( 1 ≤ k ≤ n ) mit Pkn ( x 1 ,..., x n ) = x k für alle x 1 ,..., x n ∈ N . Um daraus weitere berechenbare Funktionen zu gewinnen, bedient man sich folgender Konstruktionsprinzipien zur Erzeugung arithmetischer Funktionen aus gegebenen. 1. Komposition: Sind f : N m → N und g 1 ,..., g m : N n → N arithmetische Funktionen, so auch die Funktion h : N n → N definiert durch h ( x 1 ,..., x n ) = f (g 1 ( x 1 ,..., x n ),..., g m ( x 1 ,..., x n )) für alle x 1 ,..., x n ∈ N . 2. Primitive Rekursion Für beliebige arithmetische Funktionen g : N n → N und h : N n +2 → N wird durch die Rekursion f ( x 1 ,..., x n ,0) = g ( x 1 ,..., x n ) 4 f ( x 1 ,..., x n , y + 1) = h ( x 1 ,..., x n , y, f ( x 1 ,..., x n , y)) ( x 1 ,..., x n , y ∈ N ) in eindeutiger Weise eine weitere arithmetische Funktion f : N n +1 → N definiert. 3. Minimalisierung (Anwendung des µ-Operators) Aus g : N n +1 → N kann durch h ( x 1 ,..., x n ) = µy [g ( x 1 ,..., x n , y) = 0] ( x 1 ,..., x n ∈ N ) (für die rechte Seite lies: „kleinstes y, sodass g ( x 1 ,..., x n , y) = 0 “) eine weitere arithmetische Funktion h : N n → N gewonnen werden, wobei man allerdings noch den Fall betrachten muss, dass es überhaupt kein y mit g ( x 1 ,..., x n , y) = 0 gibt oder dass für das kleinste derartige y nicht alle Funktionswerte g ( x 1 ,..., x n , u ) mit 0 ≤ u < y auch wirklich definiert waren. In diesem Fall lässt man dann h ( x 1 ,..., x n ) undefiniert. Funktionen, welche aus den Grundfunktionen durch (ev. wiederholte) Anwendung der Komposition und primitiven Rekursion gewonnen werden können, heißen primitiv rekursiv. Lässt man darüber hinaus auch noch die Anwendung des µ-Operators zu, so heißen die so erhaltenen Funktionen µ-rekursiv, welche jedoch im Gegensatz zu primitiv rekursiven Funktionen dann i.allg. nicht mehr total sind! Lange Zeit war man der Meinung, mit dem Begriff der primitiv rekursiven Funktion bereits den allgemeinsten Rekursionsbegriff gefunden zu haben, bis dann 1928 Ackermann mit seiner berühmten und heute nach ihm benannten Funktion A: N 2 → N , welche rekursiv durch A(0,y)=y+1, A(x+1,0)=A(x,1) A(x+1,y+1)=A(x,A(x+1,y)) für alle x , y ∈ N definiert ist, ein Gegenbeispiel fand. Diese Funktion wächst, wie man zeigen kann, so rasch, dass es zu jeder primitiv rekursiven Funktion g : N n → N stets ein c ∈ N gibt, sodass g ( x 1 , x 2 ,..., x n ) < A (c, x 1 + x 2 + ... + x n ) für alle x 1 ,..., x n ∈ N gilt. Wäre aber A(x,y) primitiv rekursiv, so auch g(x):=A(x,x) (=A( P12 ( x , y), P12 ( x , y)) , woraus jedoch der Widerspruch g(c)=A(c,c)<A(c,c) folgen würde! Andererseits kann man zeigen, dass die Ackermann-Funktion µ-rekursiv ist, d.h. das Konzept der µ-Rekursivität ist echt umfassender. Man kann weiter zeigen, dass überhaupt jede rekursiv „in sinnvoller Weise“ definierte Funktion (als Beispiel nehme man die rekursive Definition der Ackermann-Funktion) auch bereits µ-rekursiv ist, d.h. man hat mit µ-rekursiven Funktionen bereits das allgemeinste Konzept von rekursiven Funktionen. 5 Ferner kann man zeigen, dass die rekursiven Funktionen mit den Turing-berechenbaren Funktionen übereinstimmen, d.h. es gilt für arithmetische Funktionen µ-rekursiv ⇔ (allgemein) rekursiv ⇔ Turing-berechenbar Dies veranlasste Church 1936 zu folgender weitergehenden Churchsche These: Jede intuitiv berechenbare Funktion ist rekursiv bzw. Turingberechenbar. Diese heute allgemein anerkannte Hypothese kann man naturgemäß nicht beweisen (da der Begriff „intuitiv berechenbar“ nicht exakt definiert ist), wohl aber könnte man sie durch ein Gegenbeispiel widerlegen, so es eines gibt. Obwohl also der Begriff der primitiven Rekursivität, wie wir gesehen haben, echt spezieller ist als die (allgemeine) Rekursivität, haben doch sehr viele für die Praxis wichtige arithmetische Funktionen die Eigenschaft primitiv rekursiv zu sein. Wir zählen nachfolgend einige auf. (Die Aufzählung erfolgt dabei in einer Weise, dass stets nur Funktionen verwendet werden, welche bereits vorher definiert wurden. Darüber hinaus werden nur die Grundfunktionen, im besonderen die Nachfolgerfunktion s(x)=x+1, verwendet.) Vorgängerfunktion: ⎧ Pr ed(0) = 0 Pr ed ( x ) = ⎨ ⎩Pr ed (s( x )) = x Addition: x+0= x ⎧ x+y=⎨ ⎩ x + s( y) = s( x + y) Produkt: x *0 = 0 ⎧ x*y = ⎨ ⎩ x * s( y) = x + x * y Potenzen: ⎧ x0 = 1 x y = ⎨ s( y) = x *xy) ⎩x Fakultät: 0!= 1 ⎧ x!= ⎨ ⎩s( x )!= s( x ) * x! Modifizierte Differenz: x −& 0 = x ⎧ x −& y = ⎨ ⎩x −& s( y) = Pr ed( x −& y) Signum-Funktion: 6 ⎧ sg (0) = 0 sg(x)= ⎨ ⎩sg (s( x )) = 1 Konträre Signum-Funktion: sg( x ) = 1 −& sg( x ) Absolutdifferenz: x − y = ( x −& y) + ( y −& x ) Minimum (zweier Argumente): min( x, y) = x −& ( x −& y) Maximum (zweier Argumente): max(x, y) = y + ( x −& y) Etwas komplizierter sind die entsprechenden Definitionen von Rest rem(x,y) und Quotient quot(x,y) bei einer (Ganzzahl-) Division von x durch y. Die nachfolgende Definition zeigt eigentlich, dass r(x,y) := rem(y,x) bzw. q(x,y) := quot(y,x) primitiv rekursiv sind, aber wegen rem(x,y)= r (P22 ( x, y), P12 ( x , y)) (und analog für quot(x,y)) gilt dies dann auch für rem(x,y) und quot(x,y). (Allgemein braucht also die Komponente für den Nachweis der primitiven Rekursivität nicht unbedingt die letzte sein.) Rest (bei der Division von x durch y): rem(0, y) = 0 ⎧ rem( x , y) = ⎨ ⎩rem(s( x ), y) = sg ( y − s(rem( x , y)) ) * s(rem( x , y)) Quotient (bei der Division von x durch y) quot (0, y) = 0 ⎧ quot ( x , y) = ⎨ ⎩quot (s( x ), y) = quot ( x , y) + sg (rem(s( x ), y)) Anzahl der Teiler von x x τ( x ) = ∑ sg (rem( x , y)) y =0 Im Folgenden betrachten wir auch Prädikate P( x 1 , x 2 ,..., x n ) , welche für jede Belegung ( x 1 ,..., x n ) ∈ N n den Wahrheitswert „wahr“ oder „falsch“ ergeben. Wir nennen dabei P( x 1 , x 2 ,..., x n ) entscheidbar, wenn ihre charakteristische Funktion ⎧ 1, falls P(x 1 ,..., x n ) wahr χ P ( x 1 ,.., x n ) = ⎨ ⎩0, falls P(x 1 ,..., x n ) falsch berechenbar ist. Ist χ P ( x 1 ,..., x n ) wenigstens für den ersten Fall χ P ( x 1 ,..., x n ) =1 berechenbar, so nennen wir P( x 1 , x 2 ,..., x n ) auch semi-entscheidbar. Ferner heißt 7 P( x 1 , x 2 ,..., x n ) rekursiv bzw. primitiv rekursiv, falls dies für seine charakteristische Funktion gilt. Diese Sprechweisen gelten auch für die n-stellige Relation R P = χ −1 ({ 1 }) , auf N n , welche dem Prädikat ordnet ist. P( x 1 , x 2 ,..., x n ) in umkehrbar eindeutiger Weise zuge- Nachfolgend werden einige primitiv rekursive Prädikate mit Hilfe ihrer charakteristischen Funktionen eingeführt. Prädikat Bedeutung Charakteristische Funktion xy x teilt y sg (rem(y,x)) x<y x kleiner als y sg ( y −& x ) x=y x ist gleich y sg ( x − y ) Pr(x) x ist Primzahl sg ( τ( x ) − 2 ) Vom Standpunkt der Programmierung ist noch festzuhalten, dass man bei der Programmierung von primitiv rekursiven Funktionen bzw. Prädikaten stets mit sog. LOOP-Programmen auskommt, welche außer Wertzuweisungen nur Schleifen (engl. Loops) mit einer vorher genau festgelegten Anzahl von Schleifendurchläufen zulassen. Im Gegensatz dazu können allgemein rekursive Funktionen nur mit sog. WHILEProgrammen berechnet werden, wo allgemeine Schleifen zugelassen sind, die bei Nichterfüllung einer bestimmten Bedingung verlassen werden. (Insbesondere ist also die Anzahl der Schleifendurchläufe i. allg. vorher nicht bekannt.) Prinzipiell ist noch zu sagen, dass es zu jedem rekursiven Programm immer auch ein iteratives Gegenstück gibt, in der die rekursiven Aufrufe durch Iterationen, also Schleifen, aufgelöst wurden. Besonders einfach ist dabei die iterative Auflösung, wenn der rekursive Aufruf nur einmal vorkommt und zwar am Ende des Programms. Man spricht dann auch von einer Tail-Recursion oder endständigen Rekursion. Die iterative Lösung hat den Vorteil, dass sie oft schneller und weniger speicherplatzintensiv ist. (Es müssen insbesondere keine Rücksprungadressen auf dem Runtime-Stack gespeichert werden.) Was die Komplexität, also den Aufwand bei der Lösung eines Problems am Computer betrifft, so werden wir im folgenden fast ausschließlich die sog. Zeitkomplexität betrachten, welche die Anzahl der elementaren Schritte zur Lösung eines Problems misst. Daneben gibt es noch die Speicherplatzkomplexität und die Hardwarekomplexität, welche sich auf den Speicherplatzbedarf bzw. die verwendete Hardware beziehen. Bei der Messung der (Zeit-)Komplexität kann man nur in den seltensten Fällen eine genaue Anzahl der Rechenoperationen T(n) in Abhängigkeit von der sog. Größe n des Problems angeben. (Für eine zu sortierende Liste wäre n z.B. die Anzahl der Listenelemente.) Sehr oft muss man sich mit gröberen Abschätzungen begnügen, z.B. O(T(n)), unter Verwendung der sog. Landausymbole. Da diese somit bei der Analyse von 8 Algorithmen eine große Rolle spielen, nachfolgend einige allgemeine Bemerkungen zu diesem Thema. Wir betrachten dazu ausschließlich reelle Funktionen f(n), die auf der Menge N der natürlichen Zahlen definiert sind. Dann ist O(g(n )) = {f (n ) es gibt ein C > 0 und ein n 0 ∈ N , sodass f (n ) ≤ C g (n ) für alle n ≥ n 0 } die Menge aller derjenigen Funktionen f(n), von denen man sagt, dass sie höchstens so rasch wachsen wie g(n). Statt f(n) ∈ O(g(n)) hat sich dabei die etwas ungenaue Bezeichnungsweise f(n)=O(g(n)) eingebürgert und das gilt in gleicher Weise auch für die noch einzuführenden Symbole. Aus analytischer Sicht gilt ferner f(n)=O(g(n)) ⇔ lim sup n →∞ f (n ) g(n ) <∞ Analog dazu ist Ω(g (n )) = {f (n ) es gibt ein C > 0 und ein n 0 ∈ N , sodass f (n ) ≥ C g (n ) für alle n ≥ n 0 } die Menge aller derjenigen Funktionen f(n), von denen man sagt, dass sie mindestens so rasch wachsen wie g(n). Aus analytischer Sicht gilt diesmal f(n)= Ω (g(n)) ⇔ lim inf n →∞ f (n ) g(n ) >0 Sehr wichtig ist auch die Menge Θ(g(n )) := O(g (n )) ∩ Ω(g (n )) und von ihren Elementen, also den Funktionen f(n), für die sowohl f(n)=O(g(n)) als auch f(n)= Ω(g (n )) gilt (wofür man wieder f(n)= Θ (g(n)) schreibt),sagt man, dass sie gleich rasch wachsen wie g(n). Es gilt somit Θ(g(n )) = {f (n ) ∃ C1 , C 2 > 0 und ein n 0 ∈ N , sodass C1 g(n ) ≤ f (n ) ≤C 2 g(n ) ∀ n ≥ n 0 } In der Praxis macht man dabei oft Gebrauch von lim n →∞ f (n ) g(n ) = C für ein C > 0 ⇒ f (n ) = Θ(g(n )) , wobei diese Bedingung jedoch nur hinreichend, aber nicht notwendig ist. Besonders wichtig ist dabei noch der Spezialfall C=1. In diesem Fall schreibt man f(n)~g(n) und nennt f(n) und g(n) asymptotisch gleich. Zwei weitere Wachstumseigenschaften sind wie folgt definiert: f (n ) = o(g(n )) :⇔ lim n →∞ f (n ) g(n ) =0 (f(n) „wächst langsamer“ als g(n)) 9 f (n ) = ω(g(n )) :⇔ f (n ) g(n ) →∞ (f(n) „wächst schneller“ als g(n)) Statt f (n ) ~ g(n ) kann man damit auch f(n)=(1+o(1))g(n) schreiben. Die asymptotische Gleichheit von f(n) und g(n) ist insbesondere auch gleichwertig damit, dass der sog. relative Fehler f (n ) − g (n ) g(n ) (aber i.allg. nicht der absolute Fehler f (n ) − g(n ) !), den man bei der Ersetzung von f(n) durch g(n) begeht, für n → ∞ gegen 0 strebt. Komplexitätsabschätzungen für den Rechenaufwand T(n) werden nun oft in der Form T(n)=O(g(n)) (g(n) ist dann bis auf eine multiplikative Konstante C der sog. Worst Case) oder in der Form T(n)= Θ (g(n)) (entspricht dem sog. Average Case) angegeben. Gelegentlich kann man auch beweisen, dass für Algorithmen zur Lösung eines bestimmten Problems ein gewisser Mindestaufwand auf jeden Fall erforderlich ist, was man dann durch eine Beziehung der Form T(n)= Ω(g(n )) ausdrückt. Bei vielen solchen Wachstumsabschätzungen von Algorithmen eine große Rolle spielt das sog. Master-Theorem. Eine für unsere Zwecke ausreichend allgemeine Formulierung lautet: Satz 1.1: Sei T: R → R eine reelle Funktion, die für x ≤ 1 gleich 0 ist und ansonsten der Rekursion T( x ) = aT( x / b) + f ( x ) für gewisse reelle Konstanten a,b mit a ≥ 1, b > 1 und eine reelle Funktion f mit f(x)= Θ( x k ) genügt. (Hier sind in der Formulierung der Rekursion auch noch „marginale Abweichungen“ von x / b nach unten oder oben, wie z.B. ⎣x / b ⎦ und/oder ⎡x / b ⎤ erlaubt, ohne dass sich an der nachfolgenden Aussage etwas ändert.) Es ist dann ⎧ Θ( x k ), falls a < b k ⎪ T( x ) = ⎨ Θ( x k log b x ), falls a = b k ⎪ Θ( x log b a ), falls a > b k ⎩ (Im Fall a = b k ist wegen log b x = Θ(log c x ) auch irgendeine andere Basis c zulässig.) Dieses Master-Theorem kommt besonders häufig bei Komplexitätsabschätzungen von sog. Divide and Conquer Algorithmen zum Einsatz. Darunter versteht man Algorithmen, bei denen (in Anlehnung an Caesar’s Wahlspruch „Divide et impera!“) ein Problem in effizienter Weise in mehrere Teilprobleme zerlegt wird, sodass aus deren Lösung sich dann die Lösung des ursprünglichen Problems ohne großen Aufwand rekonstruieren lässt. 10 Als ein klassisches Beispiel für ein Divide and Conquer Verfahren sei hier MERGESORT angeführt, das zur Sortierung von Listen verwendet wird. Wir betrachten hier den Fall, dass zu Beginn die Liste L in dem Feld [L[1],L[2],...,L[n]] abgelegt ist und diese etwa aufsteigend sortiert werden soll. Die einfache Grundidee besteht nun darin, dass man die Liste L in etwa gleich große Listen A und B zerlegt, diese kleineren Listen nach der gleichen Methode rekursiv sortiert und schließlich aus den beiden sortierten Listen A und B in einfacher Weise eine sortierte Liste von A macht. Die Details dazu können aus dem nachfolgenden Derive-Programm ersehen werden: Für den Aufwand bei MERGESORT, gemessen an der Anzahl C n der durchzuführenden Vergleiche, wobei n die Anzahl der Listenelemente der zu sortierenden Liste L ist, gilt dann die rekursive Beziehung: C n = C ⎡n / 2 ⎤ + C ⎣n / 2 ⎦ + n für n > 1 und C1 = 0 . Hierbei ist C ⎡n / 2 ⎤ bzw. C ⎣n / 2 ⎦ der Aufwand für das Sortieren der Teillisten A bzw. B und n der Aufwand für das Zusammenfügen dieser sortierten Teillisten zu einer einzigen sortierten Liste. (Zu diesem abschließenden „Einsortieren“ müssen nach dem „Reissverschlussprinzip“ nur jeweils die beiden ersten Elemente der zwei sortierten Teillisten miteinander verglichen werden, um das einzusortierende Element zu bestimmen, welches dann natürlich aus der entsprechenden Teilliste sofort entfernt wird. Dies wird solange durchgeführt, bis eine der beiden Teillisten leer ist – also maximal n mal, wonach ev. noch übrigbleibende Elemente der zweiten Teilliste einfach hinten angehängt werden.) Für diese Rekursion gibt es sogar eine exakte Lösung, nämlich C n = n ⎣log 2 n ⎦ + 2n − 2 ⎣log 2 n ⎦+1 (n ≥ 1) Diese zeigt insbesondere, dass C n ~ n log 2 n (bzw. etwas gröber C n = Θ(n log 2 n ) ). Auf dieses Ergebnis wäre man aber auch mit Hilfe des Master-Theorems gekommen, indem man die rekursive Beziehung zunächst vereinfacht zu C n = 2C n / 2 + n für n>1 und C1 = 0 . und dann den hier zutreffenden 2. Fall a = b = 2 im Master-Theorem betrachtet. Wir wollen nun eine grobe Einteilung von Problemen in verschiedene Komplexitätsklassen vornehmen. Es bedeutet hierbei keine Einschränkung der Allgemeinheit, wenn wir nur Entscheidungsprobleme mit einer Antwort in {JA,NEIN} (oder auch {1,0} bzw. 11 {WAHR,FALSCH}) betrachten, da sich jedes Problem in ein oder mehrere Entscheidungsprobleme umformulieren lässt. Die einfachsten Probleme sind nun sicher diejenigen, für die es einen sog. Polynomialzeitalgorithmus gibt, d.h. dessen Laufzeit T(n) sich durch T(n)=O( n k ) für eine positive Konstante k nach oben hin abschätzen lässt. Die Klasse all dieser Probleme wird im folgenden mit P bezeichnet. Solche Probleme werden auch als effizient berechenbar oder praktikabel bezeichnet. Eine weitere wichtige Komplexitätsklasse ist NP. Es ist dies die Klasse aller Probleme, für es einen nichtdeterministischen Polynomialzeitalgorithmus gibt, was JA-Antworten betrifft. Darunter versteht man, dass eine konkret vorgelegte Lösung, welche zu einer positiven Antwort führt, in Polynomialzeit auf Korrektheit überprüft werden kann. Was nichtdeterministisch daran ist, ist die Art und Weise, wie man zur Lösung kommt. (Im Extremfall durch eine Zufallswahl unter allen Möglichkeiten oder durch Raten!) Gilt dasselbe, aber auf NEIN-Antworten bezogen, so erhält man die Komplexitätsklasse co-NP. Trivialerweise gilt sowohl P ⊆ NP als auch P ⊆ co-NP. Versucht man Probleme aus der Klasse NP dadurch zu lösen, indem man einfach alle in Frage kommenden Lösungen mit Hilfe des Polynomialzeitalgorithmus überprüft, so kommt man in der Regel auf Abschätzungen für die Laufzeit T(n) der Form T(n)=O( e f ( n ) ). Es sind dies die Probleme mit exponentiellem Wachstum, welche zur Komplexitätsklasse EXP gehören. Wenn dabei f(n) nicht allzu schnell wächst, genauer wenn gilt f(n)=o(n), so spricht man auch von subexponentiellem Wachstum. Eine andere Klasse ist RP (random polynomial time). Es ist dies die Klasse der Probleme, für deren Lösung es ebenfalls einen Polynomialzeitalgorithmus gibt, wobei jedoch die JA-Antworten mit einer Irrtumswahrscheinlichkeit ≤ 1/2 behaftet sind, während die NEIN-Antworten stets korrekt sind. Indem man hier JA und NEIN austauscht, kommt man analog wie oben zur Klasse co-RP. Wieder gilt nach Definition P ⊆ RP und P ⊆ co-RP. Auf Probleme in der Klasse RP stößt man häufig im Zusammenhang mit Primzahltests. Als typisches Beispiel sei hier der sog. Rabin-Miller Test genannt, bei dem eine vorgegebene ungerade Zahl N>1 in der Weise auf Primalität getestet wird , indem man N zunächst in der Form N = s2 t + 1 , ungerade, t > 0 darstellt und dann für eine Zufallszahl a mit 1<a<N überprüft, ob entweder a s ≡ 1 mod N gilt, oder unter den ersten t Gliedern u 0 , u 1 ,..., u t −1 der rekursiv durch u 0 = a s mod N, u k +1 = u 2k mod N Folge einmal –1 mod N vorkommt. Trifft dies zu, wird N als Primzahl akzeptiert, ansonsten verworfen. Nach einem Satz von Rabin-Monier ist dann die Fehlerwahrscheinlichkeit für die Aussage „N ist prim“ bei einem einzelnen Test < ¼ und 12 kann durch mehrfache Wiederholung des Tests sogar beliebig klein gemacht werden. (Man beachte, dass im Gegensatz dazu ein Testergebnis „N ist zusammengesetzt“ stets korrekt ist.) Im Zusammenhang mit Primzahltests und Faktorisierungsproblemen nimmt man übrigens als Maß für die „Größe“ des Problems üblicherweise nicht die zu untersuchende Zahl N selbst, sondern die Anzahl n = ⎣log10 N ⎦ + 1 ihrer Stellen oder rechnerisch auch ln N, was wegen n = Θ(ln N) in Komplexitätsabschätzungen auf dasselbe hinausläuft. Wie später noch genauer ausgeführt wird, hat ein einzelner Rabin-Miller-Test dabei die Laufzeit O(n 3 ) bzw. O( (ln N) 3 ). Ist C irgendeine Komplexitätsklasse mit C ⊇ P und sind A,B ∈ C Probleme mit der Eigenschaft, dass sich die Lösung von A in Polynomialzeit auf die Lösung von B zurückführen lässt, so sagt man A sei (polynomial) reduzierbar auf B und schreibt A ≤ P B . Gilt sowohl A ≤ P B als auch B ≤ P A , so heißen A und B (polynomial) äquivalent, i.Z. A ≡ P B . Die anschauliche Deutung: Im Falle A ≤ P B ist A höchstens so schwer wie B, im Falle A ≡ P B sind A und B gleich schwer. Ist das Problem B aus einer Komplexitätsklasse D mit D ⊇ C und so beschaffen, dass (bezogen auf D) sogar A ≤ P B für alle A∈ C gilt, so nennt man es C-hart. Liegt darüber hinaus B sogar in C, so nennt man es C-vollständig. Ist B ein C-hartes Problem und gilt B ≤ P B′ , so ist wegen der Transitivität von ≤ P dann B′ ebenfalls C-hart. Von besonderer Bedeutung für die Komplexitätstheorie sind NP-vollständige Probleme, die also nach obigem gewissermaßen die „härtesten“ Probleme in der Klasse NP darstellen, auf deren Lösung sich die Lösung aller anderen Probleme in NP zurückführen lassen. Würde man auch nur für eines der NP-vollständige Probleme zeigen können, dass es in P liegt, so hätte dies NP ⊆ P , also weiter NP =P zur Folge. (Es ist unbekannt, ob dies gilt, nach heutigem Stand jedoch eher unwahrscheinlich.) Um zu zeigen, dass ein vorgegebenes Problem NP-vollständig ist, müsste man nach obigem nur zeigen, dass es erstens in NP liegt und zweitens ein bereits als NPvollständig erkanntes Problem darauf reduzierbar ist. Dazu benötigt man allerdings ein „allererstes“ Problem, von dem „auf normalem Weg“ zeigen kann, dass es NPvollständig ist. Diesen Zweck erfüllte historisch gesehen (Beweis von Cook, 1971) das nachfolgende SAT- Problem (Erfüllungsproblem der Aussagenlogik) Gegeben: Eine Formel F der Aussagenlogik. Gefragt: Ist F erfüllbar (engl. satisfiable), d.h. gibt es eine Belegung der Variablen mit Wahrheitswerten, sodass F den Wert WAHR bekommt? Das allgemeine SAT-Problem lässt sich auf ein ganz spezielles reduzieren, nämlich: 3-SAT (auch 3KNF-SAT genannt) 13 Gegeben: Eine Formel F der Aussagenlogik in konjunktiver Normalform (KNF) mit höchstens 3 Literalen pro Klausel. (Unter Klauseln versteht man dabei die Maxterme der KNF und unter Literalen deren mit ∨ verknüpften Komponenten.) Gefragt: Ist F erfüllbar? Damit ist es nach obigem nun relativ einfach, für eine Reihe weiterer Probleme den Nachweis zu erbringen, dass sie NP-vollständig sind. Nachfolgend eine kleine Auswahl: Mengenüberdeckung Gegeben: k Mengen T1 ,..., Tk ⊆ M für eine feste Grundmenge M und ein n ≤ k . Gefragt: Gibt es eine Auswahl von n Teilmengen Ti1 ,..., Ti n , deren Vereinigung M ist, d.h. die M „überdecken“? Cliquen-Problem Gegeben: Ein ungerichteter Graph G=(V,E) und eine positive ganze Zahl k. Gefragt: Besitzt G eine Clique der Größe mindestens k? (Eine Clique ist dabei eine Teilmenge der Knotenmenge, wobei je zwei verschiedene Knoten durch eine Kante in E verbunden sind.) Knotenüberdeckung Gegeben: Ein ungerichteter Graph G=(V,E) und eine positive ganze Zahl k. Gefragt: Besitzt G eine „überdeckende Knotenmenge“ der Größe höchstens k? (Darunter versteht man eine Teilmenge V ′ ⊆ V mit V ′ ≤ k , sodass für alle Kanten {u, v}∈ E gilt u ∈ V ′ oder v ∈ V ′ .) Rucksack-Problem (oder Teilmengen-Summen-Problem) Gegeben: Positive ganze Zahlen a 1 ,..., a k und b. Gefragt: Gibt es eine Teilmenge I ⊆ {1,2,..., k} mit ∑a i∈I i = b? Partition Gegeben: Positive ganze Zahlen a 1 ,..., a k . Gesucht: Gibt es eine Teilmenge I ⊆ {1,2,..., k} mit der Eigenschaft ∑a = ∑a i∈I i i∉I i ? Bin Packing Gegeben: Eine „Behältergröße“ b ∈ N + , n „Objekte“ a 1 ,..., a n ≤ b und eine Anzahl k>0 von Behältern. Gefragt: Gibt es eine Abbildung f : {1,..., n} → {1,..., k} , sodass gilt ∑a f (i)= j i ≤ b für alle j=1,..,k, 14 d.h. gibt es eine Aufteilung der n Objekte auf die k Behälter, bei der kein Behälter „überläuft“? Gerichteter Hamilton-Kreis Gegeben: Ein gerichteter Graph G=(V,E). Gefragt: Besitzt G einen Hamiltonkreis, d.h. gibt es eine Permutation π der Knotenindexmenge {1,2,...,n}, sodass ( v π (i ) , v π( i +1) ) ∈ E für i = 1,2,...,n-1 und ( v π( n ) , v π(1) ) ∈ E . Ungerichteter Hamilton-Kreis Gegeben: Ein ungerichteter Graph G=(V,E). Gefragt: Besitzt G einen Hamiltonkreis, d.h. gibt es eine Permutation π der Knotenindexmenge {1,2,...,n}, sodass {v π (i ) , v π( i +1) } ∈ E für i = 1,2,...,n-1 und {v π ( n ) , v π(1) } ∈ E . Travelling Salesman Problem (TSP) Gegeben: Eine n x n Matrix (d ij ) von Distanzen zwischen n Städten und eine Zahl k>0. Gefragt: Gibt es eine „Rundreise“ einer Gesamtlänge ≤ k , d.h. eine Permutation π von {1,2,...,n}, sodass n −1 (∑ d π ( i ),π ( i +1) ) + d π ( n ),π (1) ≤ k ? i =1 Färbbarkeit von Graphen Gegeben: Ein ungerichteter Graph G=(V,E) und eine positive ganze Zahl k. Gesucht: Gibt es eine Färbung der Knoten von V mit k verschiedenen Farben, sodass keine zwei benachbarten Knoten in G diesselbe Farbe haben? Probleme aus der Klasse NP führen sehr häufig in natürlicher Weise auf Suchbäume. Von einem ausgezeichnet Knoten – der Wurzel des Baumes – aus, werden dabei auf der Suche nach Lösungen, welche gewissen ausgezeichneten Pfaden von der Wurzel zu den Blättern des Baumes entsprechen, die Knoten des Baumes solange in systematischer Weise abgesucht, bis man auf eine Lösung stößt. Hierbei gibt es zwei prinzipiell verschiedene Suchmethoden, die Tiefensuche (engl. Depth First Search oder kurz DFS) und die Breitensuche (engl. Breadth First Search oder kurz BFS). Bei der Tiefensuche geht man von der Wurzel ausgehend und bei den Knoten jeweils die erste noch nicht „abgearbeitete“ Verzweigung nehmend solange „in die Tiefe“, bis man auf ein Blatt des Baumes stößt. Ist dieses keine Lösung des Problems, so geht man im Zuge des Backtracking zum letzten Knoten zurück, welcher noch nicht abgearbeitete Söhne (bzw. Nachbarn) besitzt und nimmt die nächste Verzweigung. In dieser Weise fortfahrend kann man manchmal sehr schnell auf eine Lösung kommen, aber im worst case ist der Aufwand in der Regel exponentiell. 15 Bei der Breitensuche werden dagegen laufend Knoten einer (anfangs leeren) Warteschlange W hinzugefügt (beginnend mit der Wurzel des Baumes). In jedem Schritt wird dann der jeweils erste Knoten der Warteschlange nach einer problemspezifischen „Behandlung“ gestrichen und seine eventuelle Söhne (bzw. Nachbarn) in die Warteschlange hinten eingereiht. Ist die Warteschlange leer, so ist man alle Knoten des Baumes durchgegangen. Beide Verfahren, die Tiefen- und die Breitensuche, lassen sich als verschiedene Methoden der Durchnummerierung von Knoten eines Graphen ansehen, welche dann jeweils die Reihenfolge vorgibt, in der die Knoten zur Lösung eines spezifischen Problems „abgearbeitet“ werden, z.B. 1 2 3 1 6 5 8 7 4 Durchnummerierung bei Tiefensuche 2 5 3 6 4 7 8 Durchnummerierung bei Breitensuche Eine Kombination aus Tiefensuche und Breitensuche sind sog. Branch and Bound Algorithmen. Sie werden bei der Lösung von Optimierungsverfahren eingesetzt, bei denen keine anderen effizienten Verfahren bekannt sind, z.B. bei NP-vollständigen Optimierungsproblemen wie dem TSP. Muss etwa bei einem Problem eine Kenngröße (z.B. bei TSP die Länge eines Rundweges) minimiert werden, so kann man oft für jeden Knoten Schranken (engl. bounds) angeben, welche bei Vervollständigung von der durch jeden Knoten gegeben partiellen Lösung (z.B. bei TSP die Länge des Weges durch alle bis dahin besuchten Städte) zu einer vollständigen Lösung nicht unterschritten werden können. Bei TSP könnte man einen Bound z.B. folgendermaßen gewinnen: Man bildet zunächst aus der Distanzmatrix D=( d ij ) der n Städte die sog. zeilenreduzierte Matrix D′ = (d ′ij ) , indem man von allen Elementen einer jeden Zeile ihr Minimum abzieht und danach in völlig analoger Weise D′ die spaltenreduzierte Matrix D′′ = (d ′ij′ ) , indem man die gleiche Prozedur spaltenweise durchführt. Die Gesamtsumme der Zeilen- und Spaltenminima wäre dann ein Bound für das TSP, der nicht unterschritten werden kann. Bei der praktischen Durchführung wird nun ein Pfad in dem Suchbaum bis zu einer vollständigen Lösung verfolgt, wobei jeweils die Abzweigungen im Suchbaum genommen werden, die zu Knoten mit minimalem Bound führen, was einer Tiefensuche entspricht. Anschließend müssen dann nur noch jene Knoten abgearbeitet werden, welche einen kleineren Bound als die schon gefundene vollständige Lösung haben, wobei die Abarbeitung der noch in Frage kommenden Knoten wie bei einer Breitensuche geschieht. In völlig analoger Weise können damit natürlich auch Maximierungsprobleme behandelt werden. 16 2. Algorithmen auf Graphen Wir wenden uns nun der Untersuchung konkreter Algorithmen zu, wobei der erste Schwerpunkt den Algorithmen auf Graphen gewidmet ist. Um dabei für das folgende eine feste Grundlage zu haben, wiederholen wir zunächst einige grundlegende Definitionen der Graphentheorie. Ein gerichteter Graph (oder Digraph) wird nachfolgender immer in der Form G=(V,E) angegeben, wobei E ⊆ V × V wobei eine Relation auf der Menge V der Knoten des Graphen ist. Die Elemente von E werden dabei auch Bögen oder (gerichtete) Kanten genannt. Ist E symmetrisch und irreflexiv, so erhält man einen (ungerichteten oder gewöhnlichen) Graphen. Da es dabei auf Unterscheidung der Richtung nun nicht mehr ankommt, fasst man meist je zwei gegenläufige Kanten (u,v),(v,u) ∈ E zu einer zusammen, welche mit {u,v} bezeichnet wird. Gelegentlich ist für einen (gerichteten oder ungerichteten) Graphen auch noch eine Funktion w definiert, welche jedem Bogen bzw. jeder Kante eine reelle Zahl zuordnet, wobei diese von der Problemstellung her meist nichtnegativ ist. (Z.B. könnte diese Bewertung für einen Graphen, welcher ein Straßennetz repräsentiert, die Längen bzw. die Transportkosten für die Straßenstücke sein, welche die Knoten verbinden.) Man spricht in diesem Fall von bewerteten Graphen. Ist G=(V,E) ein gerichteter Graph und V = {v1 , v 2 ,..., v n } endlich, was wir im Folgenden stets voraussetzen wollen, so kann die Relation E in einfacher Weise durch eine Matrix A= (a ij ) , die sog. Adjazenzmatrix des Graphen, beschrieben werden, für die gilt ⎧ 1, falls (v i , v j ) ∈ E a ij := ⎨ sonst ⎩ 0 Liegt darüber hinaus eine Bewertung w auf G vor, so wird auch oft die Matrix betrachtet, bei der die entsprechenden Matrixeinträge die Bewertungen der Kanten sind. (Führt dabei keine Kante von v i nach v j , so ist der entsprechende Matrixeintrag je nach Problemstellung 0 oder ∞ .) Anstelle von Adjazenzmatrizen, für deren Speicherung ja in jedem Falle V 2 Plätze benötigt werden, ist es speziell bei Graphen mit wenig Kanten günstiger, sog. Adjazenzlisten zu betrachten, bei der für jeden Knoten v ∈ V die Menge A(v)= {w ∈ W ( v, w ) ∈ E} (oft als verkettete Liste) abgespeichert wird. Nimmt man an, dass pro Knoten bzw. Kante ein konstanter Speicherplatzbedarf benötigt wird (was allerdings nicht sehr realistisch ist, da die Graphen ja unbeschränkt groß sein können), so ist dann O( V + E ) eine Abschätzung für den gesamten Speicherplatzbedarf. Dies alles gilt natürlich auch für ungerichtete Graphen, indem man sie, wie oben ausgeführt, als Spezialfälle von gerichteten Graphen ansieht. In einem gerichteten Graph (V,E) heißt nun jede Folge v 0 v1 ...v k mit v 0 , v1 ,..., v k ∈ V und ( v 0 , v1 ), ( v1 , v 2 ),..., ( v k −1 , v k ) ∈ E eine (gerichtete) Kantenfolge. v 0 und v k heißen dann (durch die Kantenfolge) verbunden. Die Zahl k wird dabei die Länge der 17 Kantenfolge genannt. Ist dabei v 0 = v k so spricht man von einer geschlossenen Kantenfolge, sonst von einer offenen. Eine offene bzw. geschlossene Kantenfolge, die jede Kante des Graphen enthält, wird (wie dann auch der Graph selbst) eulersch genannt. Falls alle Knoten der Kantenfolge paarweise verschieden sind, spricht man von einer Bahn oder einem (gerichteten) Weg, falls dies mit genau der Ausnahme v 0 = v k zutrifft von einem Zyklus oder einem (gerichteten) Kreis. Ein Weg bzw. Kreis, der alle Knoten des Graphen enthält, wird (wie dann auch der Graph selbst) hamiltonsch genannt. Existieren in dem gerichteten Graphen keine Zyklen positiver Länge, so heißt er azyklisch. Alle zuvor eingeführten Begriffe lassen sich auch wieder auf ungerichtete Graphen spezialisieren, wodurch man insbesondere die zu den Begriffen Bahn und Zyklus analogen Begriffe Weg bzw. Kreis erhält. Statt azyklisch sagt man auch kreisfrei. Ein ungerichteter kreisfreier Graph wird auch Wald genannt. In ungerichteten Graphen (V,E) kann für alle a,b ∈ V a ~ b :⇔ a und b sind durch einen Weg verbunden eine Äquivalenzrelation definiert werden. Die Klassen der zugehörigen Partition heißen die Zusammenhangskomponenten des Graphen. Besitzt der Graph nur eine Zusammenhangskomponente (d.h. sind je zwei seiner Knoten verbunden), so heißt er zusammenhängend. Diesen Zusammenhangsbegriff kann man in ganz analoger Weise auch gerichteten Graphen einführen, was dann auf sog. stark zusammenhängende gerichtete Graphen Graphen führt. Führt sie ist aber auch noch die Abschwächung sehr wichtig, bei der man nur verlangt, dass der zugrundeliegende ungerichtete Graph (sein sog. Schatten, der aus dem gerichteten Graphen im Wesentlichen durch Weglassung der Kantenorientierung hervorgeht) zusammenhängend ist. In diesem Fall spricht man von schwach zusammenhängenden gerichteten Graphen. Ein ungerichteter Graph, welcher zusammenhängend und kreisfrei ist, wird Baum genannt. Insbesondere sind also die Zusammenhangskomponenten eines Waldes stets Bäume. Bäume sind besonders strukturierte Graphen, welche in vielen Anwendungen eine wichtige Rolle spielen. Sie lassen sich auf verschieden Weisen einfach charakterisieren, wie der folgende Satz zeigt: Satz 2.1: Für einen (ungerichteten) Graphen G=(V,E) mit n Knoten sind folgende Aussagen äquivalent. (1) G ist ein Baum. (2) Je zwei Knoten von G sind durch genau einen Weg verbunden. (3) G ist zusammenhängend, aber der Graph G e , der aus G durch Wegnahme einer beliebigen Kante e ∈ E entsteht, ist nicht mehr zusammenhängend. (4) G ist zusammenhängend und hat genau n-1 Kanten. (5) G ist kreisfrei und hat genau n-1 Kanten. 18 (6) G ist kreisfrei, aber der Graph G v , w , welcher aus G entsteht, indem man zwei ursprünglich nicht benachbarte Knoten v, w ∈ V durch eine Kante verbindet, enthält genau einen nichttrivialen Kreis. Ist G=(V,E) ein ungerichteter Graph, so heißt G ′ = (V ′, E ′ ) ein Teilgraph von G, wenn gilt V ′ ⊆ V und E ′ ⊆ E . Gilt dabei insbesondere V ′ = V , so heißt G ′ ein spannender Teilgraph von G. Ist ferner G zusammenhängend und G ′ ein spannender Teilgraph, welcher ein Baum ist, so heißt G ′ auch spannender Baum oder ein Gerüst von G. In vielen Anwendungen ist nun z.B. für einen zusammenhängenden ungerichteten Graphen mit einer Bewertung ein Minimalgerüst gesucht, d.h. ein Gerüst, für welches die Summe aller Bewertungen seiner Kanten minimal ist. (In obigem Beispiel eines Straßennetzes sollten z.B. nach der “Stilllegung” von möglichsten Straßen doch alle Knoten verbunden bleiben und die Summe der Distanzen insgesamt ein Minimum sein.) Der nachfolgende Algorithmus von Kruskal ist vom Typ her ein sog. GreedyAlgorithmus. Damit ist gemeint, dass in den einzelnen Schritten des Algorithmus immer gerade das gemacht wird, was im Moment das Beste zu sein scheint (“den besten Happen zuerst”), um zu einer insgesamt optimalen Lösung zu gelangen. Algorithmus 2.2 (Kruskal) Sei G=(V,E) ein zusammenhängender Graph mit einer Bewertung w. Auf nachfolgende Weise erhält man dann für ihn ein Minimalgerüst: 1. Sortiere zunächst die Kantenmenge E = {e1 , e 2 ,..., e m } so um, dass danach gilt w (e1 ) ≤ w (e 2 ) ≤ ... ≤ w (e m ) und setze zu Beginn B ← ∅ , M ← {{v} v ∈ V} , k ← 1. Die Elemente von M werden dabei im folgenden als “Komponenten” angesprochen. (Tatsächlich repräsentieren sie die jeweiligen Zusammenhangskomponenten beim sukzessiven Aufbau des Minimalgerüsts B.) 2. Füge die Kante e k zu B dazu , d.h. B:= B ∪ {e k } , falls die Endknoten von e k in verschiedenen Komponenten K 1 , K 2 von M liegen und führe in diesem Fall die Ersetzung M ← (M \ { K 1 , K 2 }) ∪ {K 1 ∪ K 2 } durch, d.h. die beiden Komponenten K 1 und K 2 werden zu einer “verschmolzen”. 3. Ist M = 1 , so stoppe das Verfahren mit der Ausgabe des Minimalgerüsts B. Ansonsten setze k ← k+1 und fahre bei 2. fort. Die Begründung, warum dieser Algorithmus funktioniert, basiert im Wesentlichen auf folgenden 19 Satz 2.3: Sei G=(V,E) ein zusammenhängender Graph mit der Bewertung w und U ⊆ V . Ist dann e 0 eine Kante, welche zwei Knoten in U und in V\U verbindet mit minimaler Bewertung, so existiert stets ein Minimalgerüst B, welche diese Kante enthält. Was die Komplexität des Kruskal-Algorithmus betrifft, so beträgt diese für den ersten Schritt der Sortierung (z.B. mit Hilfe von Mergesort) O( E ln E ) und für die Durchführung der Rekursion in 2. und 3. bei geschickter „Buchführung“ der Zugehörigkeit der Knoten zu den Komponenten O( E ln V ) , was dann wegen E ≤ V ( V − 1) / 2 < V ⇒ ln E = O(ln V ) 2 zugleich die Gesamtkomplexität des Verfahrens ist. Insbesondere liegt also der KruskalAlgorithmus in der Komplexitätsklasse P. Ebenfalls vom Typ „Greedy-Algorithmus“ ist der Dijkstra-Algorithmus zur Bestimmung der kürzesten Wege zwischen einem festgehaltenen Knoten v 0 und einem beliebigen Knoten v in einem (gerichteten oder ungerichteten) Graphen mit einer Bewertung w. („Kürzest“ ist dabei in Hinblick auf die Summe der Bewertungen längs eines Weges zu verstehen.) In einer nachfolgenden Formulierung werden wir, um uns bez. des Typs des Graphen nicht festlegen zu müssen, eine Kante von u nach v einfach mit uv bezeichnen, wobei dann uv dann (u,v) bzw. {u,v} bedeutet, je nachdem, ob der Graph gerichtet ist oder nicht. Algorithmus 2.4: (Dijkstra) Sei G=(V,E) ein (gerichteter oder ungerichteter) Graph mit einer nichtnegativen Bewertung w. Ist dann v 0 ∈ V ein festgewählter Knoten, so kann dann für jeden Knoten v ∈ V in folgender Weise der Abstand d(v):=d(v, v 0 ) , sowie eine Menge pred(v) aller unmittelbaren “Vorgänger” (auf einem kürzesten Weg von v nach v 0 ) berechnet werden: (1) Setze d ( v 0 ) ← 0, d(v) ← ∞ ∀ v ∈ V \{ v 0 } , pred(v):= ∅ ∀ v ∈ V , sowie U ← V. (2) Falls U= ∅ , dann STOP, sonst weiter mit (3). (3) Finde ein u ∈ U für das d(u) minimal ist. Ist d(u)= ∞ , dann ebenfalls STOP. (4) Für alle v ∈ U mit uv∈E sei d(v):=min{d(v),d(u)+w(uv)}. Zusätzlich wird, falls der neue Wert von d(v) kleiner als der alte ist, auch pred(v) aktualisiert: pred(v) ← {u}. (Ist man dabei nicht nur an einem, sondern an allen kürzesten Wegen von v 0 nach v interessiert, so hätte man im Fall der Gleichheit des alten und neuen Distanzwertes die Ersetzung pred(v) ← pred(v) ∪ {u} vorzunehmen.) (5) Setze U:=U\{u} und mach weiter mit (2). Nach Beendigung des Algorithmus kann man zu jedem v ∈ V , welches von v 0 überhaupt über einen Weg “erreichbar” ist, was sich in d(v)< ∞ ausdrückt, auch sofort einen kürzesten Weg v 0 v1 ...v k von v 0 nach v = v k in der Weise angeben, indem man jeweils ein v i −1 ∈ pred( v i ) in der Reihenfolge i = k,k-1,…,1 auswählt. 20 2 Wie man leicht sieht, ist der Rechenaufwand beim Dijkstra-Algorithmus O( V ) ), was bedeutet, dass er ebenfalls sehr schnell und insbesondere auch wieder ein Polynomialzeitalgorithmus ist. Ferner kann man mit demselben Aufwand, indem man einfach alle Kanten mit 1 bewertet, für jeden Graphen auch überprüfen, ob ein Knoten v von v 0 aus erreichbar ist, da genau in diesem Fall nach Durchlaufen des Algorithmus d(v) einen endlichen Wert hat. Will man die kürzeste Distanz zwischen zwei beliebigen Knoten eines Graphen feststellen, so könnte man im Prinzip den Dijkstra-Algorithmus V -mal durchführen, indem man einfach den Startknoten alle Knoten des Graphen durchlaufen läßt, was dann 3 offensichtlich auf den Gesamtaufwand O( V ) führt. Von derselben Komplexität, aber in der Praxis dann doch schneller ist der sog. WarshallFloyd Algorithmus. Sei dazu V= {v1 ,...., v n } die Knotenmenge eines (gerichteten oder ungerichteten ) Graphen mit der Bewertung w und für jedes k ∈ {1,.., n} sei d ij( k ) die kürzeste Länge eines Weges zwischen v i und v j , i,j=1,..,n, (“kürzest” wieder in Hinblick auf die Bewertung) , welcher nur Knoten aus der Menge {v1 ,...., v k } enthält. Existiert überhaupt kein derartiger Weg, so setzen wir d ij( k ) = ∞ . Offensichtlich gilt dann die rekursive Beziehung d ij( k ) = min(d ij( k −1) , d ik( k −1) + d (kjk −1) ) , i,j,k=1,…,n d.h. wir müssen bei Hinzunahme des Knotens v k jedesmal prüfen, ob es gegenüber dem alten Weg ohne v k nun nicht vielleicht einen kürzeren Weg gibt, der aber dann von v i nach v k und von v k weiter nach v j laufen muss, wobei diese beiden Teilwege nur jeweils Knoten aus {v1 ,...., v k −1 } enthalten. Der nachfolgende Algorithmus war ursprünglich von Warshall nur dazu konzipiert worden, die transitive Hülle einer Relation zu bestimmen, d.h. die Bewertungen wurden von ihm alle als 1 angenommen, und er wurde erst von Floyd auf die vorliegende allgemeinere Form gebracht. Algorithmus 2.5: (Warshall-Floyd) Sei G = (V,E) ein (gerichteter oder ungerichteter) Graph mit der Bewertung w und der Knotenmenge V= {v1 ,...., v n } . Nach Durchlaufen des folgenden Algorithmus enthält dann d ij die Länge eines kürzesten Weges zwischen v i und v j , i,j=1,…,n, bezogen auf die Bewertung: 1. Setze zu Beginn für i,j=1,…,n ⎧ 0, ⎪ d ij ← ⎨ w(v i v j ), ⎪ ∞, ⎩ falls i = j falls v i v j ∈ E falls v i v j ∉ E 21 2. Für alle k=1,..., n für alle i=1,..., n für alle j=1,..., n falls d ik + d kj < d ij setze d ij ← d ik + d kj , Insbesondere können wir nach dem Durchlaufen des Algorithmus aus dem Erfülltsein der Bedingung d ij < ∞ wieder ablesen, ob zwei Knoten v i und v j durch einen Weg verbunden sind oder nicht. Ferner können wir aus der Anzahl der Iterationen in Schritt 2, welche ja n 3 beträgt, schließen, dass der Gesamtaufwand, wie bereits erwähnt, 3 tatsächlich von der Ordnung O( V ) ist. Ein weiterer wichtiger Begriff für Graphen ist der Grad eines Knotens. Genauer definieren wir für einen gerichteten Graphen G=(V,E) und für jeden Knoten v ∈ V den Weggrad von v als die Anzahl der Bögen, welche von v “wegführen”, i.Z. d + ( v). Analog heißt die Anzahl der Bögen, welche zu v “hinführen” der Hingrad von v, i.Z. d − ( v). Bei Spezialisierung auf ungerichtete Graphen entfällt natürlich die Unterscheidung zwischen Hingrad und Weggrad und man spricht dann einfach vom Grad d(v) des Knotens v. Interpretiert man d + ( v) bzw. d − ( v) als die Summe der Elemente in der Zeile bzw. Spalte der Adjazenzmatrix, welche zu v gehört, so ergibt sich daraus unmittelbar Satz 2.6: Ist jedem gerichteten Graph G=(V,E) gilt ∑d v∈V + ( v) = ∑ d − ( v) = E v∈V Durch Spezialisierung auf ungerichtete Graphen, wo aber jetzt jede Kante gewissermaßen „doppelt gezählt“ werden muss, erhält man daraus weiter Folgerung 2.7 („Handschlaglemma“): In jedem ungerichteten Graphen G=(V,E) gilt ∑ d ( v) = 2 E , v∈V d.h. die Summe aller Grade des Graphen ist gleich der doppelten Anzahl seiner Kanten. Mit Hilfe des Gradbegriffs können wir auch eine einfache Bedingung für die Existenz von geschlossenen eulerschen Kantenfolgen (auch eulersche Kreise oder Eulertouren genannt) angeben: Satz 2.8: Genau dann existiert in einem (ungerichteten) zusammenhängenden Graphen G=(V,E) ein eulerscher Kreis, wenn alle seine Knoten geraden Grad haben. Zu seiner Konstruktion bedient man sich des Algorithmus 2.9 (Hierholzer): Sei G=(V,E) ein (ungerichteter) zusammenhängender Graph, in dem alle Knoten geraden Grad haben. Dann erhält man auf folgende Weise einen eulerschen Kreis: 22 1. Wähle vom einem beliebigen Startknoten v 0 ausgehend sukzessive Knoten v1 , v 2 ,... mit der Eigenschaft, dass in der Kantenfolge v 0 v1 ...v i alle Kanten verschieden sind, bis i eine Kantenfolge v 0 v1 ...v k erreicht wird, die nicht mehr in dieser Weise fortsetzbar ist und dann aufgrund der gemachten Voraussetzungen notwendigerweise geschlossen sein muss, d.h. es ist v k = v 0 . Ist die so erhaltenene Kantenfolge K 0 bereits ein eulerscher Kreis, dann STOP. 2. Wähle einen Knoten w 0 = v j in K 0 , der mit einer noch “unbenutzten” Kante inzidiert und konstruiere davon ausgehend so wie in 1. einen weitere Kantenfolge K 1 = w 0 w 1 ...w m , die wieder nur bisher “unbenutzte” Kanten verwendet und für die dann ebenfalls wieder w m = w 0 gelten muss. Mit Hilfe von K 1 wird nun K 0 “aktualisiert” zu K 0 ← v 0 v1 ...v j w 1 ...w m −1 v j v j+1 ...v k d.h. K 1 wird in K 0 “eingebettet”. Ist damit ein eulerscher Kreis erreicht, dann STOP, ansonsten wird 2. solange wiederholt bis dies der Fall ist. Ist der Graph in Form von Adjazenzlisten (A v ) v∈V gespeichert, so ist es naheliegend, die “Buchhaltung “ über die Benutzung einer Kante uw in der Weise durchzuführen, dass man u aus A w und w aus A v “streicht”, also jeweils die Ersetzung A w ← A w \{u} und A u ← A u \{w} durchführt. Am Ende des Durchlaufs sind dann alle Adjazenzlisten leer. Insbesondere kann daher der Rechenaufwand durch O( E + V ) abgeschätzt werden. Interessanterweise sind die scheinbar verwandte Probleme, ob ein Graph hamiltonsch ist und wie gegebenenfalls ein hamiltonscher Kreis, d.h. ein Kreis, welcher jeden Knoten des Graphen genau einmal enthält, gefunden werden kann, alles andere als trivial, und gehören, wie wir bereits aus Kap. I wissen, zur Klasse NP. Es existieren dazu lediglich Teilresultate, wie z.B. der folgende Satz 2.10: Sei G=(V,E) ein ungerichteter Graph mit V ≥ 3. Gilt dann d(v) ≥ V /2, so ist G hamiltonsch. Für viele Problemstellungen, die gerichtete Graphen G = (V,E) betreffen, benötigen wir eine sog. topologische Sortierung der Knotenmenge V, womit eine Durchnummerierung V= {v1 , v 2 ,..., v n } gemeint ist, sodass stets gilt ( v i , v j ) ∈ E ⇒ i < j . Zunächst zeigt man leicht folgenden Satz 2.11: Genau dann lässt ein gerichteter Graph eine topologische Sortierung zu, wenn er azyklisch ist. Die Auffindung einer topologischen Sortierung geschieht dann mit Hilfe von Algorithmus 2.12: Sei G=(V,E) ein azyklischer gerichteter Graph, welcher durch seine Adjazenzlisten (A v ) v∈V gegeben ist. Auf folgende Weise erhält man dann eine topologische Sortierung: 23 1. Berechne vorweg die Liste (d − ( v)) v∈V aller Hingrade, indem die Adjazenzlisten durchlaufen werden. 2. Setze c ← 1 und U ← V. 3. Bestimme ein u ∈ U mit d − (u ) = 0 und setze ν (u) ← c, sowie d − ( v) ← d − ( v) − 1 für alle v ∈ V mit (u,v) ∈ E . 4. Setze U ← U\{u}. Falls U= ∅ , dann STOP, sonst setze c ← c+1 und mach weiter bei 3. Wiederum hat der Algorithmus dabei den gleichen Aufwand, wie der zur Erstellung bzw. Verwaltung der Adjazenzlisten, nämlich O( E + V ). Benötigt werden topologische Sortierungen z.B. bei der Berechnung von längsten Wegen in Netzplänen. Unter einem Netzplan versteht man dabei einen azyklischen gerichteten Graphen G mit einer reellen Bewertungsfunktion w, wobei darüberhinaus gefordert wird, dass ein Knoten Q mit d − (Q) = 0 (eine sog. Quelle) und ein Knoten S mit d + (S) = 0 (eine sog. Senke) vorliegt, und dass er darüberhinaus schwach zusammenhängend ist, d.h. ohne Berücksichtigung der Orientierung der Kanten gibt es stets einen (dann ungerichteten) Weg zwischen zwei verschiedenen Knoten des Graphen. In der sog. Netzplatztechnik geht es dann darum, für jeden Knoten des Netzplans den längsten Weg (wieder im Sinne der Bewertung) eines jeden Knotens sowohl zur Quelle als auch zur Senke zu bestimmen. In den Anwendungen sind dann die Knoten des Netzplans gewisse Ereignisse (z.B. bei einem Bauvorhaben das Erreichen von gewissen Stadien der Fertigstellung), die Quelle ist das Startereignis (z.B. Baubeginn) und die Senke das Zielereignis. Durch technologische Vorgaben können gewisse Ereignisse oft erst nach anderen eintreten, was sich im Graphen so ausdrückt, dass es von einem früheren Ereignis E zu einem späteren Ereignis F einen gerichteten Weg gibt, dessen Bögen alles Vorgänge sind, welche noch ablaufen müssen, bevor das Ereignis F eintreten kann, wenn E schon eingetreten ist. Wir denken uns die Knoten des Netzplanes mit den Nummern 9 bis n durchnummeriert, wobei wir stets annehmen, dass diese topologisch sortiert sind.. Wenn die Bewertung d ij der gerichteten Kante von i nach j die Zeitdauer darstellt, welche für den Vorgang, der durch den Bogen von i nach j dargestellt wird, benötigt wird, so sind dann für j=0,1,2,…,n die Größen FTj (=Frühestmöglicher Termin für das Eintreten des Ereignisses j), ST j (=Spätestmöglicher Termin für das Eintreten des Ereignisses j) von großer Wichtigkeit. Graphentheoretisch ist dabei FTj nichts anderes als die Länge eines im Sinne dieser Bewertung längsten Weges von 0 nach j und ST j die Differenz FTn -(Länge eines längsten Wegs von j nach n). Sie können mit der sog. Critical Path Method (abg. CPM) folgendermaßen rekursiv berechnet werden: 24 Algorithmus 2.13 (CPM): FT0 = 0, FTj = max{FTi + d ij (i, j) ∈ E}, j=1,2,…,n bzw. (mit dem dann bekannten FTn ) STn = FTn , STi = min{ST j − d ij (i, j) ∈ E}, i=n-1,n-2,…,0. Fallen FT i und ST i für einen Knoten i zusammen, so wird das dadurch repräsentierte Ereignis ein kritischen Ereignis genannt, da es dafür keinen Puffer (=zeitlichen Spielraum) gibt, will man die Mindestgesamtdauer des Projekts, nämlich FTn (auch krit. Dauer genannt), nicht gefährden. Aus graphentheoretischer Sicht müssen alle diese Knoten auf Wegen von maximaler Länge von der Quelle bis zur Senke, auch kritische Pfade (engl. critical paths) genannt, liegen. Ähnlich wie beim Dijkstra-Algorithmus müssen auch bei CPM alle Knoten durchgegangen werden, wobei der Aufwand pro Knoten O( V ) beträgt, womit sich der 2 Gesamtaufwand daher zu O( V ) ergibt. Abschließend noch ein Algorithmus zum Thema “Auftragsplanung mit Schlußterminen”. Sei dazu eine Menge von n Aufträgen gegeben, die wir der Einfachheit halber mit 1,…,n bezeichen. Jedem Auftrag i ist dabei ein Schlußtermin d i ∈ N* und ein Gewinn p i ∈ R + zugeordnet. Die Aufträge werden dabei auf einer Maschine, die pro Zeiteinheit genau einen Auftrag durchführen kann. Eine Teilmenge A ⊆ {1,..., n} von Aufträgen wollen wir dabei zulässig nennen, falls es für A eine Anordnung i1 , i 2 ,..., i k ihrer Elemente gibt, sodass d i1 ≥ 1, d i 2 ≥ 2, ... , d i k ≥ k , d.h. wenn jeder Auftrag von A vor seinem Schlußtermin erledigt werden kann. Natürlich sind wir in erster Linie an jenen zulässigen Teilmengen A interessiert, für welche der Gesamtgewinn maximiert wird. Für die Lösung dieser Aufgabe gibt es wieder einen “Greedy-Algorithmus”, dessen Komplexität, wie man zeigen kann, O(n 2 ln n ) beträgt: Algorithmus 2.14: Mit den oben eingeführten Bezeichnungen erhält man auf folgende Weise eine Teilmenge A ⊆ {1,..., n} , für welche der Gewinn maximal ist: 1. Nummeriere zu Beginn, soweit dies erforderlich ist, die Aufträge so um, dass danach gilt p1 ≥ p 2 ≥ K ≥ p i n und setze A ← ∅ und i ← 1. 2. Ist A ∪ {i} eine zulässige Lösung, so setze A ← A ∪ {i} . 3. Setzte i ← i+1. Falls i>n, dann STOP, sonst fahre mit 2. fort. Wie nachstehend ausgeführt, führt ein solcher “Greedy-Ansatz”genau dann zum Erfolg, wenn die Aufgabenstellung so interpretiert werden kann, dass optimale Lösungen in einem sog. Matroid gefunden werden müssen. Um genauer darauf eingehen zu können benötigen wir wieder einige Begriffe. Sei dazu E eine endliche Menge und U eine nichtleere Menge von Teilmengen von E. (E,U) heißt dann ein Teilmengensystem, wenn gilt 25 • A ⊆ B, B ∈ U ⇒ A ∈ U. Gilt darüber hinaus die sog. Austauscheigenschaft • A, B ∈ U, A < B ⇒ ∃ x ∈ B \ A: A ∪ {x} ∈ U so spricht man von einem Matroid. Ist ferner für ein Teilmengensystem (E,U) eine Gewichtsfunktion w: E → R vorgegeben, so ist dann das zugehörige Optimierungsproblem die Frage nach einer in U bez. ⊆ maximalen Teilmenge T, für welche das Gesamtgewicht w ( T ) = ∑ w ( e) e∈T ein Maximum (oder auch ein Minimum, je nach Aufgabenstellung) ist. Beim kanonischen Greedy-Algorithmus würde man dann in Analogie zu 4.14 so vorgehen, dass man die Elemente von E nach absteigendem (bzw. für ein Minimum nach aufsteigendem) Gewicht sortiert und zu einer anfänglich leeren Menge A in dieser Reihenfolge Elemente e ∈ E dazugibt genau dann, wenn für sie A ∪ {e} ∈ U gilt. Die so erhaltene Menge A wird jedoch im allgemeinen nicht optimal sein. Es gilt jedoch der grundlegende Satz 2.15: Sei (E,U) ein Teilmengensystem. Der kanonische Greedy-Algorithmus liefert für das zugehörige Optimierungsproblem ganau dann für jede Gewichtsfunktion w: E → R eine optimale Lösung, wenn (E,U) ein Matroid ist. 3. FFT oder schnelle Algorithmen für die Multiplikation Werden mit Hilfe der „Schulmethode“ zwei Zahlen a,b ≤ N miteinander multipliziert, so ist der Rechenaufwand offenbar von der Ordnung O( (ln N) 2 ), da dabei jede Ziffer von a mit jeder Ziffer von b multipliziert wird und die Anzahl der Bitoperationen bei diesen Operationen (nämlich der Anwendung des „kleinen 1 x 1“) nach oben beschränkt ist. Wie wir in diesem Kapitel aber zeigen werden, gibt es speziell für große Zahlen weit bessere Verfahren zur Multiplikation. Eine erste einfache Möglichkeit wurde Karatsuba 1962 (in etwas komplizierterer Form als nachfolgend beschrieben) angegeben. Denken wir uns dazu zwei positive ganze Zahlen u und v mit je 2n Bits durch ihre Binärdarstellung gegeben, also u = (u 2 n −1 ...u 1 u 0 ) 2 bzw. v = ( v 2 n −1 ...v1 v 0 ) 2 so können wir sie dann auch schreiben in der Form u = 2 n U 1 + U 0 , v = 2 n V1 + V0 , wobei U 1 = (u 2 n −1 ...u n ) 2 , U 0 = (u n −1 ...u 0 ) 2 bzw. V1 = ( v 2 n −1 ...v n ) 2 , V0 = (u n −1 ...u 0 ) 2 ist. Unter Benützung dieser Darstellungen gilt dann 26 uv = (2 2 n + 2 n ) U 1 V1 + 2 n ( U 1 − U 0 )(V0 − V1 ) + (2 n + 1) U 0 V0 . Wie diese Formel zeigt, kommt man also bei der Multiplikation von zwei Zahlen mit je 2n Bits mit nur drei Multiplikationen von Zahlen mit je n Bit, sowie einigen einfachen binären Schiebeoperationen und Additionen aus. Bezeichnet also T(n) die Anzahl der Bitoperationen für die Multiplikation von zwei Zahlen mit je n Bit, so erhalten wir so die Abschätzung T(2n ) ≤ 3T(n ) + cn für eine gewisse Konstante c > 0. Daraus folgt nun ähnlich wie in 1.1, dass gilt Satz 3.1: Für die Multiplikation von zwei Zahlen mit je n Bits nach Karatsuba ist der Rechenaufwand durch O(n lg 3 ) ≈ O(n 1.585 ) nach oben beschränkt. Karatsuba’s Methode kann noch in der Weise verallgemeinert werden, dass man u und v allgemeiner in r+1 (r ≥ 1 ) Binärblöcke unterteilt, also in der Form u = U r 2 rn + ... + U1 2 n + U 0 bzw. v = Vr 2 rn + ... + V1 2 n + V0 darstellt, wobei die U j bzw. Vj jeweils Zahlen mit n Bits sind. Sind dann U( x ) = U r x r + ... + U 1 x + U 0 bzw. V( x ) = Vr x r + ... + V1 x + V0 die zugehörigen Polynome und ist W(x) deren Produkt, also W ( x ) = U( x )V( x ) = W2 r x 2 r + ... + W1 x + W0 , so gilt dann u = U(2 n ) bzw. v = V(2 n ) , sowie uv = W (2 n ) . Wir können daher uv leicht berechnen, wenn wir die Koeffizienten von W(x) kennen. Eine effiziente Möglichkeit, diese Koeffizienten zu berechnen, besteht nun darin, dass man zunächst die 2r+1 Produkte U(0)V(0) = W(0), U(1)V(1) =W(1), ... , U(2r)V(2r) = W(2r) berechnet (obwohl bei U(j) und V(j) die Bitlänge von n in der Regel um einige Bits überschritten wird, um zwar um maximal t Bits, wobei t nur von r abhängt, so ist der Rechenaufwand für die Berechnung dieser Produkte doch von der Form T(n) + c1 n ). Es zeigt sich dann, dass die gesuchten Koeffizienten einfach eine Linearkombination dieser Produkte sind, sodass also rechnerisch nur die Berechnung dieser 2r+1 Produkte ins Gewicht fällt. Insgesamt erhalten wir daher für den Rechenaufwand wieder eine Abschätzung T((r+1)n) ≤ (2r+1)T(n) +cn mit deren Hilfe man dann leicht zeigen kann, dass allgemeiner gilt Satz 3.2: Für jedes ε > 0 gibt es einen Multiplikationsalgorithmus derart, dass für die Anzahl T(n) der Bitoperationen, welche für die Multiplikation von zwei Zahlen mit je n Bits benötigt werden, gilt T (n ) < c(ε)n 1+ε , 27 wobei c(ε) eine nur von ε abhängige Konstante ist. Dies bedeutet, dass es Algorithmen für die Multiplikation gibt, welche beliebig nahe an eine lineare Abhängigkeit von n herankommen, was doch einigermaßen überraschend ist. Im Detail gibt es aber noch eine Menge algorithmischer Feinheiten. Wir werden einige von diesen an dem nachfolgendem Beispiel studieren. Sei dazu u = (0100 1101 0010) 2 und v = (1001 0010 0101) 2 und r = 2. Die Polynome U(x) und V(x) haben dann folgendes Aussehen: U( x ) = 4 x 2 + 13x + 2 bzw. V( x ) = 9x 2 + 2x + 5 . Für die Werte von U(x), V(x) und W(x) = U(x)V(x) an den Stellen 0,1,2,3,4 erhalten wir dann U(0) =2, U(1)=19, U(2) =44, U(3)=77, U(4)=118; V(0) =5, V(1)=16, V(2) =45, V(3)=92, V(4)= 157; W(0) =10, W(1)=304, W(2)=1980, W(3)=7084, W(4) = 18526; Es geht also im Folgenden darum, möglichst effizient die Koeffizienten von W(x) aus den Werten W(0),...,W(4) zu berechnen. Dazu denken wir uns zunächst W(x) in der Form W(x) = a 4 x 4 + a 4 x 3 + a 4 x 2 + a 4 x 1 + a 0 dargestellt, wobei x k := x ( x − 1)...( x − k + 1) , k=0,1,...,4 die sog. faktoriellen Potenzen sind. Diese faktoriellen Potenzen haben allgemein die folgende wichtige Eigenschaft, dass ∆x k := ( x + 1) k − x k = kx k −1 , k=0,1,2,... d.h. durch Bildung dieser Differenzen erhält man einen Term, der äußerlich an eine 1.Ableitung für gewöhnliche Potenzen erinnert. Dies gilt dann auch für W(x) selbst: ∆W ( x ) := W ( x + 1) − W ( x ) = 4 a 4 x 3 + 3a 3 x 2 + 2a 2 x 1 + a 1 Dies kann man (in Analogie zu Mehrfachableitungen!) fortsetzen und erhält so ∆2 W ( x ) := ∆W ( x + 1) − ∆W ( x ) = 12a 4 x 2 + 6a 3 x 1 + 2a 2 ∆3 W ( x ) := ∆2 W ( x + 1) − ∆2 W ( x ) = 24a 4 x 1 + 6a 3 ∆4 W ( x ) := ∆3 W ( x + 1) − ∆3 W ( x ) = 24a 4 Man erhält so (in exakter Analogie zu den Formeln für die Koeffizienten einer Taylorreihe!) (1 / k!)∆k W (0) = a k , k=0,1,2,... 28 womit die a k in einfacher Weise durch nachfolgendes Differenzenschema erhalten werden können: 10 304 1980 7084 294 1676 5104 11442 1382/2 = 691 3428/2 = 1714 1023/3 = 341 1455/3 = 485 144/4 = 36 6338/2 = 3169 18526 Die erste Spalte wird dabei gebildet von W(0),W(1),...,W(4), die gesuchten Koeffizienten a 0 , a 1 ,...., a 4 stehen in dieser Reihenfolge an der Spitze der Spalten von links nach rechts, also a 0 = 10, a 1 = 294, ..., a 4 = 36 , d.h. es gilt W ( x ) = 36x 4 + 341x 3 + 691x 2 +294 x 1 +10 = = (((36( x − 3) + 341)(x − 2) + 691)( x − 1) + 294) x + 10 , wobei die letzere Formel eine einfache Möglichkeit angibt, die gesuchte „echte“ Polynomdarstellung W ( x ) = w 4 x 4 + w 3 x 3 + .... + w 1 x + w 0 von W(x) zu gewinnen, nämlich W(x) = 36x 4 + 125x 3 + 64x 2 + 69x + 10 . Die Antwort auf die ursprünglich gestellte Frage lautet daher 1234 ⋅ 2341 = W(16) = 2888794, wobei W(16) durch bloße binäre Addier- und Schiebeoperationen berechnet wird. Wir verzichten hier darauf, dass in diesem Beispiel vorgestellte Verfahren noch weiter auszuformalisieren und zu verfeinern, was dann zu dem sog. Toom-Cook-Algorithmus führen würde, da wir im Folgenden gleich noch bessere Verfahren kennenlernen werden. Dazu bedarf es allerdings einiger Vorbereitungen. Nachfolgend bezeichne dazu (R,+,⋅ ,1), wenn nichts anderes gesagt wird, stets einen kommutativen Ring mit Einselement. Def. 3.3: ω ∈ R heißt eine n-te Einheitswurzel (n>0), wenn ω n = 1 , und eine primitive nte Einheitswurzel, wenn darüber hinaus noch gilt, dass n: = 1+1+...+1 (n-mal) eine Einheit in R und ω n / p − 1 für keinen Primteiler p von n ein Nullteiler von R ist. Bem. 3.4: Ist R nullteilerfrei, z.B. ein Körper, so vereinfacht sich obige Bedingung für eine primitive n-te Einheitswurzel dahingehend, dass zwar ω n = 1 , aber ω n / p ≠ 1 für jeden Primteiler p von n gelten muss. Dies ist wiederum äquivalent damit, dass n die 29 Ordnung von ω in der Einheitengruppe E(R) ist. Speziell für einen Körper Fq existieren primitive n-te Einheitswurzeln also genau dann, wenn n ein Teiler von q-1 ist. Im klassischen Fall R = C dagegen existieren stets primitive n-te Einheitswurzeln für beliebiges n und sie sind alle von der Form exp(2πki/n), wobei 0 ≤ k < n und ggT(k,n)=1 gilt. Für unsere Zwecke liegt die Bedeutung von primitiven Einheitswurzeln begründet in dem Lemma 3.4: Für jede primitive n-te Einheitswurzel ω ∈R gilt: (1) ω k − 1 ist kein Nullteiler in R allgemeiner für alle k mit 0 < k < n. n −1 (2) ∑ω jk = 0 für alle k mit 0 < k < n. j= 0 (3) Seien Ω = (Ω jk ) und Ω = ( Ω jk ) und die Matrizen definiert durch Ω jk := ω jk bzw. Ω jk := ω − jk . Dann gilt ΩΩ = nI n , wobei I n hier und im folgenden die n-zeilige Einheits- matrix bezeichnet. Insbesondere ist also (1/n) Ω die zu Ω inverse Matrix. Damit sind wir nun bereit für Def. 3.5: Für jede primitive n-te Einheitswurzel ω ∈ R wird die R-lineare Abbildung DFTω : R n → R n ,welche für jeden Vektor a= (a 0 a 1 ,..., a n −1 ) ∈ R n definiert ist durch a a (f (1), f (ω), f (ω 2 ),..., f (ω n −1 )) ( f ∈ R n ) wobei f(x) = a 0 + a 1 x + ... + a n −1 x ∈ R [x ] , d.h. also DFTω (a) = a Ω , die Diskrete Fouriertransformation (DFT) genannt. Auf R n ist nun außer der punktweisen Addition, Multiplikation und Skalarmultiplikation mit Elementen aus R eine weitere wichtige Operation definiert, die im Folgenden eine große Rolle spielen wird. Def. 3.6: Für beliebige Vektoren a = (a 0 , a 1 ,..., a n −1 ) und b = (b 0 , b1 ,..., b n −1 ) in R n ist die sog. zyklische Faltung a * b definiert durch den Vektor c = (c 0 , c1 ,..., c n −1 ) ∈ R n , für welchen gilt c k := ∑a b i i + j≡ k mod n j (0 ≤ k < n) Bem. 3.7: Geht man von den Vektoren a = (a 0 , a 1 ,..., a n −1 ) , b = (b 0 , b1 ,..., b n −1 ) und c = = (c 0 , c1 ,..., c n −1 ) zu den entsprechenden Polynomen f(x) = a 0 + a 1 x + ... + a n −1 x , g(x) = = b 0 + b1 x + ... + b n −1 x bzw. h(x) = c 0 + c1 x + ... + c n −1 x in R[x] über, so sieht man unschwer ein, dass c = a * b genau dann gilt, wenn f(x)g(x) ≡ h(x) mod ( x n − 1 ). Die zyklische Faltung entspricht also aus algebraischer Sicht genau der Multiplikation in dem Faktorring R[x]/( x n − 1 ). Wie schon in der Definition angeführt, ist die DFT stets R-linear und wegen 2.4(3) auch bijektiv. Eine weitere wichtige Eigenschaft ergibt sich aus 30 Satz 3.8: Für beliebige Vektoren a,b ∈ R n gilt DFTω (a * b) = DFTω (a ) ⋅ DFTω (b) wobei auf der rechten Seite dieser Gleichung die punktweise Multiplikation in R n gemeint ist, d.h. die Abbildung DFTω ist ein Isomorphismus von (R n , * ) auf (R n , ⋅ ) . Damit eröffnet sich der folgende „Plan“ für die Multiplikation zweier großer ganzer Zahlen x,y > 0. Man stellt dazu x und y zunächst in einer geeignet gewählten Basis B dar, also n −1 x= ∑ x k B k und y = k =0 n −1 ∑y k =0 k Bk wobei n so groß gewählt wird, dass mindestens die Hälfte der führenden „Ziffern“ von x und y in diesen B-adischen Darstellung 0 sind. Durch dieses sog. Zero-padding wird erreicht, dass die Polynommultiplikation von x und y mod ( B n − 1) zu einer „normalen“ Multiplikation von x und y wird. Mit Hilfe von 3.8 können wir dann die Faltung, welche offensichtlich n 2 Multiplikationen benötigt, auf die punktweise Multiplikation zurückführen, welche nur mit n Multplikationen auskommt. Allerdings würde bei einer naiven Anwendung der DFT dieser Vorteil durch die notwendigen Transformationen und Rücktransformationen von x und y wieder zunichte gemacht werden. An dieser Stelle kommt nun eine Idee von Cooley und Tukey (1965) ins Spiel, welche aus der DFT die FFT (= Fast Fourier Transform) macht, allerdings unter der Voraussetzung, dass n eine Potenz von 2 ist. Satz 3.9: Ist n = 2 ν , so gibt es eine Möglichkeit die DFT so durchzuführen, dass man mit insgesamt 2 ν −1 ν Multiplikationen in R auskommt. Insbesondere bedeutet dies, dass die Anzahl der Multiplikationen in R von der Ordnung O(n lg n) ist. Der zugehörige Algorithmus wird schnelle Fouriertransformation oder FFT genannt. Bem. 3.10: In der Praxis wird nicht die rekursive Version der FFT verwendet, wie sie durch den Beweis von 2.9 nahegelegt wird, sondern eine iterative Version, bei der eine explizite Darstellung von Ω als Produkt von ν+1 Matrizen einfacher Bauart verwendet wird, welche nacheinander auf den Ausgangsvektor angewandt werden. Leider können wir darauf aus Zeitgründen nicht eingehen. Eine weitere wichtige Frage bei der Anwendung der FFT auf unser Multiplikationsproblem ist die Wahl eines günstigen Rings R, wobei man natürlich ausnützen wird, dass die Komponenten der zu transformierenden Vektoren ganze Zahlen sind. Es empfiehlt sich daher die Wahl R = Z m , wobei m so groß gewählt wird, dass n (b − 1) 2 < m. Letzeres deshalb, damit die beim Ausmultiplizieren von x und y entstehenden Terme n −1 ∑x j= 0 k− j yj , 0 ≤ k < n 31 unterhalb von m bleiben, d.h. keine Reduktion mod m und damit Verfälschung erfolgt. Außerdem muss auch noch darauf geachtet werden, dass überhaupt eine primitive n-te Einheitswurzel in Z m existiert. Von großem Nutzen ist daher das folgende ν Lemma 3.11: Sei n = 2 ν +1 und m = 2 2 + 1 für eine natürliche Zahl ν . Dann ist ω = 2 mod m eine primitive n-te Einheitswurzel in Z m . Bem. 3.12: Wegen ω = 2 und der besonderen Gestalt von m lassen sich die Multiplikationen in R = Z m mit den Potenzen von ω durch einfache Shift-Operationen ausdrücken, deren Aufwand nur proportional zur Bitlänge der betrachteten Zahlen steigt. Durch geschickte rekursive Anwendung dieser Fouriertransformation konnten Schönhage und Strassen (1971) zeigen, dass die Multiplikation von zwei Zahlen mit je n Bit eine asymptotische Komplexität von O( n ln n ln ln n ) hat. Da wir in diesem Kapitel viele schnelle Algorithmen für die Multiplikationen großer Zahlen kennengelernt haben, stellt sich die Frage, wie es diesbezüglich um die Division steht. Wie wir im Folgenden zeigen werden, lässt sich aber die Division zweier Zahlen auf nur einige wenige Multiplikationen zurückführen, sodass die oben angestellten Betrachtungen dann auch für sie gelten. Dies ergibt sich unmittelbar aus der einfachen Beobachtung, dass der Quotient u/v auch als Produkt u ⋅ 1/v geschrieben werden kann und 1/v Nullstelle der Funktion 1/x – v ist. Das Newtonverfahren zur iterativen Bestimmung dieser Nullstelle, nämlich x n +1 = x n + x n (1 − vx n ) = 2x n − vx 2n mit geeignet gewähltem Startwert x 0 konvergiert quadratisch gegen 1/v, da aus x n = (1 − ε) / v folgt, dass x n +1 = (1 − ε 2 ) / v . Dies bedeutet, dass sich die Anzahl der richtigen Stellen nach jeder Iteration etwa verdoppelt. Konvergenz der Ordnung 3, d.h. ε wird nach jeder Iteration zu O( ε 3 ), kann mit Hilfe der Formel x n +1 = x n + x n (1 − vx n ) + x n (1 − vx n ) 2 erzielt werden und es gibt sogar Formeln von Rabinowitz, welche eine Konvergenz der Ordnung 4 garantieren. In jedem Fall kann so die Division auf einige wenige Multiplikationen zurückgeführt werden. Gleiches gilt dann auch für die Berechnung der Wurzel a für a ≥ 0 , da die durch x n +1 = ( x n + a / x n ) / 2 definierte Folge ( x n ) für jeden positiven Startwert x 0 ebenfalls quadratisch gegen konvergiert. Auf algorithmische Feinheiten wollen wir dabei nicht mehr eingehen. a 32 4. Ausgewählte Algorithmen aus Algebra und Zahlentheorie Einer der wichtigsten Algorithmen der Mathematik ist sicher der sog. Euklidische Algorithmus, dem wir uns daher als erstes ausführlich widmen wollen. Er ist zugleich einer der ältesten Algorithmen, indem er bereits von Euklid im 7.Buch seiner „Elemente“ (ca. 300 v.Chr.) in einer einfachen Form beschrieben wurde. (D. Knuth meint sogar, er sei der „granddaddy of all algorithms, because it is the oldest nontrivial algorithm that has survived to the present day.“) Um ihn in der angemessenen Allgemeinheit formulieren zu können, wiederholen wir zunächst einige grundlegenden Begriffe der Algebravorlesung, ausgehend von Def. 4.1: Sei (R,+, ⋅ ) ein Integritätsring und a,b ∈ R. a heißt dann ein Teiler von b bzw. b ein Vielfaches von a, i.Z. a b , wenn es ein u ∈ R gibt, sodass au = b. In der folgenden Bemerkung sind einige einfache Eigenschaften für diesen Teilbarkeitsbegriff, sowie weitere grundlegenden Begriffe zusammengestellt. Bem. 4.2: Seien a,b,c,d im folgenden beliebige Elemente von R. Dann gilt: 1. Die Teiler von 1, welche auch Einheiten von R genannt werden, sind genau die bez. der Ringmultiplikation invertierbaren Elementen. Die Menge E(R) der Einheiten von R bildet bez. der (auf E(R) eingeschränkten) Multiplikation eine abelsche Gruppe. 2. Gilt für zwei Elemente a,b ∈ R, dass a b und b a , so heißen sie assoziiert, i.Z. a ~ b. Dies ist gleichwertig damit, dass a=be für ein e ∈ E(R) gilt. Die Assoziiertheitsrelation ~ ist eine Äquivalenzrelation auf R und die Teilbarkeitsrelation ⏐ „agiert“ eigentlich auf diesen Klassen, d.h. mit a b und a ~ a ′ und b ~ b ′ gilt stets auch a ′ b′ . 3. Für jedes a ∈ R sind die Einheiten von R und die zu a assoziierten Elemente ae mit e ∈ E(R) stets Teiler von, die sog. trivialen Teiler von a. Echte Teiler von a sind solche, welche nicht zu a assoziiert sind. Ist ferner a eine Nichteinheit ≠ 0, welche nur die trivialen Teiler besitzt, so wird es irreduzibel oder unzerlegbar genannt. 4. Es gilt a b ⇒ ac bc , wobei dies für c ≠ 0 auch umkehrbar ist, bzw. allgemeiner a b ∧ cd ⇒ ac bd , d.h. die Teilbarkeitsrelation ist verträglich mit der Multiplikation. 5. Aus a b und a c folgt auch a ( xb + yc) für beliebige x,y ∈ R. 6. Sind a 1 ,..., a n endlich viele Elemente von R, so wird d ∈ R ein größter gemeinsamer Teiler von a 1 ,..., a n genannt, i.Z. d= ggT( a 1 ,..., a n ), wenn gilt d a i ,i=1,2,...,n, und aus 33 t a i ,i=1,2,...,n, stets folgt t d . Dual dazu definiert man den Begriff des kleinsten gemeinsamen Vielfachen v von a 1 ,..., a n , i.Z. v=kgV( a 1 ,..., a n ). Man beachte, dass d=ggT( a 1 ,..., a n ) bzw. v=kgV( a 1 ,..., a n ) im Falle ihrer Existenz nur bis auf Assoziiertheit eindeutig bestimmt sind. Wir definieren nun eine weitere wichtige Eigenschaft von Integritätsringen, welche insbesondere auch die Existenz von ggT bzw. kgV für je endlich viele Elemente des Integritätsrings stets sicherstellen wird. Def 4.3: Ein Integritätsring (R,+, ⋅ ) heißt ein Euklidischer Ring, wenn es eine sog. Gradfunktion δ: R\{0} → N gibt, sodass für beliebige a,b ∈ R mit b ≠ 0 stets Elemente q,r ∈ R mit a=qb+r mit entweder r=0 oder δ(r)< δ(b) existieren. Bsp. 4.4: 1) Der Ring (Z,+, ⋅ ) der ganzen Zahlen bildet mit den Standardoperationen einen Euklidischen Ring, wenn man δ(a) für a ≠ 0 mit δ(a) = a definiert. Die einzigen Einheiten von Z sind 1 und –1, sodass assoziierte Elemente sich also höchstens durch das Vorzeichen unterscheiden. Ferner sind die irreduziblen Elemente von Z genau die Elemente von der Form ±p, wo p eine Primzahl ist. 2) Für jeden Körper (K,+, ⋅ ) ist der Polynomring (K[x],+, ⋅ ) ein Euklidischer Ring, wenn man für jedes Polynom p(x) ≠ 0 definiert δ(p(x)) = [p(x)] (= Grad von p(x)). Es gilt ferner E(K[x]) = K* (= K\{0}) und die irreduziblen Elemente von K[x] sind gerade die irreduziblen Polynome. 3) Die Ringe Z[ D ] sind mit der durch δ( a+b D )= a 2 − b 2 D definierten Gradfunktion für D = −2, −1,2,3 euklidisch (Bew. in Übg.). Insbesondere gilt dies daher für Z[i], den „Ring der ganzen Gaußschen Zahlen“. Bem. 4.5: Wie aus der Algebravorlesung bekannt, ist jeder Euklidische Ring (R, +, ⋅ ) auch stets ein Hauptidealring, d.h. jedes Ideal in R ist von der Form aR := {ar | r ∈ R}. Hauptidealringe sind ihrerseits faktorielle Ringe. Diese sind dadurch gekennzeichnet, dass in ihnen jedes irreduzible Element p auch Primelement ist, d.h. aus p ab stets folgt p a oder p b und sich jedes Element a ≠ 0 als Produkt von Primelementen schreiben lässt. Aus der Primelementeigenschaft ergibt sich auch sofort, dass diese Darstellung dann bis auf Reihenfolge und Assoziiertheit der Primfaktoren eindeutig ist. Sind dann a,b von 0 verschiedene Elemente von R, so lassen sie sich (nach Zusammenfassung asoziierter Primelemente) für gewisse irreduzible Elemente p1 , p 2 ,..., p r (r ≥ 0) und Einheiten e,f in der Form a = ep1α1 ...p αr r (α i ≥ 0) und b = fp1β1 ...p βr r (β i ≥ 0) darstellen, woraus dann unmittelbar folgt, dass d = p1min(α1 ,β1 ) ...p1min( α r ,βr ) bzw. v = p1max(α1 ,β1 ) ...p1max(α r ,βr ) ggT bzw. kgV von a und b sind. Wie wir aber nachfolgend sehen werden, gibt es speziell in Euklidischen Ringen eine viel bessere Methode zur Berechnung von ggT(a,b) (und 34 unter Verwendung der Beziehung dv ~ ab auch für kgV(a,b)), eben den „Euklidischen Algorithmus“. Satz 4.6 (Euklidischer Algorithmus) Sei (R,+, ⋅ ) ein Euklidischer Ring und a,b ∈ R mit b ≠ 0. Bildet man dann mit ro :=a und r1 :=b die „Divisionskette“ r0 = q 0 r1 + r2 mit δ(r2 ) < δ(r1 ) r1 = q 1 r2 + r3 mit δ(r3 ) < δ(r2 ) ... rn − 2 = q n − 2 rn −1 + rn mit δ(rn ) < δ(rn −1 ) rn −1 = q n −1 rn (wobei wir wegen δ(r1 ) > δ(r2 ) > δ(r3 ) > ... davon ausgehen können, dass für ein n>0 die n-te Division den Rest 0 ergibt), so ist dann rn - also der letzte nichtverschwindende Rest - ein ggT(a,b). Dies führt auf folgenden Algorithmus zur Bestimmung eines ggT(a,b) in allgemeinen Euklidischen Ringen Algorithmus 4.7 (Euklidischer Algorithmus) Seien a,b Elemente des Euklidischen Rings R und b ≠ 0. Der folgende Algorithmus findet dann einen ggT(a,b). 1. [b = 0?] Ist b =0, so bricht der Algorithmus ab mit ggT(a,b) = a als Ergebnis. 2. [Berechne den Rest der Division von a durch b] Berechne den Rest r bei der Division von a durch b, setze dann a ← b, b ← r und mache mit Schritt 1 weiter. Bem. 4.8: Speziell für R=Z und R = K[x] (K Körper) wird für den ggT(a,b) üblicherweise noch verlangt, dass er nichtnegativ bzw. normiert ist, wodurch er dann sogar eindeutig bestimmt ist. Eine Implementierung des obigen Algorithmus für R=Z in Derive könnte daher etwa so aussehen: Im Fall von R=Z gibt es überdies eine Binärvariante von Josef Stein, welche ganz ohne Divisionen auskommt. Sie macht sich dabei folgende einfach einzusehende Beziehungen zunutze: a) Sind a und b beide gerade, so gilt ggT(a,b) = 2 ggT(a/2,b/2). b) Ist genau eine der Zahlen a und b gerade, z.B. a, so gilt ggT(a,b) = ggT(a/2,b). c) Sind a und b beide ungerade, so gilt ggT(a,b) = ggT(a-b,b), wobei a − b <max(a,b). Dies führt dann auf folgenden 35 Algorithmus 4.9. (Binärvariante des Euklidischen Algorithmus) Für gegebene positive ganze Zahlen a und b, lässt sich der ggT(a,b) auf folgende Weise bestimmen. 1. [Extrahiere größte in a und b enthaltene Potenz von 2] Setze zunächst k ← 0 und wiederhole dann k ← k+1, a ← a/2, b ← b/2 solange, bis a und b nicht beide gerade sind. 2. [Initialisierung] Ist a ungerade, setze t ← −b und mach mit 4. weiter. Sonst setze t ← a. 3. [Halbiere t] Setze t ← t/2. 4. [Ist t gerade?] Ist t gerade, geh zurück zu 3. 5. [Verringere max(a,b)] Ist t >0, setze a ← t, sonst setze b ← -t. 6. [Subtraktion] Setze t ← a – b. Ist t ≠ 0, so geh zu 3., ansonsten bricht der Algorithmus mit dem Ergebnis ggT(a,b) = 2 k a ab. Und hier noch eine Implementierung in Derive, die durch eine kleine Modifikation zu Beginn sogar für beliebige ganze Zahlen a und b funktioniert: Bem. 4.10. Da bei jedem Durchlauf der Schritte 3.-6. mindestens eine der Zahlen a oder b zumindest halbiert wird, zeigt dies, dass die Anzahl der Schritte für a,b ≤ N jedenfalls durch einen Ausdruck O(lg N) (wobei lg N hier und im Folgenden den Logarithmus von N zur Basis 2 bezeichnet) abgeschätzt werden kann. Die tatsächliche Anzahl ist zwar i.allg. größer als beim ursprünglichen Euklidischen Algorithmus, durch werden anstelle der Divisionen nun simple Schiebeoperationen durchgeführt, deren Rechenaufwand nur linear mit der Stelligkeit der Zahlen wächst, sodass O((lg N) 2 ) dann eine Abschätzung für Gesamtaufwand angibt. Um die Anzahl der Divisionen für den ursprünglichen Euklidischen Algorithmus nach oben abschätzen zu können, wird interessanterweise die Fibonaccifolge ( Fn ) benötigt, welche am einfachsten rekursiv definiert werden kann durch F0 = 0, F1 = 1 , Fn = Fn −1 + Fn − 2 für n ≥ 2. 36 Es gibt allerdings auch eine explizite Formel für Fn , nämlich n n 1 ⎡⎛ 1 + 5 ⎞ ⎛ 1 − 5 ⎞ ⎤ ⎜ ⎟ ⎜ ⎟ ⎢ ⎥ , n=0,1,2,... − Fn = 5 ⎢⎜⎝ 2 ⎟⎠ ⎜⎝ 2 ⎟⎠ ⎥ ⎣ ⎦ Unter Verwendung von 1+ 5 Φ := 2 sieht man sofort, dass Φn Fn ≈ 5 eine ausgezeichnete Näherung für Fn darstellt (genauer: Fn ist für alle n der auf die nächste ganze Zahl gerundete Wert des rechtsstehenden Ausdrucks!) Für unsere Zwecke ist die Fibonaccifolge deswegen bedeutsam, weil der Euklidische Algorithmus für zwei benachbarte Glieder dieser Folge ein besonders viele Divisionen erfordert. Z.B. erhält man für F9 = 34 und F8 = 21 folgendes Divisionsschema 34 = 1 ⋅ 21 + 13 21 = 1 ⋅ 13 + 8 13 = 1 ⋅ 8 + 5 8=1⋅5+3 5=1⋅3+2 3 = 1 ⋅ 2 +1 2 = 2 ⋅ 1 +1 in dem alle Quotienten, mit Ausnahme des letzten gleich 1 ist (wäre übrigens auch der letzte Quotient gleich 1 bei Anwendung des Euklidischen Algorithmus auf a und b, so hätte man den uninteressanten Fall, dass a=b wäre !) Damit können wir nun zeigen, dass gilt Satz 4.11 (Lamé) Sind a und b ganze Zahlen mit a > b > 0 und erfordert die Anwendung des Euklidischen Algorithmus auf a und b genau n Divisionsschritte, so gilt a ≥ Fn + 2 und b ≥ Fn +1 . Daraus ergibt sich insbesondere die wichtige Folgerung 4.12: Die Anzahl der notwendigen Divisionen bei der Anwendung des Euklidischen Algorithmus auf zwei positive ganze Zahlen a,b ≤ N ist durch log Φ ( 5 N) − 2 ≈ 2.078 ln N + 1.672 nach oben beschränkt. ⎡ ⎤ Bem. 4.13: Wie komplizierte Rechnungen zeigen, ist demgegenüber die durchschnittliche Anzahl der notwendigen Divisionen unter obigen Voraussetzungen ungefähr 37 12 ln 2 ln N + 0.14 ≈ 0.843 ln N + 0.14 , π2 d.h. etwa 40% der Maximalanzahl und somit von etwa der derselben Größenordnung wie diese. Die Berechnung des ggT von Elementen a 1 ,..., a n ∈ Z mit n > 2 kann übrigens leicht mit Hilfe der rekursiven Beziehung ggT (a 1 ,..., a n ) = ggT (a 1 , ggT (a 2 ,..., a n )) und unter Ausnutzung der Tatsache, dass zwei zufällig ausgewählte natürliche Zahlen a und b mit der relativ großen Wahrscheinlichkeit von 6 / π 2 ≈ 0.60793 (s. Übg.) teilerfremd sind, auf den Fall n = 2 zurückgeführt werden. Dazu folgender Algorithmus 4.14: Unter Verwendung einer Subroutine für die Berechnung von ggT(a,b) lässt sich der ggT von positiven ganzen Zahlen a 1 ,..., a n mit n ≥ 1 dann in folgenden Schritten berechnen. 1. Setze d ← a n , k ← n-1. 2. Ist d ≠ 1 und k > 0, so setze d ← ggT( a k , d ) und k ← k – 1 und wiederhole diesen Schritt. Sonst setze d = ggT( a 1 ,..., a n ) . Mutatis mutandis lässt sich in ganz analoger Weise die Berechnung eines ggT( a 1 ,..., a n ) auf den Euklidischen Algorithmus zurückführen. Dieser liefert aber in etwas modifizierter Form noch weitere wichtige Informationen über den ggT. Dazu folgender Satz 4.15: In jedem Hauptidealring (R,+, ⋅ ) ist jeder größte gemeinsame Teiler d von Elementen a 1 ,..., a n ∈ R in der Form d = u 1a 1 + ... + u n a n mit u 1 ,..., u n ∈ R (Bézout Identität) darstellbar. Speziell in Euklidischen Ringen verwendet man dazu die einfache Beobachtung, dass unter Verwendung des Divisionsschemas in 4.6 a und b und induktiv dann alle Reste ri mit i=0,1,..,n, insbesondere also auch rn =ggT(a,b) sich als Linearkombination ri = x i a + y i b , i = 0,1,2,..,n, mit x i , y i ∈ R schreiben lassen. Dabei gilt wegen a = 1 ⋅ a + 0 ⋅ b und b = 0 ⋅ a + 1⋅ b speziell x 0 = 1, x 1 = 0 sowie y 0 = 0, y1 = 1 und aus rk = rk −2 − q k − 2 rk −1 ( k ≥ 2 ) folgt sofort die analoge Beziehung 38 x k = x k − 2 − q k − 2 x k −1 bzw. y k = y k − 2 − q k − 2 y k −1 ( k ≥ 2 ) Die drei Folgen ( rk ) ,( x k ) ,( y k ) sind sich also von der Art der Berechnung her sehr ähnlich und unterscheiden sich nur durch die verschieden Startwerte. Dies macht sich in eleganter Weise zunutze der Algorithmus 4.16 (Erweiterter Euklidischer Algorithmus) Sei (R,+, ⋅ ) ein Euklidischer Ring und a,b ∈ R . Nach Abbruch des nachstehend beschriebenen Algorithmus gilt dann r= ggT(a,b) = xa + yb. 1. [Initialisierung] Setze (r,x,y) ← (a,1,0) und ( r ′, x ′, y′) ←(b,0,1). 2. [Ist r ′ =0 ?] Ist r ′ =0, so bricht der Algorithmus ab. 3. [Bestimme den Quotienten q und die neuen Werte von (r,x,y) und ( r ′, x ′, y′) ] Setze q ← ⎣r / r ′⎦ und mache nach Berechnung von ( r ′′, x ′′, y ′′) ←(r,x,y) - q( r ′, x ′, y ′) , (r,x,y) ← ( r ′, x ′, y ′) , ( r ′, x ′, y ′) ←( r ′′, x ′′, y ′′) mit dem 2.Schritt weiter. (Beachte, dass ( r ′′, x ′′, y ′′) hier nur die vergleichsweise bescheidene Rolle eines Zwischenspeichers hat!) Bem 4.17: Obiger Algorithmus kann unter Aufhebung der Symmetrie in x und y noch dadurch etwas vereinfacht werden, indem man z.B. die 3.Komponente in allen Vektoren weglässt und das dann fehlende y zum Schluss aus der Gleichung r = xa + yb errechnet. Die Berechnung des Quotienten q in 3. ist natürlich abhängig vom betrachteten Euklidischen Ring. Für den Ring R=Z[i] der ganzen Gaußschen Zahlen etwa hätte man bei der Division von u durch v den zur komplexen Zahl u/v ∈ Q[i ] nächstgelegenen Gitterpunkt in Z[i] als Quotient zu nehmen. Von den im Normalfall 4 assoziierten ggT(a,b) wird standardmäßig jener im ersten Quadranten genommen. Die entsprechende Routine in Derive, welche speziell natürlich auch für ganze Zahlen a und b funktioniert, schaut so aus: 39 Was die Berechnung der Quotienten q betrifft, auf die es in 4.15 ganz entscheidend ankommt, gibt es noch einige algorithmische Feinheiten. Zunächst einmal sind diese q in der Regel überraschend klein, sodass zu ihrer Speicherung im Computer normalerweise Zahlen einfacher Genauigkeit ausreichen und eine Zurückführung der Division auf wiederholte Subtraktionen im Normalfall sinnvoll ist, was sich natürlich in einem erheblich geringeren Rechenaufwand niederschlägt. Genauer gilt der folgende Satz 4.18: Die Wahrscheinlichkeit P(q), dass ein Quotient im Euklidischen Algorithmus genau den Wert q hat, beträgt (q + 1) 2 P(q) = lg (q + 1) 2 − 1 Z.B. ist also P(1)=0.41504..., P(2)=0.16992..., P(3)=0.09311..., P(4)=0.05890... usw. Außerdem ist es unter Benutzung einer Idee von Lehmer oft möglich, bei Zahlen die in Mehrfachgenauigkeit gegeben sind, die Berechnung des Quotienten in Einfachgenauigkeit durchzuführen, indem man nur die führenden Stellen der involvierten Zahlen berücksichtigt. Nachfolgend wird diese Idee zunächst an einem einfachen Beispiel ausgeführt. Sei dazu a = 27182818 und b= 10000000 und wir nehmen an, dass zu ihrer Speicherung maschinenintern dezimale Worte der Länge 4 benutzt werden, d.h. a ist dann z.B. computerintern dargestellt als [2718,2818]. Sei nun a ′ = 2718, b ′ = 1001, a ′′ = 2719, b ′′ = 1000. Es gilt dann offensichtlich a ′ / b ′ < a / b < a ′′ / b ′′ und wenn wir den Euklidischen Algorithmus auf a ′ und b ′ bzw. a ′′ und b ′′ solange ausüben, bis sich die Quotienten erstmals unterscheiden, so müssen, wie man leicht einsieht, die gemeinsamen Quotienten auch die Quotienten für den Euklidischen Algorithmus angewandt auf die Originalzahlen a und b sein: a′ 2718 1001 716 285 146 139 b′ 1001 716 285 146 139 7 q′ 2 1 2 1 1 19 a ′′ 2719 1000 719 281 157 124 b ′′ 1000 719 281 157 124 33 q ′′ 2 1 2 1 1 3 In unserem Beispiel ist dies also für die ersten 5 Quotienten der Fall, während wir für den sechsten nur sagen können, dass er irgendwo zwischen 3 und 19 liegt. Unser Verfahren ist also hier „aus dem Tritt“ gekommen und wir müssen nun zwischendurch zu den „wirklichen“ a und b für die 6.Division zurückkehren und a ′ und b ′ bzw. a ′′ und b ′′ „nachjustieren“, um in dieser Weise weitermachen zu können. Seien a 0 und b 0 die Werte von a und b am Anfang, also hier a 0 = 27182818 bzw. b 0 = 10000000, so verändern sie sich bei den ersten 5 Divisionen in folgender Weise: 40 a b q a0 b0 a 0 -2 b 0 2 1 b0 a 0 -2 b 0 - a 0 +3 b 0 2 - a 0 +3 b 0 3 a 0 -8 b 0 3 a 0 -8 b 0 -4 a 0 +11 b 0 -4 a 0 +11 b 0 7 a 0 -19 b 0 1 1 ? In unserem Fall haben also die Variablen a und b am Beginn der 6.Division den Wert a= -4 a 0 +11 b 0 = 1268728 bzw. b = 7 a 0 -19 b 0 = 279726, sodass wir nun mit den „nachjustierten“ Werten a ′ = 126, b ′ = 28, a ′′ = 127, b ′′ = 27 in der gleichen Weise wie oben weitermachen können. In unserem Beispiel wurden also 5 Schritte des Original Euklidischen Algorithmus zu einem zusammengefasst, hätten wir aber z.B. Worte der Länge 10 genommen, so wären es 12 gewesen. Theoretische Resultate weisen daraufhin, dass das Wachstum linear ist, wobei die optimale Wortlänge vom verwendeten Computer abhängt. Algorithmus 4.19. (Lehmer’s Variante des Erweiterten Euklidischen Algorithmus) Seien a und b ganze Zahlen mit a ≥ b > 0, welche in Mehrfachgenauigkeit gegeben sind, d.h. sie seien in einer Basis M dargestellt, wobei M der Wortgröße des verwendeten Computertyps entspricht (z.B. M = 2 32 oder M = 2 64 ). Der nachfolgende Algorithmus berechnet dann ganze Zahlen d,x,y, sodass d = ggT(a,b) = xa+yb. Dabei werden die Hilfsvariablen â , b̂ ,A,B,C,D,T und q verwendet, welche alle in Einfachgenauigkeit gegeben sind (d.h. sie sind < M), sowie die Variablen t,r,u in Mehrfachgenauigkeit. 1. [Initialisierung] Setze A ← 1, B ← 0, C ←0, D ←1, x ← 1, u ← 0. 2. [Fertig?] Ist b < M, also in Einfachgenauigkeit darstellbar, so berechne d,x,y mit Hilfe der „Standardversion“ 4.16 des Erweiterten Euklidischen Algorithmus (in der Variante nach 4.17, wobei hier x und x´ den momentanen Werten von x und u entsprechen) und breche ab. Andernfalls seien â und b̂ die führenden „Ziffern“ in der obigen M-adischen Darstellung von a bzw. b. (Ist die M-adische Darstellung von b kürzer als die von a, was selten der Fall ist, so setzt man natürlich b̂ ← 0.) 3. [Teste Quotienten] Ist b̂ +C = 0 oder b̂ + D = 0 so mach mit 5. weiter, andernfalls setze q ← (â + A) /( b̂ + C) . Ist q ≠ (â + B) /( b̂ + D) , so setze ebenfalls mit 5. fort. ⎣ ⎦ ⎣ ⎦ 4. [Euklidischer Schritt] Setze T ← A – qC, A ← C, C ← T, T ← B – q D, B ← D, D ← T, T ← â - q b̂ , â ← b̂ , b̂ ← T und geh zu Schritt 3. (Beachte, dass alle diese Operationen in Einfachgenauigkeit durchgeführt werden!) 5. [Schritt mit Mehrfachgenauigkeit] Ist B = 0, so setze q ← ⎣a / b ⎦ und t ← a mod b, sowie a ← b, b ← t, t ← x – q u, x ← u, u ← t, andernfalls setze t ← Aa, t ← t+Bb, r ← Ca, r ← r+Db, a ← t, b ← r, t ← Ax, t ← t+Bu, r ← Cx, r ← r+Du, x ← t, u ← r und gehe zu 2. 41 Bem. 4.20: Wie man mit Hilfe von 4.18 zeigen kann, tritt die Möglichkeit B = 0 in Schritt 5 nur mit einer Wahrscheinlichkeit von lg(1+1/M), also in der Praxis höchst selten, auf. Dies ist insofern bedeutsam, als genau in diesem Fall eine „echte“ Division, d.h. Division mit Mehrfachgenauigkeit, durchgeführt werden muss. Eine der wichtigsten Anwendungen des Euklidischen Algorithmus betrifft die Lösung von sog. linearen Kongruenzen. Dazu folgender Satz 4.21: Die lineare Kongruenz ax ≡ b mod m ist für beliebige a,b ∈ Z und m ∈ N genau dann lösbar, wenn für d = ggT(a,b) gilt d | m. Ist dies erfüllt, so sind sämtliche mod m inkongruenten Lösungen gegeben durch die Zahlen bu m , k=0,1,...,d-1 +k d d wobei u einer Darstellung d = ua +vb entnommen ist, die man etwa mit Hilfe des Erweiterten Euklidischen Algorithmus erhalten kann. Bem. 4.22: Aus 4.21 folgt insbesondere, dass genau dann, wenn ggT(a,m)=1 ist, die Kongruenz ax ≡ 1 mod m lösbar ist und daher die Restklasse a mod m im Restklassenring Z m ein multiplikatives Inverses besitzt. Insbesondere besteht also die Einheitengruppe E( Z m ) von Z m somit aus genau jenen Restklassen a mod m mit 1 ≤ a ≤ m und ggT(a,m)=1. Die Anzahl dieser Restklassen wird mit ϕ(m) bezeichnet und ϕ die Eulersche ϕ-Funktion genannt. Eine sehr wichtige Anwendung von 4.21 (und damit indirekt auch des Euklidischen Algorithmus) ist Satz 4.23: (Chinesischer Restsatz) Seien m1 , m 2 ,..., m r ∈ N * paarweise teilerfremd. Für beliebige a 1 , a 2 ,..., a r ∈ Z ist dann das System von Kongruenzen x ≡ a 1 mod m1 , x ≡ a 2 mod m 2 , ... x ≡ a r mod m r stets lösbar. Setzt man M = m1 ...m r und M i = M / m i , i=1,...,r, so ist die mod M eindeutig bestimmte Lösung gegeben durch r x = ∑ aiMixi i =1 wobei für die x i gilt M i x i ≡ 1 mod m i , i=1,...,r. Bem 4.24: 4.23 kann auch so gedeutet werden, dass unter den gemachten Voraussetzungen die Abbildung ρ: Z m1m2 ...mr → Z m1 xZ m2 x ... x Z mr , 42 welche a mod m1 m 2 ... m r auf ( a mod m1 , a mod m 2 ,..., a mod m r ) abbildet, eine Bijektion (algebraisch sogar ein Isomorphismus!) ist. Da aus 2.23 sofort folgt, dass (a , m1 m 2 ... m r ) = 1 ⇔ (a , m1 ) = (a , m 2 ) = ... = (a , m r ) = 1 , so ergibt die Einschränkung von ρ auf E (Z m1m 2 ...m r ) eine Bijektion bzw. sogar einen Isomorphismus von E( Z m1m 2 ...m r ) auf E(Z m1 ) xE (Z m 2 ) x...xE (Z m r ) . Insbesondere folgt daraus auch sofort die Multiplikativität der Eulerschen ϕ-Funktion, d.h. für paarweise teilerfremde natürliche Zahlen m1 , m 2 ,..., m r gilt ϕ(m1 m 2 ...m r ) = ϕ(m1 )ϕ(m 2 )...ϕ(m r ) . Mit Hilfe dieser Eigenschaft lässt sich ϕ(N) für jede natürliche Zahl N auch leicht explizit berechnen sofern man die Primfaktorzerlegung von N kennt (was allerdings in der Praxis eine Hürde darstellen kann!). Es gilt nämlich Satz 4.25: Ist N = p1e1 ...p er r die Primfaktorzerlegung der natürlichen Zahl N, so gilt ϕ( N) = N(1 − 1 / p1 )...(1 − 1 / p r ). In vielen Anwendungen benötigt man Satz 4.26 (Euler-Fermat) Ist m eine natürliche Zahl und a ∈ Z zu m teilerfremd, so gilt a ϕ( m ) ≡ 1 mod m . Bem. 4.27: Der wichtige Spezialfall wo der Modul eine Primzahl p und a nicht durch p teilbar ist, wonach dann wegen ϕ(p) = p-1 gilt a p −1 ≡ 1 mod p, wird in der Literatur auch als „Kleiner Fermatscher Satz“ bezeichnet. 4.26 lässt sich noch etwas genauer formulieren. Es gilt nämlich Satz und Def. 4.28: Sei m = p1e1 ...p er r die Primfaktorzerlegung von m (mit p1 =2, falls m gerade) und λ(m) definiert durch ⎧kgV(ϕ(p1e1 ),..., ϕ(p er r )), falls m ≠ 0 mod 8 λ ( m) = ⎨ e1 er ⎩ kgV(ϕ(p1 ) / 2,..., ϕ(p r )), falls m ≡ 0 mod 8 Es gilt dann ebenfalls a λ ( m ) ≡ 1 mod m für alle zu m teilerfremden ganzen Zahlen a, wobei aber jetzt λ(m) der kleinstmögliche Exponent mit dieser Eigenschaft ist. Die so definierte Funktion λ wird auch Carmichaelfunktion genannt. Eine wichtige Anwendung von 4.28 ist das sog. RSA-Verfahren zur Verschlüsselung von Nachrichten, welches nach seinen Erfindern R.Rivest, A.Shamir und L.Adleman (1977) benannt ist und beim Nachrichtenaustausch zwischen Banken, aber auch im Internet eine große Rolle spielt. Es kann in seinen Grundzügen so beschrieben werden: Jeder Benutzer (oder vielmehr sein Computer bzw. eine zentrale Schlüsselvergabestelle) berechnet dazu zwei große Primzahlen p,q und ihr Produkt n =pq. Ferner werden benötigt zwei natürliche Zahlen e und d,welche kleiner als λ(n)= kgV(p - 1,q - 1) sind und wo d nicht sehr viel weniger Stellen als n haben soll, sodass 43 ed ≡ 1 mod kgV( p − 1, q − 1) In der Praxis wählt man dazu ein e, welches zu kgV(p - 1,q - 1) teilerfremd ist und bestimmt dazu d mit Hilfe des Erweiterten Euklidischen Algorithmus gemäß 2.21. Das Paar (n,e) bildet dann den öffentlichen Schlüssel (public key) des Benutzers, während die Zahlen p,q und d von diesem geheimgehalten werden. Will ihm nun jemand eine Nachricht zukommen lassen, so muss er sie zunächst als Dezimalzahl M < n kodieren (bei längeren Nachrichten kann eine Unterteilung in mehrere dezimale Blöcke notwendig sein) und das Chiffrat C berechnen mittels C = M e mod n (Hier und im Folgenden ist immer der kleinste nichtnegative Rest mod n zu nehmen.) Dieser erhält dann M zurück, indem er seinerseits C d mod n bildet, d.h. es gilt M = C d mod n Das RSA-Verfahren basiert mathematisch gesehen auf folgender Variante von 2.28, die aber nur unter der Voraussetzung der Quadratfreiheit von m gilt: Satz 4.29: Ist m quadratfrei, so gilt dann a λ ( m ) +1 ≡ a mod m sogar für beliebige (d.h. also nicht nur für zu m teilerfremde) ganze Zahlen a. Bem. 4.30: Die Sicherheit des RSA-Verfahrens basiert ganz entscheidend auf der Gültigkeit von zwei plausiblen, aber bisher unbewiesenen Annahmen: • Bei geeigneter Wahl der Parameter führen alle Wege zur Entschlüsselung über die Faktorisierung von n in ihre Primfaktoren p und q, d.h. diese kann nicht irgendwie umgangen werden. • Ist n genügend groß (dzt. sollte n mindestens 512 Bits - besser aber 1024 Bits - haben) und sind die Primfaktoren p und q geeignet gewählt, so ist die Faktorisierung von n = pq mit vertretbaren Aufwand nicht möglich. (Insbesondere kennt man z.Z. keinen Polynomialzeitalgorithmus für das Faktorisierungsproblem.) Wichtig für die Realisierbarkeit des RSA-Verfahrens, dass die Chiffrier- und Dechiffrierabbildungen, wofür in beiden Fällen modulare Potenzen mod n zu bilden sind und die Exponenten insbesondere bei der Dechiffrierung sehr groß sein können, algorithmisch leicht zu handhaben sind. Dies geschieht mit der der sog. „Square and Multiply“-Methode, welche zur Bildung von Potenzen in beliebigen Halbgruppen mit Einselement verwendet werden kann und eine der ältesten Algorithmen darstellt. ( Er wurde de facto schon von den alten Ägyptern zur Bildung von Potenzen in (N, + ) verwendet, was dann also der gewöhnlichen Produktbildung entspricht!) Der einfache Grundgedanke ist dabei der, dass man ausgehend von der Binärdarstellung eines Exponenten k, also r k = ∑ k i 2 i mit k i ∈ {0,1} i =0 die Potenz x k leicht dadurch bilden kann, indem man genau jene sukzessiv bestimmten Potenzen 44 x 2 = (...(( x 2 ) 2 ) 2 ...) 2 (d.h. x i-mal quadriert) i in der betrachteten Halbgruppe „aufmultipliziert“, für welche das zugehörige Bit k i in der Binärdarstellung von k gleich 1 ist. Dies führt zu folgendem Algorithmus 4.31: („Square and Multiply“) Sei (H, ⋅ , e) ein Halbgruppe mit e als Einselement e. Für beliebiges k ≥ 0 und x ∈ H kann dann die Potenz x k in H in folgender Weise bestimmt werden: 1. [Initialisierung] Setze K ← k, X ← x, Y ← e. 2. [K = 0 ?] Ist K = 0, so bricht der Algorithmus mit Y als Antwort ab. 3. [Ersetze Y durch XY, falls K ungerade] Ist K ungerade, so setze Y ← XY. 4. [Quadriere X in H] Setze X ← X 2 . 5. [„Halbiere“ K] Setze K ← ⎣K / 2⎦ und fahre mit Schritt 2 fort. Bem. 4.32: Wie man sofort sieht, werden höchstens 2 ⎣lg k ⎦ Multiplikationen (im Mittel sogar nur 1.5 ⎣lg k ⎦ ) in der betrachteten Halbgruppe benötigt. Bei der im RSA-Verfahren benötigten Bildung von Potenzen ist natürlich H = Z n und die Verknüpfung die modulare Multiplikation mod n. Da speziell e im RSA-Verfahren abgesehen von der Bedingung ggT(e,(p-1)(q-1)) = 1 frei wählbar ist, wird man darauf achten, dass möglichst wenig Einsen in der Binärdarstellung von e vorkommen, weshalb man z.B. für e oft 65537 (= 216 + 1 ) nimmt. (Ein zu kleines e wie z.B. e = 3 würde gewisse Angriffsmöglichkeiten bieten!) Speziell für die Dechiffrierung, wo C d mod n für das Chiffrat C berechnet werden muss, wobei d ≈ n gilt, ist der Aufwand in der Regel um einiges größer. Da aber die Anzahl der Bitoperationen für eine modulare Multiplikation mod n jedenfalls nicht größer als O( (ln n ) 2 ) ist, ist deren Gesamtzahl durch den Ausdruck O( (ln n ) 3 ) gegeben. Insbesondere stehen also sowohl für die Chiffrierung als auch die Dechiffrierung Polynomialzeitalgorithmen zur Verfügung, womit RSA eigentlich erst praktikabel wird. Für das modulare Potenzieren mit einem zusammengesetzten Modul wird oft der Chinesische Restsatz (und damit indirekt wieder der Euklidische Algorithmus!) verwendet. Es gilt nämlich Satz 4.33: Seien p und q zwei verschiedene Primzahlen und für ein d mit 0 ≤ d < pq seien d p := d mod (p − 1) und d q := d mod (q − 1) . Für jedes x∈N ist dann die Potenz x d mod pq gegeben durch den Ausdruck a ( x p mod p) + b( x q mod q ) mod pq, d d wobei die Zahlen a und b mit Hilfe des Chinesischen Restsatzes vorweg so bestimmt werden, dass gilt a ≡ 1 mod p, a ≡ 0 mod q bzw. b ≡ 0 mod p, b ≡ 1 mod q . 45 Bem. 4.34: Speziell beim RSA-Verfahren, wo ja p ≈ n und q ≈ n und d ≈ n gilt, liegen ideale Voraussetzungen für die Anwendung dieses Satzes vor, wodurch die Berechnung von x d mod n etwa um den Faktor 3-4 beschleunigt kann. Allerdings birgt dies hier auch gewisse Gefahren in sich. Ist nämlich genau eine der Potenzen d d x p mod p bzw. x q mod q falsch berechnet worden (und so ein Rechenfehler kann auch manchmal durch äußere Einflüsse wie z.B. durch Anwendung von Röntgenstrahlung auf den RSA-Chip provoziert werden!) und ist das Ergebnis y bekannt (z.B. dann, wenn RSA für digitale Unterschriften verwendet wird), so ist dann ggT(y- ~y ,n), wenn hier ~y die fehlerhafte Potenz x d mod n bezeichnet, eine der geheimen Primzahlen p oder q ! Nachfolgend wollen wir noch auf das Faktorisierungsproblem für ein vorgegebenes N eingehen, von dem ja wie schon erwähnt die Sicherheit von RSA und auch anderer Verfahren der Kryptographie entscheidend abhängt. Eine gute Vorstellung von dessen Schwierigkeit bekommt man, indem man sich die wichtigsten Algorithmen zu diesem Problem ansieht und deren Laufzeitverhalten untersucht, was wir also nun machen wollen. Zuallererst wird man dazu wohl mit einer Probedivision (trial division) von N durch alle Primzahlen p bis zu eine gewissen Schranke B (z.B. B=1000) beginnen. Selbst wenn auf diese Weise eine vollständige Faktorisierung von N nur selten gelingt - dafür müsste B = N sein, was für großes N sicher nicht in Frage kommt - so kann man dadurch doch oft kleine Faktoren von N abspalten und auf den verbleibenden dann kleiner gewordenen Restfaktor eine der gängigen Faktorisierungsmethoden anwenden. Sehr einfach und trotzdem bei der Auffindung nicht allzu großer Faktoren recht effizient, ist dabei die sog. ρ-Methode von Pollard-Brent, weshalb sie auch in vielen CAS an erster Stelle verwendet wird. Ihr liegt die folgende einfache Idee zugrunde: Ist N die zu faktorisierende Zahl und f(x) ein möglichst einfaches Polynom über Z mit guten Zufallseigenschaften (in der Praxis haben sich Polynome der Form x 2 + a mit a ∉ {0,−2} gut bewährt), so bildet man die Folge x 0 , x 1 , x 2 ,... , welche zu einem vorgegebenen Startwert x 0 rekursiv definiert ist durch x i +1 = f ( x i ) mod N, i = 0,1,2,... Ist nun p ein (zunächst natürlich unbekannter) Primfaktor von N und betrachtet man diese Folge (rein gedanklich) mod p , so werden sehr bald einmal zwei Folgenglieder mod p gleich sein. Theoretisch könnte dies auch erst nach p+1 Iterationen sein, in der Praxis ist dies aber schon nach O( p) Iterationen der Fall. Dieses Phänomen, welches nach einer bekannten Einkleidung auch als “Geburtstagsparadoxon” bezeichnet wird, ist eine unmittelbare Folge von Satz 4.35: Seien k und m positive ganze Zahlen, wobei k << m, d.h. m “groß” im Vergleich zu k sei. Für die Wahrscheinlichkeit Wk ,m , dass k zufällig ausgewählte ganze Zahlen alle in verschiedenen Restklassen mod m liegen gilt dann 1 2 k −1 ) ≈ e −k ( k −1) /( 2 m ) , Wk ,m = (1 − )(1 − )...(1 − m m m 46 d.h. Wk ,m wird mit wachsendem k sehr schnell klein. (Z.B. gilt Wk ,m ≤ 0.5 bereits ab etwa k ≈ 2 m ln 2 ≈ 12 . m .) Setzt man noch W0,m :=1, so ist insbesondere bei ∞ ∑W k =0 k ,m = 1 + 1 + (1 − πm 1 1 2 k −1 ) + ... ≈ ) + ... + (1 − )(1 − ) L (1 − m m m m 2 zufällig ausgewählten Zahlen zum ersten Mal eine Koinzidenz mod m zu erwarten. Für uns bedeutet dies, dass bereits für relativ kleine Indizes i,j mit i > j gilt x i ≡ x j mod p und folglich ggT ( x i − x j , N ) ≠ 1, womit man durch Bildung von ggT ( x i − x j , N) - außer in dem sehr unwahrscheinlichen Fall, dass auch x i ≡ x j mod N gilt - einen nichttrivialen Teiler von n erhält. Es wäre nun allerdings sehr aufwendig, würde man tatsächlich alle Folgenglieder x 0 , x 1 , x 2 , ... in Evidenz halten und für alle Paare (i,j) mit i >j das Erfülltsein obiger Bedingung ggT ( x i − x j , N ) ≠ 1 überprüfen. Dies ist aber auch gar nicht notwendig. So machte es sich schon Pollard in seiner ursprünglicher Version der ρ-Methode zunutze, dass es nach Floyd sogar ein derartiges Paar (i,j) mit i = 2j geben muss. Damit bräuchte man also nur parallel zur Folge der x i eine weitere Folge y i , i = 0,1,2,... mit gleichem Startwert y 0 = x 0 , aber der doppelt so schnell laufenden Rekursion y i +1 = f ( f ( y i )) , i = 0,1,2,... berechnen, womit also y i = x 2 i gelten würde, und man bräuchte dann jeweils nur für jedes i die Bedingung ggT ( y i − x i , N ) ≠ 1 überprüfen. Man beachte, dass obiges Verfahren allerdings auch versagen kann, nämlich dann, wenn nicht nur x i ≡ x j mod p , sondern sogar x i ≡ x j mod N gilt, was allerdings sehr unwahrscheinlich ist und in welchem Falle man dann ggT ( x i − x j , N) = N erhält. In diesem Fall empfiehlt es sich, das Polynom f(x) gegen ein anderes auszutauschen. (Man könnte auch einen anderen Startwert x 0 versuchen, doch bringt dies in der Regel nichts.) In nachfolgendem Algorithmus verwenden wir übrigens als erstes Polynom f(x) = x 2 + 1 , das sich in der Praxis recht gut bewährt hat. Man beachte jedoch, dass für Zahlen spezieller Bauart u.U. andere Polynome günstiger sein können. Dies gilt insbesondere für m Mersennesche Zahlen M p = 2 p − 1 ( p ∈ P) und Fermatsche Zahlen Fm = 2 2 + 1 (m ∈ N), für die Polynome der Bauart x e + 1 mit e = p bzw. e = 2 m + 2 deutlich besser sind. Algorithmus 4.36 (Original ρ-Methode nach Pollard): Der nachfolgende Algorithmus ergibt in der Regel einen nichttrivialen Teiler einer zusammengesetzten natürlichen Zahl N (in seltenen Fällen allerdings den Teiler N) 1. [Initialisierung] Setze z.B. x ← 2, y ← 2. 2. [Iterierte Anwendung von f] Setze x ← x 2 + 1 mod N, y ← y 2 + 1 mod N, y ← y 2 + 1 mod N 47 3. [Faktor gefunden?] Setze d ← ggT(x-y,N). Ist d > 1, so beende den Algorithmus und gib d als Faktor von N aus, sonst mach mit 2. weiter. Man kann aber, worauf R.Brent als erster hingewiesen hat, auch nur mit der Folge x 0 , x 1 , x 2 , ... allein auskommen, wenn man obige Überprüfung nur für jene Paare (i,j) vornimmt, wo j von der speziellen Form j = 2 k − 1 ist und i nur jeweils die Werte i = j + 2 k −1 + r , r = 1,..., 2 k −1 , durchläuft. Dies reicht bereits aus um eine Periode zu finden und ergibt gegenüber der ursprünglichen ρ-Pollardschen Methode eine Beschleunigung um immerhin ca. 25%. Algorithmus 4.37 (ρ-Methode nach Brent) Der nachfolgende Algorithmus ergibt in der Regel einen nichttrivialen Teiler einer zusammengesetzten natürlichen Zahl n (in seltenen Fällen allerdings den Teiler N) 1. [Initialisierung] Setze z.B. x ← 5, y ← 2, sowie k ← 1, l ← 1. 2. [Faktor gefunden?] Setze d ← ggT(x-y,N). Ist d > 1, so beende Algorithmus und gib d als Faktor von N aus. 3. [Iterierte Anwendung von f] Setze k ← k – 1. Ist danach k = 0, so setze außerdem y ← x, l ← 2l, k ← l. Anschließend setze x ← x 2 + 1 mod N und fahre mit 2. fort. Wie oben ausgeführt, sind für beide Algorithmen O( p ) Iterationen zu erwarten, wobei p der kleinste Primfaktor von N ist. Im ungünstigsten Fall, wo N das Produkt von etwa gleich großen Primzahlen ist, wären dies O( 4 n ) Iterationen. Der Aufwand pro Iteration kann wiederum durch O((ln N) 2 ) abgeschätzt werden kann, da ja im wesentlichen nur eine feste Anzahl von Multiplikationen von Zahlen der gleichen Größenordnung wie N vorgenommen werden muss. Der Gesamtaufwand wäre demnach O( p (ln N) 2 ) und hängt damit sehr stark von der Größe des kleinsten Primfaktors p von N ab. Der ungünstigste Fall bei Anwendung dieser Methode liegt dann vor, wenn n das Produkt etwa zwei gleich großer Primzahlen ist, womit dann für unser p gilt p ≈ N , d.h. in dieser Fall beträgt der Aufwand O( 4 N (ln N) 2 ) , was aber immer noch erheblich günstiger ist als der entsprechende Aufwand bei der Probedivision in diesem Fall, nämlich O( N (ln N) 2 ) . In dem geschilderten Fall, dass nämlich N das Produkt von etwa zwei gleich großen Primzahlen ist, ist übrigens eine andere Faktorisierungsmethode sehr aussichtsreich, die bereits auf Fermat zurückgeht. Hierbei versucht man die zu faktorisierende Zahl N in der Form N = u 2 − v 2 mit natürlichen Zahlen u und v darzustellen, woraus dann in trivialer Weise die Faktorisierung N = (u + v)(u - v) folgt. Das erste in Frage kommende u ist natürlich u = N und falls p und q die gleiche Stellenanzahl haben und sich in der der ersten Hälfte der Stellen nicht unterscheiden, so klappt es auch bereits mit diesem u und der ⎡ ⎤ 48 dann ganzen Zahl v := u 2 − N . Ansonsten müsste man u laufend um 1 erhöhen und (ev. bis zu einer gewissen Schranke für u) jeweils überprüfen, ob das so definierte v wirklich ganz ist. (Die Erfolgschancen sind dann allerdings für großes N schon sehr gering!) Etwas formaler schaut dies dann so aus: Algorithmus 4.38: Der nachfolgende Algorithmus liefert für jedes zusammengesetzte N eine nichttriviale Faktorisierung. 1. Setze u ← ⎡ N ⎤ und v ← u 2 −N. 2. Im Falle, dass v ganz ist, ist dann N= (u − v)(u + v) eine nichttriviale Faktorisierung von N, andernfalls setze v ← v + 2u + 1 , u ← u + 1 und wiederhole Schritt 2. Dazu auch noch ein Derive-Programm mit einem Faktorisierungsbeispiel für ein 243-stelliges N, das nebenbei bemerkt in der österreichischen Kriminalgeschichte eine gewisse Rolle gespielt hat. Nehmen wir jedoch einen Fall, der typischer ist, als der obige, wo nämlich N=ab mit a ≈ N 2 / 3 und b ≈ N 1 / 3 jene Faktorsierung ist, bei der sich die Faktoren a und b “am nächsten kommen”, so wären dann dafür etwa 1 N (N 2 / 3 − N1/ 2 ) ≈ N 4 / 3 = 1 N 2 / 3 (a + ) − N 1 / 2 = a 2 2N 2 / 3 2N 2 / 3 2 2 Iterationen notwendig und damit sogar mehr als bei bei einer simplen Probedivision durch alle Teiler bis b ≈ N 1 / 3 ! Die Fermatsche Faktorisierungsmethode lässt sich sich jedoch in folgender Weise verallgemeinern: Kann man Zahlen ganze Zahlen a,b so finden, dass gilt a 2 ≡ b 2 mod N und a ≡ ± b mod N , so folgt aus N (a + b)(a − b) sofort, dass dann ggT (a + b, N) und ggT (a − b, N) nichttriviale Teiler von N sind. Viele gerade der besten Faktorisierungsalgorithmen basieren auf dieser einfachen Überlegung von Legendre. 49 Auch die nachstehend beschrieben sog. Faktorbasismethode beruht darauf. Unter einer Faktorbasis versteht man dabei eine Menge B = {p 1 , p 2 ,K , p m } , wobei, abgesehen von der eventuellen Ausnahme p 1 = −1 , die p i verschiedene Primzahlen sind, welche nach aufsteigender Größe geordnet seien. Ferner heißt eine ganze Zahl b eine B-Zahl bezüglich eines gegebenen N, wenn der kleinste Absolutrest a = b 2 mod N (d.h. − N / 2 < a ≤ N / 2) als ein Produkt von (nicht notwendig verschiedenen) Zahlen aus B dargestellt werden kann. Eine ganze Zahl b, für deren sämtliche Primfaktoren p gilt p ≤ S für eine natürliche Zahl S, wird auch S-glatt (S-smooth) genannt. Danach ist jede B-Zahl insbesondere auch P-glatt für die größte in B vorkommende Primzahl P. (Die Umkehrung gilt natürlich i. allg. nur dann, wenn B alle Primzahlen ≤ P enthält.) Wie eine einfache heuristische Überlegung zeigt, sollte für eine Zahl N ≤ x die Wahrscheinlichkeit y-glatt zu sein, etwa u − u mit u = ln x ln y betragen. Mit diesen Bezeichnungen gilt nun der grundlegende Satz 4.39: Seien b 1 , b 2 ,K , b n bezüglich der B- Zahlen, für welche also gilt Faktorbasis B= {p 1 , p 2 ,K , p m } b i 2 ≡ p 1 ei1 p 2 ei 2 K p m eim mod N , i = 1,K , n. Ist für sie die Summe der Vektoren e i : = ( e i1 mod 2, e i 2 mod 2,K , e im mod 2) , i=1,2,…,n, gleich 0 im Vektorraum Z 2 m , so sind dann 1 n ∑ e , j = 1,K, m 2 i =1 ij Lösungen der Kongruenz x 2 ≡ y 2 mod N , welche für ein zusammengesetztes N mit einer Wahrscheinlichkeit von höchstens 50% trivial sind. (Genauer beträgt diese Wahrscheinlichkeit 1 / 2 r −1 , wobei r die Anzahl der verschiedenen Primfaktoren von N bezeichnet.) x = b 1 b 2 K b n , y = p 1 f1 p 2 f2 K p m f m mit f j = In der Praxis läuft die Auffindung von n linear abhängigen Vektoren der im Satz beschriebenen Art auf eine Gaußelimination über dem Körper Z 2 hinaus. Wir haben es dabei grundsätzlich mit folgendem Optimierungsproblem zu tun: Wie groß ist die Faktorbasis B zu wählen, damit der Gesamtaufwand - also der Aufwand für die Auffindung von B-glatten Zahlen, der natürlich mit zunehmender Größe der Faktorbasis sinkt und der Aufwand für die sich daran anschließende Gaußelimination, deren Aufwand wieder mit zunehmender Größe der Faktorbasis steigt - möglichst klein wird? Eine heuristische Überlegung zeigt, dass für die größte Primzahl P in der Faktorbasis B gelten sollte P ≈ exp(c ln N ln ln N ) mit einer Konstanten c, welche vom Verfahren zur Generierung von B-Zahlen abhängt. Verwendet man dabei zu ihrer Generierung einfach das Polynom Q(x):= x 2 − N für nahe 50 bei N liegende Werte von x, wie dies Kraitchik als erster vorgeschlagen hat, oder die Kettenbruchmethode CFRAC von Lehmer-Powers (implementiert von MorrisonBrillhart), welche die Kettenbruchentwicklungen von kN für kleine k∈N* verwendet, so ist c= 1 / (2 2 ) . Bei der wichtigsten derartigen Methode, dem sog. Quadratischen Sieb (QS) wird, bei der wie der Name schon sagt, zur Generierung der B-Zahlen eine ausgeklügelte Siebmethode verwendet und in diesem Fall ist c=1. Für sie kann der Gesamtrechenaufwand durch den Ausdruck O(exp( (1 + o(1)) ln N ln ln N )) beschrieben werden. Zur Generierung von B-Zahlen werden dabei gewöhnlich mehrere Polynome der allgemeinen Form Ax 2 + Bx + C verwendet, weshalb diese Methode dann auch MPQS (=Multiple Polynomial Quadratic Sieve) genannt wird. Um diese Methode wenigstens für den einfachsten Fall, wo man Q(x):= ( x + m) 2 − N mit ⎣ ⎦ m = N zur Generierung der B-Zahlen verwendet, beschreiben zu können, müssen wir etwas auf die Theorie der quadratischen Reste eingehen, was aber auch für sich genommen ein wichtiges Thema ist, welches in den Anwendungen (vor allem auch wieder in der Kryptographie) eine große Rolle spielt. Für jede ungerade Primzahl p und jede nicht durch p teilbare ganze Zahl a ist das sog. Legendresymbol (a/p) definiert durch ⎧ +1, wenn x 2 ≡ a mod p lösbar ist ( a / p) = ⎨ 2 ⎩−1, wenn x ≡ a mod p unlösbar ist Im ersten Fall sagt man auch, “a ist quadratischer Rest mod p”, im zweiten “a ist quadratischer Nichtrest mod p.” Ferner setzen wir (a/p) = 0, falls p | a. Ist allgemeiner a eine beliebige ganze Zahl und b eine ungerade natürliche Zahl, welche eine Darstellung der Form b = b = p 1 p 2 ... p r (r ≥ 0) als Produkt (nicht notwendig verschiedener) Primzahlen besitzt, so wird das Jacobisymbol (a/b) erklärt durch (a / b) = (a / p 1 )(a / p 2 )...(a / p r ) , wobei die (a / p i ) , i = 1,2,...,r, Legendresymbole sind. Für die Berechnung des Legendresymbols (a/p) könnte man z.B. das sog. „Eulersche Kriterium“ verwenden, nämlich Satz 4.40: Seien p ∈ P\{2} und a ∈ Z beliebig. Dann gilt: (a/p) ≡ a ( p −1)/ 2 mod p. Bem. 4.41: Diesen Satz könnte man auch für einen probabilistischen Primzahltest verwenden, indem man überprüft, ob eine vorgegebene ungerade natürliche Zahl N für ein ganze Zahl a mit 0<a<N die Bedingung (a/N) ≡ a ( N −1) / 2 mod N erfüllt, was dann auf den sog. Solovay-Strassen-Test führt. Gegenüber dem gewöhnlichen Fermattest, nämlich 51 a N −1 ≡ 1 mod N stellt dies offensichtlich eine Verschärfung dar. Für ein zufällig ausgewähltes a ist die Wahrscheinlichkeit, dass ein zusammengesetztes N diesen Test besteht, höchstens ½. Allerdings wurde der Solovay-Strassen-Test in der Praxis durch den bereits besprochenen Rabin-Miller-Test verdrängt, welcher sowohl stärker als auch schneller ist. Im Folgenden seien die wichtigsten Eigenschaften eines Jacobisymbols (a/b) aufgelistet. Satz 4.42: Seien a , a 1 , a 2 , b, b1 , b 2 ∈ Z und b, b1 , b 2 überdies positiv und ungerade. Dann gilt: 1. a 1 ≡ a 2 mod b ⇒ (a 1 / b) = (a 2 / b) , d.h. (a/b) agiert bei festgehaltenem Nenner b als Funktion von a auf den Klassen mod b. 2. (a 1a 2 / b) = (a 1 / b)(a 2 / b) (Multiplikativität im Zähler) 3. (a / b 1 b 2 ) = (a / b 1 )(a / b 2 ) (Multiplikativität im Nenner) 4. (b1 / b 2 )(b 2 / b1 ) = (−1) ( b1 −1) / 2⋅( b 2 −1) / 2 , d.h. (b1 / b 2 ) und (b 2 / b1 ) haben dasselbe Vorzeichen, wenn entweder b1 ≡ 1 mod 4 oder b 2 ≡ 1 mod 4 ist, und andernfalls, d.h. also für b1 ≡ b 2 ≡ 3 mod 4 , verschiedenes Vorzeichen. (Quadratisches Reziprozitätsgesetz). 5. ( −1 / b) = ( −1) ( b −1)/ 2 , d.h. (-1/b) =1, wenn b ≡ 1 mod 4 , und (-1/b) = -1 , wenn b ≡ 3 mod 4 (1.Ergänzungssatz) 6. (2 / b) = ( −1) ( b −1)/8 , d.h. (2/b) =1, wenn b ≡ ±1 mod 8 , und (2/b) = -1 , wenn b ≡ ±3 mod 8 (2.Ergänzungssatz). 2 Damit können wir insbesondere eine sehr schnelle Methode zur Berechnung von (a/b) angeben, nämlich Algorithmus 4.43: Zur Berechnung des Jacobisymbols (a/b), wobei a , b ∈ Z und b überdies positiv und ungerade vorausgesetzt sei, sind folgende Schritte durchzuführen. 1. [Sind a und b teilerfremd?] Ist ggT(a,b) ≠ 1, so beende Algorithmus mit der Ausgabe von (a/b) = 0. 2. [Initialisierung des Vorzeichens] Setze s ← 1. 3. [Fertig?] Ist a =1 oder b =1 so beende Algorithmus mit der Ausgabe von (a/b) = s. 4. [Reduktion von a mod b] Ersetze a durch den kleinsten Absolutrest von a mod b. 5. [1. Ergänzungssatz] Ist a < 0, so setze a ← - a und, falls b ≡ 3 mod 4 ist, auch s ← -s. 6. [Entferne ev. Faktoren 2 aus a] Setze j ← 0 und solange a gerade ist, setze j ← j+1 und a ← a/2. 7. [2.Ergänzungssatz] Ist j ungerade und b ≡ ±3 mod 8, so setze s ← -s. 8. [Quadratisches Reziprozitätsgesetz] Vertausche a und b, d.h. setze t ← a, a ← b, b ← t, und setze zusätzlich s ← -s, falls a ≡ b ≡ 3 mod 4 ist. Fahre dann mit 3. fort. 52 Bem. 4.44: Da a beim Durchlauf von 3.-8. spätestens ab dem zweiten Durchlauf in Schritt 4. (und ev. auch in 6.) mindestens halbiert wird, so werden die Schritte 3.-8. höchstens ⎡lg a ⎤ mal durchlaufen. Nur in Schritt 4. ist dabei eine „echte“ Division durchzuführen, alle anderen Rechnungen können auf einfache Additionen und Schiebeoperationen zurückgeführt werden. (Um etwa zu entscheiden, ob b die angegebenen Bedingungen mod 4 bzw. mod 8 erfüllt, werden nur jeweils die letzten 2 bzw. 3 Bits von b benötigt!) Der Gesamtrechenaufwand ist daher höchstens von der Ordnung O((lg a ) 3 ) und es liegt insbesondere ein Polynomialzeitalgorithmus vor. Wir kommen damit wieder auf das Quadratische Sieb zurück, welches in seiner einfachsten Form durch folgenden Algorithmus beschrieben werden kann: Algorithmus 4.45: Gegeben sei eine zusammengesetzte natürliche Zahl N, welche keine Primzahlpotenz sei. Der folgende Algorithmus liefert dann einen nichttrivialen Faktor d von N. 1. Man setze B ← {p1 , p 2 ,..., p t } , wobei p1 = −1 , p 2 = 2 und die Primzahlen p 3 ,.., p t kleinstmöglich und in aufsteigender Reihenfolge so gewählt sind, dass ( N / p j ) = 1 , j=2,3,..,t, und p t ≈ exp( gilt. Ferner setze man m ← 1 ln N ln ln N ) 2 ⎣ N ⎦. 2. Man finde t+1 Paare (a i , b i ) , i = 1,2,..,t+1, wobei b i = x i + m , a i = ( x i + m) 2 − N für nach aufsteigendem Absolutbetrag geordnete Zahlen x i ∈ {0,±1,±2,...} , sodass die a i alle über B faktorisierbar sind, d.h. dass gilt b i ≡ a i = p1 i1 p 2 i 2 K p t it mod N, i = 1,K, t + 1. 2 e e e 3. Für die analog wie in Satz 4.39 definierten Vektoren Exponentenvektoren e i ∈ Z 2 , i=1,2,...,t+1, finde man mit Methoden der linearen Algebra (Gaußelimination) eine nichtleere Teilmenge T ⊆ {1,2,..., t + 1} , sodass ∑ e i = 0 . t i∈T t 1 ∑ e ij , j=1,2,...,t. 2 i∈T i∈T j=1 5. Ist x ≡ ± y mod N , so kehre man zu 3. zurück und finde eine Menge T mit den dort angegebenen Eigenschaften, welche bis dahin noch nicht untersucht wurde. (In dem unwahrscheinlichen Fall, dass es so etwas nicht gibt, müsste man sogar vorher nach 2. zurückkehren und dort einige Paare (a i , b i ) gegen neue austauschen.) Dann mache man mit 4. weiter. 4. Man setze x ← ∏b i und y← ∏ p j j , wobei f j = f 6. Gebe ggT(x-y,N) (bzw. ggT(x+y,N) ) als (nichtrivialen) Faktor von N aus. Bem. 4.46: 1. Die im ersten Schritt angegebene Größenordnung der größten in der Faktorbasis vorkommenden Primzahl p t ist nur ein sehr ungefährer Richtwert und die in 53 der Praxis verwendeten Werte, die auf experimenteller Basis in Abhängigkeit von der gewählten Implementierung bestimmt werden, sind eher kleiner. 2. Die Bestimmung der t+1 Paare (a i , b i ) im 2. Schritt ist der bei weitem aufwendigste Teil des Algorithmus und erfolgt in der Praxis durch einen Siebprozess, welcher der Methode ihren Namen gegeben hat. (Mehr dazu in den Übungen) Die zur Zeit beste Faktorisierungsmethode für wirklich große Zahlen (ab ca. 120 Stellen) ist allerdings das Zahlkörpersieb oder kurz NFS (=number field sieve). Sie baut ebenfalls auf der Faktorbasismethode auf, wenngleich in einer stark verallgemeinerten Form, auf die wir hier nur kurz eingehen können. Man wählt dazu zunächst ein irreduzibles Polynom f(x)∈ Z[ x] vom Grad d und ein ganzes m mit f(m) ≡ 0 mod N. Für ein N mit 100-200 Stellen ist dabei d=5 oder d=6 optimal. Dieses Polynom und das zugehörige m erhält man so, indem man m:=[ N 1/ d ] setzt und anschließend N in der Basis m entwickelt: N = m d + c d −1 m d −1 +...+ c 0 , 0 ≤ c i < m f(x) ergibt sich dann ganz einfach, indem man in dem rechtsstehenden Ausdruck m durch x ersetzt. Insbesondere ist also f(m)=N. Sollte der (allerdings sehr unwahrscheinliche Fall) eintreten, dass f(x) nicht irreduzibel, also f(x)=g(x)h(x) mit Polynomen g(x),h(x) vom Grad < d wäre, so würde daraus sofort die, wie man zeigen kann, nichttriviale Faktorisierung N=g(m)h(m) folgen, d.h. man wäre fertig, bevor man überhaupt richtig angefangen hätte! Ist nun α eine komplexe Nullstelle von f(x), so kann man dann den Erweiterungsring Z[ α ] von Z und die Abbildung Φ: Z[ α ] → Z N betrachten, welche jedes Vorkommen von α in einem Term aus Z[ α ] durch m mod N ersetzt. Aufgrund unserer Voraussetzungen ist diese Abbildung wohldefiniert und darüber hinaus ein Ringhomomorphismus. Wir benötigen nun eine endliche Menge S von Paaren (a,b) teilerfremder ganzer Zahlen, sodass einerseits das in Z[ α ] gebildete Produkt der a - αb für alle Paare (a,b) in S ein Quadrat, etwa γ 2 , ist, andererseits aber auch das in Z gebildete Produkt der Zahlen a - mb für alle Paare (a,b) in S ein Quadrat, etwa ν 2 ist. Mit x=Φ(γ) und y= ν hat man dann, wie man leicht nachrechnet, eine Lösung von x 2 ≡ y 2 mod N, die, wenn sie nichttrivial ist, wieder auf die bekannte Weise zu einer Faktorisierung von N führt. Leider können wir hier auf algorithmische Details nicht mehr eingehen. Es sei jedoch noch erwähnt, dass der Rechenaufwand durch den Term O(exp((c + o(1))(ln N ) 1/ 3 (ln ln N ) 2 / 3 )) für ein c<2 abgeschätzt werden kann, was asymptotisch deutlich besser als beim Quadratischen Sieb (QS) bzw. dessen Verallgemeinerung, dem MPQS (=multiple polynomial quadratic sieve) ist. Dabei gilt c= 3 64 / 9 ≈ 1.923 für Zahlen allgemeiner Bauart, wo das allgemeine Zahlkörpersieb (GNFS) verwendet wird bzw. c= 3 32 / 9 ≈ 1.526 für Zahlen einer gewissen speziellen Bauart, wo dann das spezielle Zahlkörpersieb (SNFS) zum Einsatz kommt. 54 Das Quadratische Sieb bzw. das Zahlkörpersieb kommen vor allem zum Einsatz, wenn man der zu faktorisierenden Zahl N weiß (wie das z.B. typischerweise bei den RSAModuln der Fall ist), dass sie keine kleinen Faktoren besitzt. Ist über die Größenordnung der Faktoren von N dagegen nichts bekannt, wird man zuerst noch andere, weniger aufwändigere Methoden ausprobieren. Zu diesen gehören neben der bereits behandelten Pollardschen ρ -Methode vor allem Methoden, die auf der Theorie der Elliptischen Kurven basieren. Als Vorbereitung dazu aber vorher noch eine weitere Methode von Pollard, seine (p-1)-Methode, da diese mit derselben Grundidee arbeitet. Bei dieser versucht man mit einem zur Testzahl N teilerfremden a eine Potenz a r mod N so zu bilden, sodass für einen (vorderhand noch unbekannten) Primfaktor p von N gilt, dass p-1 ein Teiler von r ist. Für jedes solche r folgt nämlich aus dem „Kleinen Fermatschen Satz“ sofort, dass r a r ≡ (a p −1 ) p −1 ≡ 1 mod p womit durch Bildung von ggT (a r − 1, N ) sofort einen nichttrivialer Teiler von N finden könnte, außer in dem höchst unwahrscheinlichen Fall, dass auch a r ≡ 1 mod N gilt. Die Hauptschwierigkeit ist dabei klarerweise das Auffinden eines geeigneten r. Unter der (in der Praxis allerdings eher selten zutreffenden) Voraussetzung, dass p−1 für wenigstens einen Primteiler p von N keine “großen” Primzahlpotenzen enthält, also Spotenzglatt für eine nicht allzu große Schranke S ist, wären dann u.a. alle r geeignet, die sämtliche Primzahlpotenzen ≤ S als Faktoren enthalten, da für sie dann p-1 ein Teiler von r wäre. Um so ein r zu konstruieren geht man nach folgendem 2-Stufenplan vor: Ist p1 , p 2 ,..., p s die Folge der Primzahlen in ihrer natürlichen Reihenfolge und q i = p iei , sodass für ein fest gewählte Schranke S1 gilt q i ≤ S1 , aber p i q i > S1 , d.h. also e i = ⎣ln S1 / ln p i ⎦ , i=1,2,..,s so berechnet man für eine fest gewählte Basis a in der 1. Stufe der Reihe nach die Zahlen b1 = a q1 mod N und b i = b iq−i 1 mod N für i > 1 sowie u1 = b 1 − 1 und u i = ( b i − 1) u i −1 mod N für i > 1 und überprüft periodisch, ob ggT ( u i , N ) > 1 ist, womit man dann (außer in dem sehr unwahrscheinlichen Fall ggT ( u i , N ) = N ) einen nichttrivialen Teiler von N gefunden hätte. Gilt stets ggT (u i , N) = 1 für i=1,2,..,s, so war die 1.Stufe der Pollardschen (p − 1)-Methode erfolglos und man kann eine 2.Stufe in folgender Weise anschließen. Ist S2 dazu eine weitere fest gewählte Schranke, welche in der Praxis etwa 10-100 mal so groß wie S1 ist und seien die Primzahlen q mit S1 < q ≤ S 2 fortlaufend mit q s+1 , q s+ 2 ,..., q t benannt, so setzt man 55 c1 = b qs s+1 mod N und c i = c i −1b s s+i q − q s+ i−1 mod N für i>1. Ferner wird wieder eine Folge u1 , u 2 , u 3 ,.. in analoger Weise wie oben, aber mit den c i anstelle der b i definiert und es wird wieder periodisch überprüft, ob ggT ( u i , N ) > 1 ist. Wie man sich leicht überlegt, führt diese 2.Stufe sicher dann zum Erfolg, wenn N einen Primfaktor p besitzt, sodass für einen Primfaktor q von p−1 mit S1 < q ≤ S2 gilt, dass (p−1)/q potenzglatt ist bez. S1 . (q ist also für p-1 gewissermaßen ein nicht zu großer “Ausreißer” bez. der S1 − Potenzglattheit.) Anstelle eines ausformulierten Algorithmus nachstehend ein DERIVE-Programm dazu, aus dem man noch verschieden Feinheiten der Implementierung entnehmen kann. Als eindrucksvolles Beispiel für die Wirksamkeit dieser Methode ist die Auffindung des 25-stelligen Primfaktors p=1155685395246619182673033 von 2 257 − 1 in nur 13.1s (!) auf einem 2GHz-PC angegeben. Anhand der Faktorisierung von p-1 ist übrigens im nachhinein auch klar, warum die besondere Wahl der Schranken für den gefundenen Primfaktor p zum Erfolg führte: S1 =120 000 „deckt“ alle Primzahlpotenzen von p-1 mit Ausnahme der Primzahl 1050151 „ab“, wobei dieser „Ausreißer“ aber noch unterhalb der zweiten Schranke S 2 = 1 200 000 liegt. Für die (p-1)-Methode von Pollard wird (zumindestens „gedanklich“) in der primen Restklassengruppe Z*p für einen Primfaktor p der zu faktorisierenden Zahl N gerechnet. Man kann aber die gleiche Idee auch auf andere Gruppen mit Erfolg anwenden. Z.B. hat 56 H. Williams ein (p+1)-„Gegenstück“, die heute nach ihm benannte (p+1)-Methode angegeben, welche in der eindeutig bestimmten Untergruppe U der Ordnung p+1 der multiplikativen Gruppe eines endlichen Körpers Fp 2 gerechnet wird und wo man also hofft, dass für einen Primfaktor p von N die Zahl p+1 S-potenzglatt für ein nicht zu großes S ist. Zur Erläuterung der ECM (=Elliptic Curve Method), die von H.W.Lenstra im Jahre 1985 eingeführt wurde und mit der in der Praxis Primfaktoren bis etwa 40 Stellen ausgesiebt werden können (in Einzelfällen wurden aber auch schon Primfaktoren mit mehr als 50 Stellen damit gefunden!), benötigen wir zunächst einige Grundtatsachen aus der Theorie der Elliptischen Kurven. Zunächst einmal genügt es in diesem Zusammenhang, wenn wir nur elliptische Kurven über einem Körper K der einfachen Bauart y 2 = x 3 + ax + b (a , b ∈ K) betrachten, wobei das rechsstehende Polynom keine mehrfachen Nullstellen haben soll, was man auch rein rechnerisch durch die Bedingung 4a 3 + 27 b 2 ≠ 0 ausdrücken kann. Für jede spezielle Wahl von a,b ∈ K erhalten wir dann eine elliptische Kurve E als die Menge aller Punkte (x,y) ∈ K x K, welche obiger Gleichung genügen, ergänzt um den sog. “unendlich fernen” Punkt O. Eine elliptische Kurve über dem Körper R könnte – ohne die Hilfsgeraden – z.B. so aussehen, wie unten abgebildet. Q P P+Q Die Hilfsgeraden sollen dabei andeuten, wie für zwei Punkte P und Q von E die Summe P+Q definiert ist. Man unterscheidet dazu folgende Fälle: 57 1. Ist P=O bzw. Q=O, so ist P+Q=Q bzw. P+Q=P, d.h. O spielt die Rolle eines neutralen Elements (“Nullelements”) bez. + . Wir können daher nachfolgend P ≠ O und Q ≠ O voraussetzen. 2. Liegen P und Q spiegelbildlich bezüglich der x-Achse, so sei P+Q=O. Wegen 1. sind daher P und Q in diesem Fall zueinander invers. 3. Liegt weder der Fall 1. noch der Fall 2. vor, so sei P+Q wie aus der vorstehenden Zeichnung ersichtlich definiert, d.h. man bestimmt den eindeutig definierten Schnittpunkt der Sekante durch P und Q (bzw. im Fall P=Q der Tangente durch P) mit der Kurve und definiert P+Q als seinen Spiegelpunkt bez. der x-Achse. Den Fall 3. wollen wir uns nun noch genauer ansehen. Sei dazu P = ( x 1 , y 1 ) und Q = ( x 2 , y 2 ) , so ist die Steigung k der Sekante durch P und Q (bzw. im Fall P = Q Tangente durch P) gegeben durch ⎧ y 2 − y1 falls x 1 ≠ x 2 ⎪⎪ x − x , 2 1 k=⎨ 2 3x + a ⎪ 1 , falls x 1 = x 2 , y 1 ≠ 0 ⎩⎪ 2 y 1 Für die Koordinaten ( x 3 , y 3 ) von P+Q ergibt sich daher nach einfacher Rechnung x 3 = k 2 − x1 − x 2 , y 3 = − y1 + k( x1 − x 3 ) Wie man nun zeigen kann, bildet E bez. der so definierten Addition eine abelsche Gruppe, welche im klassischen Fall K = Q sogar endlich erzeugt ist (Satz von Mordell). Für Anwendungen in der Kryptographie besonders wichtig ist aber der Fall, wo K = Fq , d.h. ein endlicher Körper mit q Elementen ist, wobei q oft sogar eine Primzahl ist. Bei der ECM tritt an die Stelle der oben betrachteten Gruppen eine mittels einer elliptischen Kurve E mod p konstruierte Gruppe E p und an die Stelle von a in der (p−1)Methode ein Punkt P der Kurve, wobei hier die entsprechende Bedingung für r rP = O wäre, würde man mod p rechnen. Tatsächlich kann man in Unkenntnis von p aber natürlich nur mod N, d.h. auf einer “Pseudokurve” E N rechnen und dies drückt sich in E N so aus, dass rP undefiniert ist, da bei der Berechnung von k in obiger Formel entweder ggT ( x 2 − x 1 , N ) > 1 oder ggT (2 y 1 , N ) > 1 ist. Durch die Berechnung dieser ggT erhält man also (außer natürlich in dem sehr unwahrscheinlichen Fall, dass rP = O auch in E N gilt) wieder einen nichttrivialen Teiler von N. Die algorithmische Durchführung ist ansonsten gleich wie für die (p−1)-Methode, der einzige Unterschied besteht in der additiven Schreibweise. Insbesondere gibt es auch hier wieder in ganz analoger Weise zwei Stufen. Für uns von besonderem Interesse ist natürlich noch der Rechenaufwand zur Auffindung eines Primfaktors p von N. Dieser beträgt 58 O(exp( (2 + o(1)) ln p ln ln p ) was bezogen auf N selbst, im ungünstigsten Fall p ≈ N von der Größenordnung her den gleichen Aufwand, nämlich O(exp( (1 + o(1)) ln N ln ln N ) wie bei Anwendung des Quadratischen Siebs ergibt. In vielen Anwendungen von Elliptischen Kurven (z.B. dem ElGamal-Verfahren in der Kryptographie) benötigt man Punkte auf einer vorgegebenen elliptischen Kurve y 2 = x 3 + ax + b über einem Restklassenring Z p für eine (oft sehr “große”) Primzahl p. Hierfür kennt man nur ein probabilistisches Verfahren, welches trotzdem sehr effizient ist. Man wählt dazu solange ein zufälliges x ∈ {0,1,..., p − 1} bis gilt (( x 3 + ax + b) / p) =1. (Da die Wahrscheinlichkeit dafür 50% beträgt, benötigt man im Mittel 2 Versuche, doch können es im Einzelfall auch viel mehr sein, was dies zu einem probabilistischen Verfahren macht.) Danach muss nur noch die “Wurzel” mod p aus x 3 + ax + b gezogen werden, wofür es schnelle Algorithmen gibt, wie wir jetzt zeigen werden. Der folgende Algorithmus ist dabei, wie der zweite Schritt zeigt, ebenfalls probabilistischer Natur. Algorithmus 4.46 (Shanks-Tonelli): Sei p eine ungerade Primzahl und a ∈ {1,..., p − 1} . Die Berechnung einer Lösung von x 2 ≡ a mod p, falls eine solche existiert, geschieht dann auf folgende Weise. 1. Berechne das Jacobisymbol (a/p) mit Hilfe des Alg. 5.9. Ist (a/p)= -1, so hat x 2 ≡ a mod p keine Lösung und man beende das Verfahren mit einer entsprechenden Ausgabe. 2. Wähle ein zufälliges b ∈ {1,..., p − 1} solange bis (b/p)=-1, d.h. b muss ein quadratischer Nichtrest mod p sein. 3. Stelle p-1 in der Form p − 1 = 2 s t dar, wobei t ungerade ist. 4. Berechne a −1 mod p mit Hilfe des erweiterten Euklidischen Algorithmus 2.16. 5. Setze c ← b t mod p und r ← a ( t +1) / 2 mod p. s − i −1 6. Für i=1,2,..,s-1 mache folgendes: Berechne d ← (r 2 a −1 ) 2 mod p. Im Falle, dass d ≡ −1 mod p setze r ← r ⋅ c mod p, in jedem Falle aber c ← c 2 mod p. 7. Gib das Wurzelpaar ± r aus. Der aufwändigste Teil des Algorithmus ist Schritt 6. Dort werden s-1 modulare Potenzen mod p berechnet, für die er Aufwand O((lg p) 3 ) beträgt. Wegen s=O(lg p) ergibt sich als Gesamtaufwand des Algorithmus daher O((lg p) 4 ) , womit also insbesondere ein Polynomialzeitalgorithmus vorliegt. Zwei Spezialfälle verdienen es noch gesondert betrachtet zu werden, da für sie sich der Aufwand zu O((lg p) 3 ) vermindert: 59 Satz 4.47: Sei p eine ungerade Primzahl und a ein quadratischer Rest mod p. Dann gilt für die Lösungen ± r von x 2 ≡ a mod p: 1. Ist p ≡ 3 mod 4, so ist r = a ( p +1) / 4 mod p. ⎧ a ( p +3) / 8 mod p, falls d ≡ 1 mod p 2. Ist p ≡ 5 mod 8 und d = a ( p −1) / 4 mod p, so ist r = ⎨ ( p −5 ) / 8 mod p, falls d ≡ −1 mod p ⎩2a (4a ) Neben dem Faktorsierungsproblem und dem Rucksack-Problem gibt es noch ein weiteres Problem, nämlich DLP (=discrete logarithm problem), welches als Grundlage von PublicKey-Kryptosystemen (wie z.B. für das ElGamal-Verfahren oder auch für den DiffieHellman-Schlüsselaustausch) dienen kann. In seiner allgemeinsten Form geht es dabei um folgendes Problem: Für eine fest vorgegebene endliche zyklische Gruppe G der Ordnung n und ein ebenfalls fest vorgegebenes erzeugendes Element α ∈ G bestimme man zu einem beliebigen Element β ∈ G eine ganze Zahl x mit 0 ≤ x < n so, dass gilt α x = β in G. Im klassischen Fall wurde für die Gruppe G die prime Restklassengruppe Z *p für eine “große” Primzahl p genommen und für α eine Primitivwurzel mod p, d.h. ein erzeugendes Element von Z *p , welches nach einem Satz von Gauß stets existiert. In neuerer Zeit werden aber speziell in der Kryptographie auch immer häufiger Gruppen genommen, die mit Hilfe von Elliptischen Kurven definiert werden, weshalb diese Variante von DLP in der Literatur auch ECDLP genannt wird. Der Grund dafür ist algorithmischer Natur: Bei geeigneter Kurvenwahl (sog. “supersinguläre” und auch sog. “anomale” Kurven müssen dabei vermieden werden!) sind im Gegensatz zum Faktorisierungsproblem oder auch dem oben beschriebenen “klassischen DLP” nicht einmal subexponentielle Algorithmen zur Lösung bekannt, weshalb man mit vergleichsweise viel kleineren Schlüssellängen auskommt. Wir beschreiben nachfolgend pars pro toto nur eine wichtige Attacke auf DLP in allgemeinen Gruppen, den sog. “Baby-step giant-step”-Algorithmus von Shanks. Dabei setzt man zunächst m = n , wobei n die Ordnung von α ist. Unter der Annahme, dass ⎡ ⎤ gilt β = α mit x=im+j, 0 ≤ i, j < m , folgt daraus die Gleichung β(α − m ) i = α j . Dies legt folgenden “Suchalgorithmus” nach i und j nahe: x Algorithmus 4.48 (Baby-step giant-step) Sei α ein festes erzeugendes Element einer zyklischen Gruppe G der Ordnung n und β ∈ G beliebig. Den diskreten Logarithmus x = log α β erhalt man dann nach Durchführung der folgenden Schritte. 1. Setze m ← ⎡ n ⎤, γ ← α − m , i ← 0 und konstruiere eine Tabelle L mit den Einträgen (α j , j) , 0 ≤ j < m , wobei diese nach der ersten Komponente aufsteigend sortiert sei. 2. Überprüfe mit Hilfe einer Binärsuche, ob β die erste Komponente eines Eintrags (α j , j) in der Tabelle L ist. In diesem Fall gib den Wert x=im+j zurück und stoppe. Andernfalls setze β ← βγ , i ← i + 1 und mach bei 2. weiter. 60 Bem 4.49: Die Tabelle L in obigem Algorithmus hat O( n ) Einträge und benötigt daher O( n lg n ) Vergleiche beim Sortieren in Schritt 1 und dies ist zugleich der Gesamtaufwand für alle Binärsuchen in Schritt 2. Jeder Schritt 2 benötigt ferner O( n ) Operationen in der zugrunde liegenden Gruppe G und unter der im allgemeinen erfüllten Voraussetzung, dass eine Gruppenoperation stärker “zu Buche schlägt” als lg n Vergleiche, kann dann daher auch der Gesamtaufwand des Algorithmus durch O( n ) Gruppenoperationen beschrieben werden. Gegenüber einer primitiven Suche, wo man nur alle x im Bereich 0 ≤ x < n der Reihe nach daraufhin überprüft, ob β = α x gilt, wofür man O(n) Gruppenoperationen benötigen würde (man beachte, dass α x +1 = αα x ), ist dies also eine erhebliche Verbesserung, wenngleich der Speicherbedarf erheblich gestiegen ist. (Ein weiteres Beispiel für den Antagonismus Speicherplatzbedarf – Rechenzeit!) Zwischen Faktorisierungsalgorithmen für ganze Zahlen und Algorithmen für DLP besteht übrigens ein gewisser Zusammenhang, sodass Fortschritte auf einem der beiden Gebiete nicht selten dann Fortschritte auf dem anderen Gebiet nach sich ziehen.