Analyse von Algorithmen (WS 2009/2010)

Werbung
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.
Herunterladen