ppt - Informatik

Report
Komplexität von Algorithmen und
Problemen
Klaus Becker
2014
2
Fallstudie - Primzahlalgorithmen
346088282490851215242960395767413316722628668900238547790489283445006220809834114464364375544153707533
664486747635050186414707093323739706083766904042292657896479937097603584695523190454849100503041498098
185402835071596835622329419680597622813345447397208492609048551927706260549117935903890607959811638387
214329942787636330953774381948448664711249676857988881722120330008214696844649561469971941269212843362
064633138595375772004624420290646813260875582574884704893842439892702368849786430630930044229396033700
105465953863020090730439444822025590974067005973305707995078329631309387398850801984162586351945229130
425629366798595874957210311737477964188950607019417175060019371524300323636319342657985162360474512090
898647074307803622983070381934454864937566479918042587755749738339033157350828910293923593527586171850
199425548346718610745487724398807296062449119400666801128238240958164582617618617466040348020564668231
437182554927847793809917495802552633233265364577438941508489539699028185300578708762293298033382857354
192282590221696026655322108347896020516865460114667379813060562474800550717182503337375022673073441785
129507385943306843408026982289639865627325971753720872956490728302897497713583308679515087108592167432
185229188116706374484964985490944305412774440794079895398574694527721321665808857543604774088429133272
929486968974961416149197398454328358943244736013876096437505146992150326837445270717186840918321709483
693962800611845937461435890688111902531018735953191561073191960711505984880700270887058427496052030631
941911669221061761576093672419481606259890321279847480810753243826320939137964446657006013912783603230
022674342951943256072806612601193787194051514975551875492521342643946459638539649133096977765333294018
221580031828892780723686021289827103066181151189641318936578454002968600124203913769646701839835949541
124845655973124607377987770920717067108245037074572201550158995917662449577680068024829766739203929954
101642247764456712221498036579277084129255555428170455724308463899881299605192273139872912009020608820
607337620758922994736664058974270358117868798756943150786544200556034696253093996539559323104664300391
464658054529650140400194238975526755347682486246319514314931881709059725887801118502811905590736777711
874328140886786742863021082751492584771012964518336519797173751709005056736459646963553313698192960002
673895832892991267383457269803259989559975011766642010428885460856994464428341952329487874884105957501
974387863531192042108558046924605825338329677719469114599019213249849688100211899682849413315731640563
047254808689218234425381995903838524127868408334796114199701017929783556536507553291382986542462253468
272075036067407459569581273837487178259185274731649705820951813129055192427102805730231455547936284990
105092960558497123779789849218399970374158976741548307086291454847245367245726224501314799926816843104
644494390222505048592508347618947888895525278984009881962000148685756402331365091456281271913548582750
83907891469979019426224883789463551
3
Teil 1
Fallstudie - Primzahlalgorithmen
Praktische Anwendbarkeit von Algorithmen
4
Primzahlen
Primzahlen sind natürliche Zahlen, die nur durch 1 und sich selbst ohne Rest teilbar sind.
Beispiele: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, ...
Satz: Jede natürliche Zahl lässt sich als Produkt von Primzahlen schreiben. Diese Darstellung
ist bis auf die Reihenfolge der Faktoren eindeutig.
Beispiel: 260 = 2*2*5*13
Man nennt die Primzahlen, die in einer Produktdarstellung einer gegebenen Zahl vorkommen,
auch Primfaktoren der Zahl.
Primzahltest
5
Das Primzahltestproblem (kurz PRIMES) besteht darin, bei einer vorgegebenen natürlichen
Zahl zu überprüfen, ob sie eine Primzahl ist.
istPrimzahl
natürliche Zahl
True
falls n eine Primzahl ist
False
falls n keine Primzahl ist
n
Primzahlerzeugung
6
Das Primzahlerzeugungsproblem besteht darin, eine Primzahl (einer bestimmten
Größenordnung) zu erzeugen.
erzeugtePrimzahl
natürliche Zahl
n
p
Primzahl p mit p >= n
p
Primzahl aus dem Bereich m..n
erzeugtePrimzahl
natürliche Zahl
natürliche Zahl
m
n
7
Primfaktorzerlegung / Faktorisierung
Das Faktorisierungsproblem (kurz FACTORIZE) besteht darin, eine vorgegebene natürliche Zahl
in ein Produkt aus Primfaktoren zu zerlegen.
primfaktoren
natürliche Zahl
n
L
Liste der Primfaktoren von n
primfaktoren
260
[2, 2, 5, 13]
8
Primzahlalgorithmen
Aufgabe:
Aus der Primzahleigenschaft ergibt sich direkt ein einfacher Algorithmus, mit dem man bei
einer natürlichen Zahl n überprüfen kann, ob es sich um eine Primzahl handelt.
Formuliere den Algorithmus in Struktogrammform. Implementiere und teste den Algorithmus.
Überlege dir Möglichkeiten zur Verbesserungen des einfachen Algorithmus.
Aufgabe:
Entwickle einen Algorithmus zur Erzeugung von Primzahlen. Implementiere und teste den
Algorithmus.
Aufgabe:
(a) Bei kleineren Zahlen kann man eine Primfaktorzerlegung oft direkt angeben. Bestimme eine
Primfaktorzerlegung von n = 48 und n = 100.
(b) Bei größeren Zahlen sollte man systematisch vorgehen, um die Primfaktoren zu
bestimmen. Bestimme eine Primfaktorzerlegung von n = 221 und n = 585.
(c) Entwickle zunächst einen Algorithmus zur Primfaktorzerlegung. Beschreibe in einem ersten
Schritt in Worten das Verfahren, das du zur Primfaktorzerlegung von Zahlen benutzt.
Beschreibe das Verfahren anschließend mit einem Struktogramm. Entwickle dann ein
Programm zur Primfaktordarstellung.
9
Teil 2
Ein einfacher Primzahltest
10
Primzahltest mit Probedivisionen
Übergabe: n = 51
ALGORITHMUS istPrimzahl(n):
# Probedivisionen
prim = True
n % 2 -> 1
k=2
n % 3 -> 0
SOLANGE k*k <= n und prim:
Rückgabe: False
Übergabe: n = 53
# Probedivisionen
n % 2 -> 1
n % 3 -> 2
n % 4 -> 1
n % 5 -> 3
n % 6 -> 5
n % 7 -> 4
Rückgabe: True
WENN n % k == 0:
prim = False
k = k+1
Rückgabe: prim
11
Ein einfaches Testverfahren
primzahlen = [
11,
101,
1009,
10007,
100003,
1000003,
10000019,
100000007,
1000000007,
10000000019,
100000000003,
1000000000039,
10000000000037,
100000000000031,
1000000000000037,
10000000000000061,
100000000000000003,
1000000000000000003,
10000000000000000051,
100000000000000000039,
1000000000000000000117,
...]
def istPrimzahl(n):
...
Mit Probedivisionen
from time import *
for p in primzahlen:
t1 = clock()
ergebnis = primzahl(p)
t2 = clock()
t = t2 - t1
print("Primzahl: ", p,
"Rechenzeit: ", t)
Erhöhe systematisch die
„Größe“ der Primzahl
12
>>>
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
...
Laufzeitverhalten
11 Rechenzeit: 5.86666741164e-06
101 Rechenzeit: 8.3809534452e-06
1009 Rechenzeit: 1.50857162014e-05
10007 Rechenzeit: 3.54793695847e-05
100003 Rechenzeit: 0.000101968266917
1000003 Rechenzeit: 0.000324342898329
10000019 Rechenzeit: 0.00104817791088
100000007 Rechenzeit: 0.00332500359683
1000000007 Rechenzeit: 0.0105655886432
10000000019 Rechenzeit: 0.0407208178693
100000000003 Rechenzeit: 0.140259725747
1000000000039 Rechenzeit: 0.447675891768
10000000000037 Rechenzeit: 1.41919042783
100000000000031 Rechenzeit: 4.55093566361
1000000000000037 Rechenzeit: 14.3208156344
10000000000000061 Rechenzeit: 45.2250185429
100000000000000003 Rechenzeit: 144.197546336
Aufgabe: Schätze ab, wie lange eine Überprüfung einer 100- bzw. 600-stelligen Primzahl mit
Hilfe von Probedivisionen in etwa dauert.
13
Zusammenhänge
Gesetzmäßigkeit:
Wenn man die Anzahl der Stellen der Ausgangszahl um 2 erhöht, dann erhöht sich die
Laufzeit etwa um den Faktor 10. Jede zusätzliche Stelle bei der Ausgangszahl führt also dazu,
dass die Laufzeit mit dem Faktor √10 multipliziert wird.
Es handelt sich hier um ein exponentielles Wachstumsverhalten, das man mathematisch mit
einer Exponentialfunktion beschreiben kann: Wenn i die Anzahl der Stellen der Ausgangszahl
angibt, dann lässt sich die Laufzeit mit einer Funktion wie folgt beschreiben:
L(i) = c*(√10)i ; mit einer Konstanten c
>>>
...
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
...
100000000003 Rechenzeit: 0.140259725747
1000000000039 Rechenzeit: 0.447675891768
10000000000037 Rechenzeit: 1.41919042783
100000000000031 Rechenzeit: 4.55093566361
1000000000000037 Rechenzeit: 14.3208156344
10000000000000061 Rechenzeit: 45.2250185429
100000000000000003 Rechenzeit: 144.197546336
14
Prognosen
Prognose:
Wenn die Zahl 100 Stellen haben soll, also 88 Stellen mehr als eine 12-stellige Zahl, so
benötigt man nach der gefundenen Gesetzmäßigkeit 1044-mal so lange wie bei der 12stelligen Zahl - also etwa 1044 Sekunden.
>>>
...
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
Primzahl:
...
100000000003 Rechenzeit: 0.140259725747
1000000000039 Rechenzeit: 0.447675891768
10000000000037 Rechenzeit: 1.41919042783
100000000000031 Rechenzeit: 4.55093566361
1000000000000037 Rechenzeit: 14.3208156344
10000000000000061 Rechenzeit: 45.2250185429
100000000000000003 Rechenzeit: 144.197546336
15
Teil 3
Eine Komplexitätanalyse
16
Problematik von Laufzeitmessungen
Laufzeitmessungen werden in der Praxis durchgeführt, um das Laufzeitverhalten eines
Programms unter Realbedingungen zu ermitteln.
Aus systematisch durchgeführten Laufzeitmessungen kann man oft Gesetzmäßigkeiten
erschließen, wie die Laufzeit von den zu verarbeitenden Daten abhängt.
Bei Laufzeitmessungen muss man aber sehr genau darauf achten, dass sie unter gleichen
Bedingungen erfolgen. Ein Wechsel des Rechners führt in der Regel zu anderen Ergebnissen.
Auch Änderungen in der Implementierung wirken sich in der Regel auf die Messergebnisse aus.
Selbst bei ein und demselben Rechner und derselben Implementierung können sich die
Bedingungen ändern, da oft mehrere Prozesse gleichzeitig ablaufen.
Ergebnisse von Laufzeitmessungen sind also kaum auf andere Systeme (andere Rechner,
andere Programmiersprachen) übertragbar. Um diese Schwierigkeit zu überwinden, soll im
Folgenden ein anderer Weg zur Beschreibung der Berechnungskomplexität beschritten werden.
Kostenabschätzung
17
ALGORITHMUS istPrimzahl(n):
prim = True
k=2
SOLANGE k*k <= n und prim:
WENN n % k == 0:
prim = False
k = k+1
Rückgabe: prim
Bei der Ausführung des Algorithmus (bei
einer vorgegebenen natürlichen Zahl) spielt
es eine Rolle, wie viele Operationen
ausgeführt werden müssen.
Im dargestellten Algorithmus werden u.a.
folgende Operationen ausgeführt:
n % k (Probedivision)
k*k (Produkt)
… <= n (Vergleich)
… und … (logische Operation)
k+1 (Inkrementieren)
Bei der Festlegung eines Kostenmaßes müssen Annahmen über den Aufwand der
verschiedenen auszuführenden Operationen gemacht werden. Zwei ganz unterschiedliche
Wege kann man dabei bestreiten. Ein Weg besteht darin, unterschiedliche Aufwände von
Operationen möglichst genau zu erfassen und im Kostenmaß zu berücksichtigen. Ein anderer
Weg beschränkt sich darauf, dominante Operationen auszuwählen und die Kosten nur grob
zuzuschätzen. Wir werden hier nur den zweiten Weg beschreiten.
18
Fachkonzept Kostenfunktion
Die Problemgröße ist eine präzise
Beschreibung des Umfangs der zu
verarbeitenden Daten, von dem das Zeitbzw. Speicherverhalten von
Lösungalgorithmen maßgeblich beeinflusst
wird.
Ein Kostenmaß legt fest, in welchem Maße
welche Operationen bei der
Aufwandsbestimmung berücksichtigt werden.
Eine Kostenfunktion ordnet der
Problemgröße i die vom Algorithmus
benötigten Gesamtkosten K(i) bzgl. des
vorgegebenen Kostenmaßes zu.
Bei der Beschreibung der Zeitkomplexität mit
Hilfe einer Kostenfunktion werden in der
Regel eine Reihe von Vereinfachungen
vorgenommen sowie Annahmen gemacht.
Die Festlegung einer Kostenfunktion kann
somit als eine Form der Modellierung
angesehen werden, weil hier ein
Berechnungsmodell entwickelt werden muss,
das den Berechnungsaufwand vereinfachend
beschreibt.
Wie bei jeder Modellierung kann das
entwickelte Modell mehr oder auch weniger
geeignet sein, um die zu beschreibende
Wirklichkeit darzustellen. Bei der
Modellierung der Zeitkomplexität kommt es
darauf an, sinnvolle Annahmen über den
Aufwand bestimmter, im Algorithmus
vorkommender Operationen zu machen.
Problemgröße / Kosten
19
Problemgröße i:
Anzahl der Stellen der Ausgangszahl n (als Maß für die Länge von n)
Kosten K:
Anzahl der durchzuführenden Probedivisionen
ALGORITHMUS istPrimzahl(n):
Übergabe: n = 541
prim = True
Anzahl der Stellen: 3
k=2
Probedivisionen
SOLANGE k*k <= n und prim:
n%2>0
WENN n % k == 0:
prim = False
k = k+1
Rückgabe: prim
n%3>0
…
n % 23 > 0
Rückgabe: True
Anzahl der Probedivisionen: 22
20
Kostenabschätzung
Aufgabe:
Für welche Zahlen benötigt man die wenigsten Probedivisionen, für welche die meisten?
Aufgabe:
Betrachte den Fall, dass n eine Primzahl mit i = 3, 4, … Stellen ist. Wie viele Probedivisionen
benötigt man mindestens / höchstens, um das mit dem gegebenen Algorithmus festzustellen?
Kostenanalyse
21
best case (bester Fall):
der Fall, in dem bei der Ausführung des Algorithmus die wenigsten Kosten anfallen
best case: n ist eine gerade Zahl (mit i Stellen)
Beispiel: n = 998; i = 3
Probedivisionen
n%2=0
Rückgabe: False
Anzahl der Probedivisionen: 1
Es gilt:
K(i) = 1
„gar kein“
Wachstum
22
Kostenanalyse
worst case (schlechtester Fall):
der Fall, in dem bei der Ausführung des Algorithmus die meisten Kosten anfallen
Worst case: n ist die größte Primzahl mit i Stellen
Beispiel: n = 997; i = 3
Probedivisionen:
n%2>0
n%3>0
n%4>0
…
z % 31 > 0 (Beachte: √997 = 31....)
Rückgabe: True
Anzahl der Probedivisionen: 30
Beachte: 10i-1 < n < 10i
Es gilt: √(10i-1)-1 < K(i) < √(10i)-1
Also: (√10)i-1 -1 < K(i) < (√10)i – 1
exponentielles
Wachstum
23
Klassifikation von Kostenfunktionen
Eine (Kosten-) Funktion f wächst schneller als eine (Kosten-) Funktion g, wenn der Quotient
f(i)/g(i) mit wachsendem i gegen unendlich strebt.
Eine (Kosten-) Funktion f wächst langsamer als eine (Kosten-) Funktion g, wenn der Quotient
f(i)/g(i) mit wachsendem i gegen 0 strebt.
Eine (Kosten-) Funktion f wächst genauso schnell wie eine (Kosten-) Funktion g, wenn der
Quotient f(i)/g(i) mit wachsendem n gegen eine Konstante c strebt.
Eine (Kosten-) Funktion f wächst höchstens so schnell wie eine (Kosten-) Funktion g, wenn f
genauso schnell oder langsamer als g wächst.
Eine (Kosten-) Funktion f wächst mindestens so schnell wie eine (Kosten-) Funktion g, wenn f
genauso schnell oder schneller als g wächst.
Die Klasse aller Funktionen, die nicht höchstens so schnell wachsen wie eine vorgegebene
(Kosten-) Funktion f, wird mit O(f) bezeichnet. Man liest das so: "Groß O von f".
Die Klasse aller Funktionen, die nicht mindestens so schnell wachsen wie eine vorgegebene
(Kosten-) Funktion f, wird mit Ω(f) bezeichnet. Man liest das so: "Groß Omega von f".
24
Wachstumsverhalten
Im worst case (d.h. n ist eine Primzahl) wächst die Kostenfunktion, die die maximale Anzahl
von Probedivisionen einer i-stelligen Zahl beschreibt, genauso schnell wie die
Exponentialfunktion g(i) = (√10)i.
Es gilt: (√10)i-1 -1 < K(i) < (√10)i – 1
Also: ((√10)i-1 -1) / (√10)i < K(i) / (√10)i < ((√10)i – 1) / (√10)i
Also: (1/√10) - 1/(√10)i < K(i) / (√10)i < 1 - 1/(√10)i
Für i gegen unendlich konvergiert K(i) / (√10)i gegen eine Zahl :
1/√10 < lim (K(i) / (√10)i) < 1
25
Wachstumsprototypen
Prototyp
Grundeigenschaft
f(n) = log(n)
logarithmisches Wachstum:
Wenn n sich verdoppelt, dann wächst f(n) um einen konstanten Betrag.
f(n) = n
lineares Wachstum:
Wenn n sich verdoppelt, dann verdoppelt sich f(n) ebenfalls.
f(n) = n*log(n)
logarithmisch-lineares Wachstum
f(n) = n2
quadratisches Wachstum:
Wenn n sich verdoppelt, dann vervierfacht sich f(n).
f(n) = n3
kubisches Wachstum:
Wenn n sich verdoppelt, dann verachtfacht sich f(n).
f(n) = nk
polynomiales Wachstum
Wenn n sich verdoppelt, dann vervielfacht sich f(n) mit 2k.
f(n) = bn
exponentielles Wachstum:
Wenn n sich um 1 erhöht, dann vervielfacht sich f(n) mit b.
26
Praktische Anwendbarkeit
Algorithmen, deren
Zeitkomplexität durch eine
Kostenfunktion beschrieben
wird, die exponentiell oder
noch schneller wächst,
gelten als praktisch nicht
anwendbar.
Wir nehmen hier an, dass
zur Verarbeitung einer
Kosteneinheit eine
Millisekunde benötigt wird.
aus: P. Breuer:
Praktische Grenzen der Berechenbarkeit.
27
Teil 4
Die Komplexität des Primzahltestproblems
28
Komplexität von Problemen
Die (Zeit-)Komplexität eines Problems beschreibt man durch eine Komplexitätsklasse, die eine
untere Schranken für die Komplexität aller Algorithmen, die das Problem lösen, bilden.
Zur Beschreibung der Komplexität eines Problems muss man folglich Aussagen über alle
möglichen Algorithmen zur Lösung des Problems machen. Man zeigt, dass ein bestimmter
Ressourcenverbrauch bei all diesen Algorithmen erforderlich ist und von keinem Algorithmus
unterschritten werden kann. Die Schwierigkeit beim Nachweis solcher Aussagen besteht darin,
dass man den Nachweis über alle denkbaren - d.h. bis jetzt gefundenen und auch noch nicht
bekannten - Algorithmen führen muss.
29
Komplexität des Primzahltestproblems
Der Primzahltest mit Probedivisionen ist ein Algorithmus mit exponentieller Zeitkomplexität.
Dieser Algorithmus ist für große Ausgangszahlen praktisch nicht anwendbar.
Entsprechendes gilt für andere naheliegende Algorithmen, z.B. für das Verfahren mit dem Sieb
des Eratosthenes (mit einer passend gewählten Kostenmodellierung).
Es stellt sich die Frage, ob alle Primzahltest-Algorithmen eine exponentielle Zeitkomplexität
haben bzw., ob es Primzahltest-Algorithmen mit einer nicht-exponentiellen Zeitkomplexität
gibt.
Lange Zeit gab es hierauf keine positive oder negative Antwort.
AKS-Primzahltest
30
Der AKS-Primzahltest wurde im Jahr 2002 von den drei indischen Wissenschaftlern Manindra
Agrawal, Neeraj Kayal und Nitin Saxena ein Primzahltest entwickelt.
ALGORITHMUS istPrimzahl(n):
1. if n ist eine reine Potenz:
2.
return ZUSAMMENGESETZT
3. finde das kleinste r mit o_r(n) > log(n)2
4. if 1 < ggT(a,n) < n für ein a ≤ r:
5.
return ZUSAMMENGESETZT
6. if n ≤ r:
7.
return PRIM
8. for a=1 to sqrt(phi(r))*log(n) do
9.
10.
if (x+a)n ≠ xn+a (mod (xr-1,n)):
return ZUSAMMENGESETZT
11. return PRIM
Quelle: http://de.wikipedia.org/wiki/AKS-Primzahltest
31
AKS-Primzahltest
Info:
Der AKS-Primzahltest hat eine Zeitkomplexität, die nicht schneller als die Potenzfunktion
g(i) = i12 wächst.
P bezeichnet die Klasse der Probleme, die mit einem Algorithmus mit polynomialer
Zeitkomplexität gelöst werden können.
Folgerung: PRIMES gehört zur Klasse P.
Probabilistische Testverfahren
32
Für praktische Zwecke ist der AKS-Primzahltest wenig geeignet. Das Laufzeitverhalten des
AKS-Primzahltest ist für große Primzahlen immer noch sehr schlecht.
In der Praxis benutzt man heute oft probabilistische Testverfahren, da sie sehr effizient
arbeiten. Probabilistischen Testverfahren funktionieren nach dem folgenden Prinzip: Bei
Übergabe einer natürlichen Zahl n erhält man als Rückgabe entweder "n ist keine Primzahl"
oder "n ist wahrscheinlich eine Primzahl". Diese Testverfahren liefern also keine absolute
Gewissheit, wenn sie das Ergebnis "n ist wahrscheinlich eine Primzahl" produzieren. Die
übergebene Zahl n kann mit einer bestimmten Wahrscheinlichkeit auch keine Primzahl sein.
Allerdings ist diese Wahrscheinlichkeit sehr gering, so dass man die Unsicherheit oft in Kauf
nimmt.
istWahrscheinlichPrimzahl
natürliche Zahl
True
falls n wahrscheinlich
eine Primzahl ist
False
falls n keine Primzahl ist
n
33
Miller-Rabin-Test
import random
def miller_rabin_pass(a, n):
d = n - 1
s = 0
while d % 2 == 0:
d = d >> 1
s = s + 1
a_to_power = pow(a, d, n)
if a_to_power == 1:
return True
for i in range(s-1):
if a_to_power == n - 1:
return True
a_to_power = (a_to_power * a_to_power) % n
return a_to_power == n - 1
def miller_rabin_test(n):
for repeat in range(20):
a = 0
while a == 0:
a = random.randrange(n)
if not miller_rabin_pass(a, n):
return False
return True
34
Miller-Rabin-Test
Eines der probabilistischer Testverfahren ist das Miller-Rabin-Verfahren. Beachte, dass die
Wiederholungszahl 20 (s.uo) die Fehlerwahrscheinlichkeit beeinflusst. Setzt man diese
Wiederholungszahl auf einen größeren Wert, so nimmt die Fehlerwahrscheinlichkeit ab.
Info:
Der Miller-Rabin-Test hat eine Zeitkomplexität, die nicht schneller als die Potenzfunktion
g(i) = k*i3 wächst. Die Konstante k beschreibt hier die Anzahl der durchgeführten
Wiederholungen.
35
Teil 5
Primzahlerzeugung
Ein einfaches Verfahren
36
erzeugtePrimzahl
natürliche Zahl
m
p
natürliche Zahl
n
ALGORITHMUS primzahl(m, n):
gefunden = False
SOLANGE nicht gefunden:
erzeuge eine Zufallszahl k aus dem Bereich m..n
WENN istPrimzahl(k):
gefunden = True
Rückgabe: k
Primzahl aus dem Bereich m..n
37
Test des Verfahrens
Aufgabe:
Implementiere und teste das Verfahren. Benutze einen schnellen Primzahltest (z.B. den MillerRabin-Test).
38
Beurteilung des Verfahrens
Aufgabe:
Wie lange dauert es durchschnittlich, bis man eine Primzahl im vorgegebenen Bereich
gefunden hat?
39
Verteilung der Primzahlen
Die Funktion π(x) beschreibe die Anzahl der Primzahlen, die kleiner oder gleich x sind.
Primzahlsatz:
Die Funktionen π(x) und g(x) = x/ln(x) sind asymptotisch äquivalent. Für große x gilt: π(x) ist
ungefähr gleich x/ln(x).
Genauer: Für x >= 11 gilt: x/ln(x) <= π(x) <= x/ln(x)*(1+3/(2ln(x)))
Blau: π(x)
Grün: x(ln(x)
40
Verteilung der Primzahlen
Aufgabe:
Schätze ab, wie viele Primzahlen es im Bereich 100000..999999 gibt.
Wie viele Zahlen muss man im Durchschnitt testen, um eine Primzahl im angegebenen Bereich
zu erhalten?
Aufgabe:
Beim RSA-Verfahren (1024-Bit-Schlüssel) benutzt man Primzahlen aus dem Bereich 21023 ..
21024 (das sind Zahlen mit etwa 308 bzw. 309 Stellen). Wie viele Primzahlen gibt es in diesem
Bereich?
Wie viele Zahlen muss man im Durchschnitt testen, um eine Primzahl im angegebenen Bereich
zu erhalten?
>>> from math import log
>>> ...
41
Teil 6
Primfaktorzerlegung
Ein einfaches Verfahren
42
primfaktoren
natürliche Zahl
n
L
Liste der Primfaktoren von n
ALGORITHMUS primfaktoren(n):
faktoren = []
z=n
SOLANGE z > 1:
bestimme den kleinsten Primfaktor p von z mit Probedivisionen
füge p in die Liste faktoren ein
z = z // p
Rückgabe: faktoren
43
Ein einfaches Faktorisierungsverfahren
# Übergabe: n = 51
ALGORITHMUS primfaktoren(n):
# Initialisierung
faktoren = []
{faktoren -> []}
z=n
{z -> 51}
initialisiere die Liste faktoren:
faktoren = []
z % 2 -> 1
initialisiere die Hilfsvariable z:
z=n
z % 3 -> 0
SOLANGE z > 1:
# Probedivisionen
# Aktualisierung
p=z
{p -> 3}
faktoren = faktoren + [p] {faktoren -> [3]}
z = z // p
{z -> 17}
z % 2 -> 1
Rückgabe: faktoren
z % 3 -> 2
z % 4 -> 1
z % 5 -> 2
# Aktualisierung
{p -> 17}
faktoren = faktoren + [p] {faktoren -> [3, 17]}
z = z // p
# Rückgabe: [3, 17]
füge p in die Liste faktoren ein
z = z // p
# Probedivisionen
p=z
bestimme den kleinsten Primfaktor p
von z mit Probedivisionen
{z -> 1}
44
Eine Implementierung
def primfaktoren(n):
"""
>>> primfaktoren(24)
[2, 2, 2, 3]
"""
faktoren = []
z = n
while z > 1:
# bestimme die kleinsten Primfaktor p von z
i = 2
gefunden = False
while i*i <= n and not gefunden:
if z % i == 0:
gefunden = True
p = i
else:
i = i + 1
if not gefunden:
p = z
# füge p in die Liste der Faktoren ein
faktoren = faktoren + [p]
z = z // p
return faktoren
45
Systematische Laufzeitmessungen
from faktorisierung import primfaktoren
testzahlen = [
from time import *
11,
testzahlen = [...]
101,
for z in testzahlen:
1009,
t1 = clock()
10007,
ergebnis = primfaktoren(z)
100003,
t2 = clock()
1000003,
t = t2 - t1
10000019,
print("Zahl:
", z)
100000007,
print("Primfaktoren:", ergebnis)
1000000007,
print("Rechenzeit: ", t)
10000000019,
print()
100000000003,
Aufgabe:
Führe die Messungen durch. Kannst du
anhand der Zahlen erste Zusammenhänge
erkennen? Kannst du Prognosen erstellen,
wie lange man wohl bis zum nächsten
Ergebnis warten muss?
1000000000039,
10000000000037,
100000000000031,
1000000000000037,
10000000000000061,
100000000000000003,
1000000000000000003,
10000000000000000051,
100000000000000000039,
...]
46
Zusammenhänge und Prognosen
Gesetzmäßigkeit:
Wenn man die Anzahl der Stellen der
Ausgangszahl um 2 erhöht, dann erhöht sich
die Laufzeit um den Faktor 10. Jede
zusätzliche Stelle bei der Ausgangszahl führt
also dazu, dass die Laufzeit mit dem Faktor
√10 multipliziert wird.
...
Es handelt sich hier um ein exponentielles
Wachstumsverhalten, das man mathematisch
mit einer Exponentialfunktion beschreiben
kann: Wenn k die Anzahl der Stellen der
Ausgangszahl angibt, dann erhält man eine
Laufzeit vom Typ L(k) = c*(√10)k mit einer
Konstanten c.
Primfaktoren: [10000000000037]
Prognose:
Wenn die Zahl 100 Stellen haben soll, also
88 Stellen mehr als eine 12-stellige Zahl, so
benötigt man nach der gefundenen
Gesetzmäßigkeit 1044-mal so lange wie bei
der 12-stelligen Zahl - also etwa 1044
Sekunden.
Zahl:
Zahl:
1000000000039
Primfaktoren: [1000000000039]
Rechenzeit: 0.906267137304
Zahl:
10000000000037
Rechenzeit: 2.88270213114
Zahl:
100000000000031
Primfaktoren: [100000000000031]
Rechenzeit: 9.1279123464
1000000000000037
Primfaktoren: [1000000000000037]
Rechenzeit: 28.5701070946
Zahl:
10000000000000061
Primfaktoren: [10000000000000061]
Rechenzeit: 91.2736900919
...
47
Kostenanalyse
best case (bester Fall): der Fall, in dem bei der Ausführung des Algorithmus die wenigsten
Kosten anfallen
worst case (schlechtester Fall): der Fall, in dem bei der Ausführung des Algorithmus die
meisten Kosten anfallen
average case (durchschnittlicher Fall): eine Mittelung der Kosten über alle Fälle
best case: n ist eine Zweierpotenz mit i Stellen
worst case: n ist die größte Primzahl mit i Stellen
Beispiel: n = 29 = 512; i = 3
Beispiel: n = 983; i = 3
Beachte: 10 < 24; n < 10i < 24*i
Beachte: 10i-1 < n < 10i
Probedivisionen:
Probedivisionen:
z=n
z=n
z%2=0
z%2>0
p = z; faktoren = faktoren + [p]; z = z//2
z%3>0
z%2=0
...
p = z; faktoren = faktoren + [p]; z = z//2
z % 31 > 0; Beachte: √983 = 31.35...
...
-> z ist Primzahl
Anzahl der Probedivisionen: 9 < 4*3
Anzahl der Probedivisionen: 31 > √100 = (√10)2
K(i) <= 4*i
(√10)i-1 -1< = K(i) <= (√10)i -1
48
Wachstumsverhalten
best case (bester Fall): der Fall, in dem bei
der Ausführung des Algorithmus die
wenigsten Kosten anfallen
worst case (schlechtester Fall): der Fall, in
dem bei der Ausführung des Algorithmus die
meisten Kosten anfallen
K(i) <= 4*i
(√10)i-1 <= K(i) <= (√10)i
f(i) = 4*i
lineares
Wachstum
Wenn man die Problemgröße um 1 erhöht,
dann wachsen die Kosten den festen Betrag
4.
f(i) = (√10)i
exponentielles
Wachstum
Wenn man die Problemgröße um 1 erhöht,
dann wachsen die Kosten mit dem Faktor
√10. Wenn man die Problemgröße um 2
erhöht, dann wachsen die Kosten mit dem
Faktor √10*√10, also mit den Faktor 10.
49
Anwendbarkeit des Faktorisierungsalg.
Der unten dargestellte Faktorisierungsalgorithmus ist praktisch nicht anwendbar.
Angenommen, der Rechenaufwand beträgt bei 10 Stellen 1 Zeiteinheit.
Dann beträgt der Rechenaufwand bei 100 Stellen 1045 Zeiteiheiten.
Wenn sich die Rechnergeschwindigkeit um den Faktor 1000 verbessert, dann beträgt der
Rechenaufwand immer noch 1042 Zeiteiheiten.
ALGORITHMUS primfaktoren(n):
initialisiere die Liste faktoren:
faktoren = []
initialisiere die Hilfsvariable z:
z=n
SOLANGE z > 1:
bestimme den kleinsten Primfaktor p
von z mit Probedivisionen
füge p in die Liste faktoren ein
z = z // p
Rückgabe: faktoren
50
Komplexität d. Faktorisierungsproblems
Problem: Gibt es „schnelle“ Algorithmen zur Primfaktorzerlegung?
Es gibt eine Vielzahl an Faktorisierungsalgorithmen. Bis jetzt ist es nicht gelungen, einen
Algorithmus zur Primfaktorzerlegung zu entwickeln, der eine nicht-exponentielle
Zeitkomplexität hat.
Ein nichtdeterministischer Algorithmen
51
ALGORITHMUS primfaktoren
Übergabe: natürliche Zahl n
primfaktoren = []
z=n
SOLANGE z > 1:
i = Anzahl der Stellen von z
# rate eine natürliche Zahl k mit i Stellen (als potentieller Primfaktor)
k=0
WIEDERHOLE i mal:
nichtdeterministisch
„Jetzt eine 5“
k = k * 10
z=0|z=1|z=2|z=3|z=4|z=5|z=6|z=7|z=8|z=9
k=k+z
# überprüfe, ob k tatsächlich Primfaktor von z ist
Orakel
WENN k > 1 und k < z und z%k == 0 und istPrimzahl(k):
primfaktoren = primfaktoren + [k]
z = z // k
SONST:
primfaktoren = primfaktoren + [z]
z=1
Rückgabe: primfaktoren
Der Algorithmen liefert die Liste der
Primfaktoren, wenn das Orakel jeweils
die „richtige“ Ziffer liefert.
Ein nichtdeterministischer Algorithmen
52
ALGORITHMUS primfaktoren
Übergabe: natürliche Zahl n
primfaktoren = []
z=n
SOLANGE z > 1:
i = Anzahl der Stellen von z
# rate eine natürliche Zahl k mit i Stellen (als potentieller Primfaktor)
k=0
WIEDERHOLE i mal:
nichtdeterministisch
k = k * 10
z=0|z=1|z=2|z=3|z=4|z=5|z=6|z=7|z=8|z=9
k=k+z
# überprüfe, ob k tatsächlich Primfaktor von z ist
WENN k > 1 und k < z und z%k == 0 und istPrimzahl(k):
primfaktoren = primfaktoren + [k]
z = z // k
SONST:
primfaktoren = primfaktoren + [z]
z=1
Rückgabe: primfaktoren
Der nichtdeterministische Algorithmus hat eine polynomiale Zeitkomplexität.
53
Die Klassen P und NP
P bezeichnet die Klasse der Probleme, die mit einem Algorithmus mit polynomialer
Zeitkomplexität gelöst werden können.
Zur Klasse P gehört das Problem des Primzahltests („PRIMES“).
NP bezeichnet die Klasse der Probleme, die mit einem nichtdeterministischen Algorithmus mit
polynomialer Zeitkomplexität gelöst werden können.
Jedes Problem aus P gehört auch zu NP.
Zur Klasse NP gehört auch das Problem der Primfaktorzerlegung („FACTORIZE“).
54
NP-vollständige Probleme
Ein Problem p* heißt NP-vollständig genau dann, wenn es in der Komplexitätsklasse NP liegt
(d.h. mit einem nichtdeterministischen Algorithmus mit polynomialer Komplexität gelöst
werden kann) und wenn jedes Problem p aus NP auf p* polynomial reduzierbar ist.
p
<=
<=
p
<=
p*
NP
p
NP-vollständige Probleme spielen bei der Klärung der Frage P=NP? eine zentrale Rolle. Wenn
es gelingt, ein NP-vollständiges Problem p* mit einem Algorithmus mit polynomialer
Komplexität zu lösen, dann ist die Aussage P=NP bewiesen. Denn, NP-Vollständigkeit bedeutet
ja, dass jedes Problem p aus NP auf p* polynomial reduzierbar ist. Aus einem polynomialen
Algorithmus für p* lässt sich dann ein polynomialer Algorithmus für jedes p aus NP erzeugen.
Zur Klärung der Frage P=NP? konzentriert man sich also auf das Lösen NP-vollständiger
Probleme.
55
Ein NP-vollständiges Problem
Hamilton-Problem:
Gegeben ist ein Graph mit seinen Knoten und Kanten. Gesucht ist eine Rundreise durch den
Graphen, in der jeder Knoten genau einmal vorkommt - nur Start- und Zielknoten kommen
genau zweimal vor. Eine solche Rundreise wird auch Hamiltonkreis genannt.
56
P = NP?
Es gibt inzwischen eine Vielzahl von Problemen, die als NP-vollständig nachgewiesen sind. Zu
diesen Problemen gehört das Hamilton-Problem.
Alle Versuche, ein NP-vollständiges Problem mit einem polynomialen Algorithmus zu lösen, sind
bisher fehlgeschlagen. Die NP-vollständigen Probleme erweisen sich also als "harte Nüsse" und
gelten als schwer lösbare Probleme.
Aufgrund der vielen fehlgeschlagenen Versuche, einen polynomialen Lösungsalgorithmus für
ein NP-vollständiges Problem zu finden, vermutet man, dass die Frage P=NP? negativ zu
beantworten ist.
Sollte es dennoch gelingen, ein NP-vollständiges Problem mit einem polynomialen Algorithmus
zu lösen, so lässt sich jedes andere Problem aus deer Klasse NP (als auch das
Faktorisierungsproblem) mit einem polynomialen Algorithmus lösen.

similar documents