algorithmische grundlagen einführung in das programmieren ... · zum messen der rechenzeit...
TRANSCRIPT
4 Algorithmen
Wir haben bisher Programme zur Losung verschiedener Probleme geschrieben.Jedes Programm basiert auf einer Idee (Algorithmus), die man in jederProgrammierprache aufschreiben kann.Beim Entwickeln von Algorithmen geht es u.a. darum, moglichst schnelleAlgorithmen mit wenig Speicherplatzbedarf zu finden.
In diesem Kapitel schauen wir uns zuerst an, wie man experimentell feststellt,wie
”schnell“ ein Algorithmus ist.
Anschließend schauen wir uns Algorithmen zum Sortieren an,die verbluffend schnell sind.
4. Algorithmen4.1 Rechenzeit4.2 Schnelles Sortieren
4.1 Rechenzeit von Programmen
Wir haben z.B. bei der Berechnung von Potenzen 2**n und bei der
Berechnung von Fibonacci-Zahlen gesehen, dass
verschiedene Programme/Funktionen fur ein Berechnungsproblem zwar
das gleiche Ergebnis berechnen, jedoch sehr unterschiedlich schnell sind.
Dieser Unterschied zeigte sich insbesondere dadurch, dass bei großeren
Eingaben der Unterschied immer großer wurde.
Wir wollen die Rechenzeit von Programmen bei großer werdenden
Eingaben messen und eine quantitative Aussage uber den Unterschied der
Rechenzeiten machen.
Diese Aussage muss allgemeiner sein als eine konkrete Voraussage der
Rechenzeit in Sekunden, da sie z.B. fur Rechner verschiedener
Geschwindigkeit nutzlich sein soll.
4.1.1
Zum Messen der Rechenzeit benutzen wir die Funktion time() aus dem Modultime.py, die die Zeit seit dem 1.1.1970 in Sekunden als float zuruckgibt.
Jede Messung sieht wie folgt aus:schaue auf die Uhrstarte die zu messende Funktion und warte, bis sie beendet istschaue auf die Uhrdie Differenz der Uhrzeiten ist die Rechenzeit
Wir werden eine Messreihe aus einer Reihe von Messungen durchfuhren, beidenen das Argument fur die Funktion immer großer wird.
Der Einfachheit halber wollen wir Funktionen betrachten,die nur von einem Argument abhangig sind.
Wir beginnen mit zwei Funktionen aus dem Modul potenzieren.py:
potLangsam(n,a=2) gibt a**n zuruck; das Ergebnis wird mit n-fachem Multipli-zieren berechnet
potSchnell(n,a=2) gibt a**n zuruck; das Ergebnis wird mit wiederholten Qua-drieren berechnet
4.1.2
#----------------------------------------------------------------------------------
# potenzieren.py
#----------------------------------------------------------------------------------
# Das Modul stellt zwei Funktion zur Berechnung von a**n bereit:
# potLangsam(n,a=2) und potSchnell(n,a=2).
#----------------------------------------------------------------------------------
# potLangsam(n,a=2) berechnet a**n duch n-fache Multiplikation von a.
# Das ist die langsame Art des Potenzierens.
def potLangsam(n,a=2):
erg = 1
for i in range(n):
erg = erg * a
return erg
#----------------------------------------------------------------------------------
# potSchnell(n,a=2) berechnet "a hoch n" mittels wiederholtem Quadrieren.
# Das ist die schnelle Art des Potenzierens.
def potSchnell(n,a=2):
if n==0: return 1
if n%2==1: return potSchnell(n-1,a)*a
b = potSchnell(n//2,a)
return b*b
4.1.3
# messreihe-v1.py
#---------------------------------------------------------------------------------------------------
# Liest von der Kommandozeile einen Startwert fur n (int) und eine Versuchsdauer (int) in Sekunden.
# Gibt die Zeit-Messungen der Funktion potLangsam aus dem Modul potenzieren.py aus.
# Die Argumente fur den Funktionsaufruf beginnen mit n+1 und werden bei jeder Messung
# um 1 erhoht, bis die Versuchsdauer erreicht wird.
#---------------------------------------------------------------------------------------------------
import sys, time, random
n = int(sys.argv[1])
versuchsdauer = int(sys.argv[2])
# Importiere das Modul mit der Funktion, die fur die Messreihe benutzt wird.
from potenzieren import potLangsam
# versuchsEnde ist die Zeit, bei der der Versuch beendet wird.
versuchsEnde = time.time() + versuchsdauer
# messEnde ist die Zeit der Beendigung der letzten Messung.
messEnde = 0
# Fuhre die Messreihe mit wachsendem n durch, bis die Zeit abgelaufen ist.
while messEnde < versuchsEnde:
# Bereite die Messung vor.
n += 1
# Rufe die Funktion auf und miss die verbrauchte Rechenzeit.
messAnfang = time.time()
potLangsam(n)
messEnde = time.time()
messErgebnis = messEnde - messAnfang
# Gib das Ergebnis der Messung aus.
print( '%d %f' % (n, messErgebnis) )
4.1.4
Wir messen die Rechenzeit mit Startwert 1000 fur 100 Sekundenund bekommen einen Haufen Daten zuruck.Die Ergebnisse des Experiments sind in der Graphik dargestellt.
# python messreihe-v1.py 1000 100
1000 0.000124
1001 0.000120
1002 0.000114
1003 0.000115
1004 0.000237
1005 0.000123
1006 0.000132
1007 0.000119
1008 0.000128
1009 0.000128
...
21428 0.014031
21429 0.013911
21430 0.014911
21431 0.013716
21432 0.013503
21433 0.015214
Kann man irgendwas erkennen?
4.1.5
Wir wollen unseren Versuchsaufbau auch fur andere Funktionen aus anderenModulen benutzen konnen.Dafur nehmen wir ein paar kleine Anderungen am Programm vor.
Der Modulname und der Funktionsname soll von der Kommandozeile gelesenwerden.
Wir machen etwas, was man eigentlich nicht machen soll:wir benutzen die Python-Funktion exec(s).
Ihr Aufruf fuhrt die Programmzeile s (string) aus.
Beispielprogramm versuch.py:
modulname = sys.argv[1]
funktionsname = sys.argv[2]
n = int(sys.argv[3])
exec('import ' + modulname)
exec(modulname + '.' + funktionsname + '(n)')
Bei Aufruf python versuch.py potenziere potLangsam werden(1) die drei Argumente von der Kommandozeile gelesen(2) import potenziere ausgefuhrt(3) potenziere.potLangsam(n) ausgefuhrt
4.1.6
# messreihe-v2.py
#-----------------------------------------------------------------------------------
# Wie messreihe-v1.py, aber liest folgendes von der Kommandozeile:
# einen Modulnamen (string), einen Funktionsnamen aus diesem Modul (string)
# einen Startwert fur n (int), eine Versuchsdauer (int) in Sekunden.
#-----------------------------------------------------------------------------------
import sys, time
modulname = sys.argv[1]
funktionsname = sys.argv[2]
n = int(sys.argv[3])
versuchsdauer = int(sys.argv[4])
# Importiere das Modul mit der Funktion, die fur die Messreihe benutzt wird.
exec('from ' + modulname + ' import ' + funktionsname)
# versuchsEnde ist die Zeit, bei der der Versuch beendet wird.
versuchsEnde = time.time() + versuchsdauer
# messEnde ist die Zeit der Beendigung der letzten Messung.
messEnde = 0
# Fuhre die Messreihe mit wachsendem n durch, bis die Zeit abgelaufen ist.
while messEnde < versuchsEnde:
# Bereite die Messung vor.
n += 1
# Rufe die Funktion auf und miss die verbrauchte Rechenzeit.
messAnfang = time.time()
exec(funktionsname + '(n)')
messEnde = time.time()
messErgebnis = messEnde - messAnfang
# Gib das Ergebnis der Messung aus.
print( '%d %f' % (n, messErgebnis) )4.1.7
Wir fuhren (fast) den gleichen Versuch mit der schnellen Potenzierfunktiondurch und bekommen noch mehr Daten zuruck.
# python messreihe-v2.py potenzieren potSchnell 1000000 25
1000000 0.005106
1000001 0.004900
1000002 0.005005
1000003 0.004979
1000004 0.004953
1000005 0.004787
...
1005177 0.004764
1005178 0.004735
1005179 0.004802
1005180 0.004742
1005181 0.004771
1005182 0.004751
1005183 0.004791
1005184 0.004708
1005185 0.004743
1005186 0.004723
Kann man irgendwas erkennen?
4.1.8
Bei”ahnlich großen“ Zahlen unterscheiden sich die Rechenzeiten kaum.
Deshalb betrachtet man die Rechenzeit eines Programms nicht abhangig vomZahlenwert der Argumente,sondern von ihrem Speicherbedarf – er wird als Lange bezeichnet.
Zahlen (int) werden binar gespeichert – als Folge von 0/1-Bits.
Die Lange einer Zahl ist die Anzahl ihrer Bits.
Um aus einer Zahl m eine Zahl zu machen,deren Lange ein Bit großer ist,
kann man m mit 2 multiplizieren.
Statt die Rechenzeit fur Eingabewertem,m ` 1,m ` 2,m ` 3, . . .
zu ermitteln, machen wir es nun furm, 2 ¨m, 2 ¨ 2 ¨m, . . . , 2i ¨m, . . .
Damit erwischen wir dann ab Startwert m jeweils eine Zahl, deren Lange 1großer ist als bei der vorigen Zahl.
Im Programm mussen wir dafur nur die Zeile n += 1 durch n *= 2 ersetzen.
Das ist dann messreihe-v3.py. 4.1.9
Wir messen die Rechenzeit von potLangsam mit Startwert 10 fur 100Sekunden.Es wird also 2n berechnet fur n “ 10, 20, 40, . . ..Wir bekommen nicht mehr besonders viele Daten zuruck.
# python messreihe-v3.py potenzieren potLangsam 10 100
10 0.000022
20 0.000018
40 0.000015
80 0.000018
160 0.000026
320 0.000043
640 0.000083
1280 0.000190
2560 0.000425
5120 0.001185
10240 0.003683
20480 0.011922
40960 0.040362
81920 0.146418
163840 0.575278
327680 2.267954
655360 9.310225
1310720 37.495275
2621440 153.379177
# python messreihe-v3.py potenzieren potSchnell 10 100
10 0.000037
20 0.000030
40 0.000025
80 0.000026
160 0.000027
320 0.000027
...
1310720 0.005600
2621440 0.011480
5242880 0.023633
10485760 0.049603
...
2684354560 18.744394
5368709120 38.501850
10737418240 81.293822
Kann man irgendwas erkennen?4.1.10
Wir zeichnen die Messergebnisse mal als Funktionsgraphen . . .
. . . und konnen nichts erkennen, weil die Achsen sich so stark unterscheiden . . .
4.1.11
Also zeichnen wir die Messergebnisse gemeinsam in ein Koordinatensystem . . .
Man sieht, dass potSchnell viel schnellerals potLangsam ist . . . aber das wusstenwir auch vorher schon.
Der offensichtlichste Unterschied zwischen den beiden Funktionen ist ihre Steigung.
Wie kann man diesen Unterschied quantifizieren?
Dazu vergleichen wir nun die Rechenzeiten von aufeinanderfolgenden Messungen
und geben den Faktor an, um den die Rechenzeit bei der zweiten Messung großer ist.
4.1.12
# expoTest-int.py
#-----------------------------------------------------------------------------------
# Wie messreihe-v3.py, aber:
# Bei jeder Messung wird der Faktor ausgegeben, um den die Rechenzeit im Vergleich
# zur vorherigen Messung gewachsen ist.
#-----------------------------------------------------------------------------------
import sys, time
# Kommandozeile lesen und Funktion aus Modul exportieren: wie gehabt ...
versuchsEnde = time.time() + versuchsdauer # die Zeit, bei der der Versuch beendet wird
messEnde = 0 # die Zeit der Beendigung der letzten Messung
letzteMessung = 0 # das Ergebnis der letzten Messung
print( ' %11s | %12s | %6s \n%s' % ('n', ' Rechenzeit', 'Faktor', ' '+'-'*36) )
# Fuhre die Messreihe mit wachsendem n durch, bis die Zeit abgelaufen ist.
while messEnde < versuchsEnde:
# Bereite die Messung vor.
n *= 2
# Rufe die Funktion auf und miss die verbrauchte Rechenzeit.
messAnfang = time.time()
exec(funktionsname + '(n)')
messEnde = time.time()
messErgebnis = messEnde - messAnfang
# Gib das Ergebnis der Messung aus.
if letzteMessung>0:
print( ' %12d | %10.3f | %6.2f ' % (n, messErgebnis, messErgebnis/letzteMessung) )
# Bereite die nachste Messung vor.
letzteMessung = messErgebnis
4.1.13
Jetzt sehen wir tatsachlich mal einen quantitativen Unterschied zwischen denRechenzeiten der beiden Potenzierfunktionen.
python expoTest-int.py \
potenzieren potLangsam 10000 3600
n | Rechenzeit | Faktor
--------------------------------
40000 | 0.031 | 2.27
80000 | 0.113 | 3.63
160000 | 0.434 | 3.85
320000 | 1.690 | 3.89
640000 | 7.062 | 4.18
1280000 | 27.944 | 3.96
2560000 | 113.503 | 4.06
5120000 | 477.411 | 4.21
10240000 | 1865.149 | 3.91
python expoTest-int.py \
potenzieren potSchnell 1000000 100
n | Rechenzeit | Faktor
------------------------------------
4000000 | 0.020 | 2.21
8000000 | 0.039 | 1.98
16000000 | 0.078 | 2.01
32000000 | 0.160 | 2.06
64000000 | 0.331 | 2.07
128000000 | 0.684 | 2.06
256000000 | 1.410 | 2.06
512000000 | 2.955 | 2.10
1024000000 | 6.277 | 2.12
2048000000 | 13.130 | 2.09
4096000000 | 27.160 | 2.07
8192000000 | 56.722 | 2.09
Wenn die Lange der Eingabe um 1 vergroßert wird, dannwachst bei potLangsam die Rechenzeit mit Faktor 4, undbei potSchnell wachst die Rechenzeit mit Faktor etwa 2.1.
4.1.14
Was haben wir bis jetzt?
Wir suchen die Funktion t,die als Argument die Lange ` einer Zahl bekommtund als Funktionswert tp`q die maximale Rechenzeit von potLangsam
bei Eingabe einer Zahl der Lange ` hat.
Wir haben experimentell festgestellt, dass tp`` 1q “ 4 ¨ tp`q ist (fur alle `).
Wenn tpkq “ w ist fur irgendein k und irgendeinen Wert w , dann wissen wir:
tpkq “ w
tpk ` 1q “ 4 ¨ w
tpk ` 2q “ 4 ¨ 4 ¨ w
......
...
tpk ` iq “ 4i ¨ w
Also gibt es eine Konstante c mit w “ 4k ¨ c , so dassdie experimentell geschatzte Rechenzeit tp`q “ c ¨ 4` ist.
4.1.15
Rechenzeitfunktionen
Bei Rechenzeitfunktionen ignorieren wir konstante Faktoren und”kleine“
Summanden. Mathematisch gesprochen: man interessiert sich nur fur dieGroßenordnung des Wachstums der Funktion.
Fur die experimentell geschatzte Rechenzeit tp`q “ c ¨ 4` von potLangsam
erhalten wir dann die Rechenzeitfunktion T p`q “ 4`.
Fur potSchnell erhalten wir so die Rechenzeitfunktion T p`q “ 2.1`.
Großenordnung des Wachstums
Beschreibung Rechenzeitfunktion T p`q
konstant 1
logarithmisch log `
linear `
quasi-linear ` ¨ log `
quadratisch `2
kubisch `3
exponentiell 2`, 3`, . . .
Ganz grob sagt man:
potLangsam und potSchnell habenexponentielle Rechenzeit.
4.1.16
Zusammenfassung:
Test auf exponentielle Rechenzeit
§ Miss die Rechenzeit der Funktion fur Eingaben aufeinanderfolgender Lange.Sind die Eingaben Zahlen, dann verdoppelt man sie fur jede Messung.Sind die Eingaben Arrays,
dann vergroßert man das Array fur jede Messung um einen Eintrag.
§ Bestimme den Faktor z ,um den sich die Rechenzeit von Messung zu Messung vergroßert.
§ Wenn der Faktor z mindestens 2 ist,dann hat die Funktion exponentielle Rechenzeit: T p`q “ z`,
d.h. T p`q “ p2`qlog2 z .
§ Wenn der Faktor z kleiner als 2 ist,dann ist die Rechenzeit der Funktion schneller als exponentiell.
Wir haben gesehen: potLangsam und potSchnell haben exponentielle Rechenzeit.
Wir werden den Test jetzt fur verschiedene Funktionen zur Berechnung derFibonacci-Zahlen durchfuhren.
4.1.17
# fibonacci.py
#-------------------------------------------------------------------------------
# Berechnung der Fibonacci-Funktion mit schnellen und langsamen Funktionen.
#-------------------------------------------------------------------------------
# fiboDirekt(n) gibt fib_n zuruck.
# Die Berechnung erfolgt iterativ durch Berechnung von fib_0, fib_1, ..., fib_n.
def fiboDirekt(n):
f = [ 0 , 1 ]
for i in range(2,n+1): f[i%2] = f[0] + f[1]
return f[n%2]
#-------------------------------------------------------------------------------
# fiboRekursiv(n) gibt fib_n zuruck.
# Die Berechnung erfolgt rekursiv uber die Definition der Fibonacci-Zahlen.
def fiboRekursiv(n):
if n==0 or n==1: return n
else: return fiboRekursiv(n-1) + fiboRekursiv(n-2)
#-------------------------------------------------------------------------------
# fibo(b) gibt (fib_{b-1},fib_{b}) zuruck. Wird von fiboSchnell benutzt.
# Es gilt: fib_{2n} = fib_{n}*fib_{n-1} + fib_{n+1}*fib_{n}
# fib_{2n+1} = fib_{n}**2 + fib_{n+1}**2
def fibo(b):
if b<=1: return (0,b)
if b%2==1:
(x,y) = fibo(b//2)
return ( y*x+(x+y)*y, y**2+(x+y)**2 )
(x,y) = fibo(b//2)
return ( x*x+y*y, y*(2*x+y) )
#-------------------------------------------------------------------------------
# fiboSchnell(n) gibt fib_n zuruck.
def fiboSchnell(n): return fibo(n)[1]4.1.18
fiboDirekt hat exponentielle Rechenzeit
# python expoTest-int.py fibonacci fiboDirekt 1000 300
n | Rechenzeit | Faktor
------------------------------------
4000 | 0.002 | 2.48
8000 | 0.004 | 2.30
16000 | 0.012 | 2.63
32000 | 0.030 | 2.61
64000 | 0.078 | 2.61
128000 | 0.267 | 3.40
256000 | 0.895 | 3.36
512000 | 2.654 | 2.97
1024000 | 10.895 | 4.10
2048000 | 42.981 | 3.95
4096000 | 169.500 | 3.94
8192000 | 672.399 | 3.97
Es sieht aus, dass fiboDirekt Rechenzeitfunktion T p`q “ 4` hat.
4.1.19
fiboRekursiv hat exponentielle Rechenzeit
Die Zeitmessung von fiboRekursiv fliegt einem gleich um die Ohren.
Die Rechenzeit ist tatsachlich doppelt exponentiell,
also sowas wie T p`q “ 22` .
# python expoTest-int.py fibonacci fiboRekursiv 5 100
n | Rechenzeit | Faktor
------------------------------------
20 | 0.005 | 34.35
40 | 31.082 | 6801.26
...
4.1.20
fiboSchnell hat exponentielle Rechenzeit
fiboSchnell hat Rechenzeit T p`q “ 3`.
Das ist auch exponentiell, aber deutlich schneller als fiboDirekt.
# python expoTest-int.py fibonacci fiboSchnell 1000 200
n | Rechenzeit | Faktor
------------------------------------
4000 | 0.000 | 0.01
8000 | 0.000 | 1.01
16000 | 0.000 | 2.05
32000 | 0.001 | 3.19
64000 | 0.002 | 2.42
128000 | 0.007 | 3.02
256000 | 0.022 | 3.00
512000 | 0.060 | 2.79
1024000 | 0.119 | 1.98
2048000 | 0.235 | 1.98
4096000 | 0.762 | 3.24
8192000 | 2.206 | 2.90
16384000 | 6.048 | 2.74
32768000 | 17.911 | 2.96
65536000 | 54.247 | 3.03
131072000 | 163.394 | 3.01
Es sieht aus, dass fiboDirekt Rechenzeitfunktion T p`q “ 3` hat.4.1.21
Beim Test der Funktion log aus dem Modul testfunktionen.py
erhalten wir einen Faktor, der kleiner ist als 2.
# python expoTest-int.py testfunktionen log 100000 0.0003
n | Rechenzeit | Faktor
------------------------------------
400000 | 0.000 | 0.55
800000 | 0.000 | 0.91
1600000 | 0.000 | 1.00
3200000 | 0.000 | 0.90
...
3276800000 | 0.000 | 1.11
6553600000 | 0.000 | 1.00
13107200000 | 0.000 | 1.00
26214400000 | 0.000 | 1.00
Die Rechenzeit der Funktion ist nicht exponentiell,
sondern lasst sich durch eine kleinere Rechenzeitfunktion abschatzen:
ein Polynom T p`q “ ` k .4.1.22
Test auf polynomielle Rechenzeit
§ Miss die Rechenzeit der Funktion fur Eingaben,deren Lange sich von Messung zu Messung verdoppelt.
Sind die Eingaben Zahlen, dann quadriert man sie fur die folgende Messung.Sind die Eingaben Arrays,
dann verdoppelt man die Array-Große fur die folgende Messung.
§ Bestimme den Faktor z ,um den sich die Rechenzeit von Messung zu Messung vergroßert.
§ Wenn der Faktor z mindestens 2 ist,dann hat die Funktion polynomielle Rechenzeit: T p`q “ ` log2 z .
Wir werden uns Beispiele dazu anschauen.
4.1.23
Aus expoTest-int.py erhalten wir polyTest-int.py,indem wir die Zeile n *= 2 andern zu n *= n.Wir werden den Polynomialzeittest an folgenden Funktionen ausprobieren.
# testfunktionen.py
#------------------------------------------------------------------------------------
# ggT(a,b) gibt den großten gemeinsamen Teiler von a und b zuruck.
def ggT(a,b):
while not a%b==0: a, b = b, a%b
return b
#------------------------------------------------------------------------------------
# ggTwrapper(a) ruft die Funktion ggT (s.o.) auf, hat aber nur ein Argument,
# damit man sie mit polynomialzeit-test.py benutzen kann.
# Man weiß, dass die Berechnung ggT(a,b) am langsten dauert, wenn a und b
# aufeinanderfolgende Fibonacci-Zahlen sind. Diese unterscheiden sich etwa um den
# Faktor 1.61. Das habe ich hier versucht mit ganzzahligen Operationen hinzubekommen.
def ggTwrapper(a): return ggT(a*161//100, a)
#------------------------------------------------------------------------------------
# Funktion log(n,b=2) gibt den
# ganzzahligen Logarithmus von n zur Basis b zuruck.
def log(n,b=2):
ergebnis = 0
while n > 1:
ergebnis += 1
n = n//b
return ergebnis
#------------------------------------------------------------------------------------
# quadrat(a) gibt a*a zuruck.
def quadrat(a): return a*a
4.1.24
# python polyTest-int.py testfunktionen log 100000000 100
Argumentlange | Rechenzeit | Faktor
-------------------------------------
33 | 0.000 | 0.98
65 | 0.000 | 1.63
129 | 0.000 | 2.68
257 | 0.001 | 1.82
513 | 0.003 | 3.89
1025 | 0.008 | 3.23
2049 | 0.025 | 3.02
4097 | 0.093 | 3.66
8193 | 0.207 | 2.23
16385 | 0.629 | 3.04
32769 | 2.742 | 4.36
65537 | 9.815 | 3.58
131073 | 39.272 | 4.00
262145 | 155.463 | 3.96
Der Faktor, um den sich die Rechenzeitverlangert, ist etwa 4.
Also hat log die RechenzeitfunktionT p`q “ `2.
4.1.25
# python polyTest-int.py testfunktionen ggTwrapper 1000000000 100
Argumentlange | Rechenzeit | Faktor
-------------------------------------
37 | 0.000 | 0.54
73 | 0.000 | 1.81
145 | 0.000 | 0.84
289 | 0.000 | 2.06
577 | 0.000 | 0.58
1153 | 0.000 | 1.01
2305 | 0.000 | 1.26
4609 | 0.000 | 1.50
9217 | 0.000 | 1.57
18433 | 0.001 | 1.78
36865 | 0.001 | 2.16
73729 | 0.001 | 1.25
147457 | 0.002 | 1.20
294913 | 0.005 | 3.29
589825 | 0.005 | 1.02
1179649 | 0.011 | 1.95
2359297 | 0.021 | 1.99
4718593 | 0.043 | 2.05
Der Faktor, um den sich die Rechenzeitverlangert, ist etwa 2.
Also hat ggT die RechenzeitfunktionT p`q “ `.
4.1.26
# python polyTest-int.py testfunktionen quadrat 1000000000 100
Arg.lange | Rechenzeit | Faktor
--------------------------------------
37 | 0.000 | 0.63
73 | 0.000 | 1.31
145 | 0.000 | 0.91
289 | 0.000 | 1.07
577 | 0.000 | 1.17
1153 | 0.000 | 1.55
2305 | 0.000 | 2.25
4609 | 0.000 | 2.18
9217 | 0.001 | 2.80
18433 | 0.002 | 1.95
36865 | 0.003 | 2.08
73729 | 0.010 | 2.92
147457 | 0.046 | 4.69
294913 | 0.085 | 1.85
589825 | 0.264 | 3.12
1179649 | 0.775 | 2.93
2359297 | 2.375 | 3.06
4718593 | 6.834 | 2.88
Der Faktor, um den sich die Rechenzeitverlangert, ist etwa 3.
Also hat n*n die RechenzeitfunktionT p`q “ ` 1.58.
4.1.27
Polynomialzeittest fur Funktionen mit einem Array als
Argument
Statt einer Zahl, die von Messung zu Messung quadriert wird,
erzeugt man ein Array, das von Messung zu Messung verdoppelt wird.
Wenn das Array Lange n haben soll, dann erzeugen wir zuerst das Array
[0, 1, 2, 3, ..., n].
Das ist der Ruckgabewert der Python-Funktion range(n).
Anschließend mischen wir das Array mit random.shuffle(array).
Wir machen das,
da wir die Rechenzeit von Sortierprogrammen messen wollen.
Auf bereits sortierten Arrays konnen Sortierprogramme ungewohnlich
schnell sein.
4.1.28
# polyTest-array.py
#-------------------------------------------------------------------------------------------------
import sys, time, random
# Lies die Argumente von der Kommandozeile.
modulname = sys.argv[1]
funktionsname = sys.argv[2]
n = int(sys.argv[3])
versuchsdauer = int(sys.argv[4])
# Importiere das Modul mit der Funktion, die fur das Experiment benutzt wird.
exec('from ' + modulname + ' import ' + funktionsname)
versuchsEnde = time.time() + versuchsdauer # die Zeit, die Messreihe zu beenden
messEnde = 0 # messEnde ist die Zeit der Beendigung der letzten Messung
letzteMessung = 0 # letzteMessung ist die zuletzt gemessene Rechenzeit.
# Fuhre die Messreihe mit wachsender Sample-Große n durch, bis die Zeit abgelaufen ist.
while messEnde < versuchsEnde:
# Erzeuge das Argument-Sample fur den Funktionsaufruf der Messung.
sample = range(n)
random.shuffle(sample)
# Fuhre die Messung durch: rufe die Funktion auf und miss die verbrauchte Rechenzeit.
messAnfang = time.time()
exec(funktionsname + '(sample)')
messEnde = time.time()
neueMessung = messEnde - messAnfang
# Gib das Ergebnis der Messung aus.
if letzteMessung>0: print('Zeit %6.3f, Faktor %2.2f' % (neueMessung, neueMessung/letzteMessung))
# Bereite die nachste Messung vor.
letzteMessung = neueMessung
n = 2*n4.1.29
# arrayfunktionen.py
#----------------------------------------------------------------------
# Funktionen zum Suchen und Sortieren mit Arrays.
#----------------------------------------------------------------------
#----------------------------------------------------------------------
# minIndexSuche(liste, start=0) gibt den Index des kleinsten Eintrags
# im Array liste[start:] zuruck.
def minIndexSuche(liste, start=0):
minIndex = start
for i in range(start+1,len(liste)):
if liste[i] < liste[minIndex]: minIndex = i
return minIndex
#----------------------------------------------------------------------
# minsort(liste) sortiert liste (Seiteneffekt!) mittels wiederholter
# Minimumssuche.
def minsort(liste):
for i in range(len(liste)-1):
# Tausche den kleinsten Eintrag in liste[i:] an Stelle i.
indexOfMin = minIndexSuche(liste, i)
liste[i], liste[indexOfMin] = liste[indexOfMin], liste[i]
#---------------------------------------------------------------------
# pythonsSorted(liste) ruft Python's Funktion sorted() auf.
def pythonsSorted(liste):
return sorted(liste)
4.1.30
#----------------------------------------------------------------------
# binSuche(liste, ziel) sucht mittels binarer Suche nach ziel im
# (sortierten) Array liste.
# Falls ziel gefunden wird, dann wird der Index von ziel zuruckgegeben.
# Falls ziel nicht gefunden wird, dann wird -1 zuruckgegeben.
# (Der Default-Wert fur ziel wird gebraucht, damit polyTest-array.py
# benutzt werden kann.)
def binSuche(liste, ziel=123):
links = 0
rechts = len(liste)-1
mitte = (links+rechts)//2
while not liste[mitte]==ziel and links<rechts:
if liste[mitte]>ziel: rechts = mitte-1
else: links = mitte+1
mitte = (links+rechts)//2
if liste[mitte]==ziel: return mitte
else: return -1
4.1.31
python polyTest-array.py arrayfunktionen minIndexSuche 100 40
Arg.lange | Rechenzeit | Faktor
--------------------------------------
200 | 0.000 | 0.56
400 | 0.000 | 1.38
800 | 0.000 | 1.98
1600 | 0.000 | 1.32
3200 | 0.000 | 1.87
6400 | 0.001 | 1.97
12800 | 0.002 | 2.01
25600 | 0.004 | 2.23
51200 | 0.007 | 1.83
102400 | 0.027 | 3.78
204800 | 0.049 | 1.81
409600 | 0.076 | 1.56
819200 | 0.174 | 2.30
1638400 | 0.423 | 2.43
3276800 | 1.007 | 2.38
6553600 | 2.002 | 1.99
13107200 | 4.334 | 2.17
26214400 | 9.595 | 2.21
Der Faktor, um den sich die Rechenzeitverlangert, ist etwa 2.
Also hat minIndexSuche dieRechenzeitfunktion T p`q “ `.
4.1.32
# python polyTest-array.py arrayfunktionen binSuche 100000 100
Arg.lange | Rechenzeit | Faktor
--------------------------------------
200000 | 0.000 | 0.63
400000 | 0.000 | 0.91
800000 | 0.000 | 0.99
1600000 | 0.000 | 0.88
3200000 | 0.000 | 1.04
6400000 | 0.000 | 1.06
12800000 | 0.000 | 0.93
25600000 | 0.000 | 1.22
51200000 | 0.000 | 0.87
102400000 | 0.000 | 1.07
Der Faktor, um den sich die Rechenzeitverlangert, ist etwa 1.
Also hat binSuche eineRechenzeitfunktion, die kleiner alsT p`q “ ` ist!
4.1.33
python polyTest-array.py arrayfunktionen minsort 100 100
Arg.lange | Rechenzeit | Faktor
--------------------------------------
200 | 0.002 | 3.28
400 | 0.007 | 3.74
800 | 0.025 | 3.52
1600 | 0.079 | 3.15
3200 | 0.219 | 2.77
6400 | 1.038 | 4.74
12800 | 4.054 | 3.91
25600 | 15.986 | 3.94
51200 | 64.858 | 4.06
102400 | 274.798 | 4.24
Der Faktor, um den sich die Rechenzeitverlangert, ist etwa 4.
Also hat minsort dieRechenzeitfunktion T p`q “ `2.
4.1.34
Python hat auch die eingebaute Sortierfunktion sorted().Wir haben sie als pythonsSorted() in arrayfunktionen.py eingebaut.
# python polyTest-array.py arrayfunktionen pythonsSorted 100 100
Arg.lange | Rechenzeit | Faktor
--------------------------------------
200 | 0.000 | 0.77
400 | 0.000 | 1.54
800 | 0.000 | 1.70
1600 | 0.000 | 1.98
3200 | 0.001 | 2.22
6400 | 0.002 | 2.02
12800 | 0.003 | 1.69
25600 | 0.007 | 2.16
51200 | 0.014 | 2.06
102400 | 0.027 | 1.86
204800 | 0.064 | 2.43
409600 | 0.295 | 4.58
819200 | 0.508 | 1.72
1638400 | 1.161 | 2.28
3276800 | 2.686 | 2.31
6553600 | 6.514 | 2.42
13107200 | 15.389 | 2.36
26214400 | 35.654 | 2.32
52428800 | 82.343 | 2.31
Der Faktor, um den sich die Rechenzeitverlangert, ist etwa 2.3.
Damit fallt sorted aus dem bisherigenRahmen.
sorted hat die RechenzeitfunktionT p`q “ ` ¨ log `.
4.1.35
Zusammenfassung
Das Wachstum der Rechenzeit von Programmen gibt verlassliche Auskunftuber die
”Schnelligkeit“ des Programms.
Wir haben gesehen, wie man es experimentell abschatzen kann.Es gibt auch ein mathematisches Modell,
mit dem man (im Prinzip) die gleichen Ergebnisse erzielt.
Die folgende Tabelle enthalt typische Rechenzeitfunktionen mit dem Namenihres Wachstums, und den Zuwachs an Rechenzeit eines Programms, das beiEingabegroße m wenige Sekunden braucht, bei einer Eingabe der Große100 ¨m.
Wachstum der Verlangerung der
Rechenzeitfunktion Beispiel Rechenzeit (s.o.)
linear T p`q “ ` wenige Minuten
quasi-linear T p`q “ ` ¨ log ` wenige Minuten
quadratisch T p`q “ ` 2 einige Stunden
kubisch T p`q “ ` 3 ein paar Wochen
exponentiell T p`q “ 2` ewig4.1.36