Algorithmische Grundlagen - Sommersemester 2016 – VL10

Werbung
Algorithmische Grundlagen
Sommersemester 2016 – VL10
Martin Mundhenk
Uni Jena, Institut für Informatik
20. Juni 2016
4 Algorithmen
Wir haben bisher Programme zur Lösung verschiedener Probleme geschrieben.
Jedes Programm basiert auf einer Idee (Algorithmus), die man in jeder
Programmierprache aufschreiben kann.
Beim Entwickeln von Algorithmen geht es u.a. darum, möglichst schnelle
Algorithmen mit wenig Speicherplatzbedarf zu finden.
In diesem Kapitel schauen wir uns zuerst an, wie man feststellt, wie schnell“
”
ein Algorithmus ist.
Anschließend schauen wir uns Algorithmen zum Sortieren an, die (viel)
schneller als das Sortieren durch wiederholte Suche des Minimums ist.
Abschließend betrachten wir noch Algorithmen zur Wegesuche in Graphen.
4. Algorithmen
4.1 Rechenzeit
4.2 Schnelles Sortieren
4.3 Suche nach Wegen in Graphen
4.1 Rechenzeit von Programmen
und Zeit-Komplexität von Problemen
Wir haben z.B. bei der Berechnung von Fibonacci-Zahlen gesehen,
dass verschiedene Programme sehr unterschiedlich lang brauchen.
Wir wollen diese Zeit messen.
#--------------------------------------------# miss-fib.py
# Misst die Rechenzeit fuers rekursive Berechnen
# der Fibonaccizahlen.
# time.time() gibt die aktuelle Zeit an.
#-------------------------------------------import stdio, sys, time, fibo_rekursiv
oberschranke = int(sys.argv[1])
for n in range(1,50):
anfangszeit = time.time()
fibo_rekursiv.fib(n)
endzeit = time.time()
rechenzeit = endzeit - anfangszeit
stdio.writef('%d %f\n', n, rechenzeit)
if rechenzeit > oberschranke: break
# python miss-fib.py 10
# 1 0.000002
# 2 0.000002
# 3 0.000002
...
# 15 0.000405
# 16 0.000654
# 17 0.001052
...
# 32 1.441119
# 33 2.333346
# 34 3.771449
# 35 6.112324
# 36 9.876957
# 37 16.031817
Beobachtungen
Die Rechenzeit von Programmen wächst mit der Größe der Eingabe.
Beispiel 1: Berechnung der Fibonacci-Zahlen
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Es wird die Rechenzeit in Sekunden
abhängig vom Eingabewert n dargestellt.
Hängt die Kurve vom Rechner ab?
4.1.2
Beobachtungen
Die Rechenzeit von Programmen wächst mit der Größe der Eingabe.
Beispiel 1: Berechnung der Fibonacci-Zahlen
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Es wird die Rechenzeit in Sekunden
abhängig vom Eingabewert n dargestellt.
Hängt die Kurve vom Rechner ab?
Der grobe Verlauf“ der Kurven ist gleich.
”
4.1.2
Beobachtungen
Die Rechenzeit von Programmen wächst mit der Größe der Eingabe.
Beispiel 1: Berechnung der Fibonacci-Zahlen
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Es wird die Rechenzeit in Sekunden
abhängig vom Eingabewert n dargestellt.
Hängt die Kurve vom Rechner ab?
Der grobe Verlauf“ der Kurven ist gleich.
”
4.1.2
Beobachtungen
Die Rechenzeit von Programmen wächst mit der Größe der Eingabe.
Beispiel 1: Berechnung der Fibonacci-Zahlen
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Es wird die Rechenzeit in Sekunden
abhängig vom Eingabewert n dargestellt.
Hängt die Kurve vom Rechner ab?
Der grobe Verlauf“ der Kurven ist gleich.
”
Er ist ähnlich der Kurve von f pnq “ 2n .
4.1.2
Beobachtungen
Die Rechenzeit von Programmen wächst mit der Größe der Eingabe.
Beispiel 1: Berechnung der Fibonacci-Zahlen
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Es wird die Rechenzeit in Sekunden
abhängig vom Eingabewert n dargestellt.
Hängt die Kurve vom Rechner ab?
Der grobe Verlauf“ der Kurven ist gleich.
”
Er ist ähnlich der Kurve von f pnq “ 2n .
Und ziemlich genau der der Kurve von f pnq “ 0.00000155 ¨ 1.61n
4.1.2
Beobachtungen
Die Rechenzeit von Programmen wächst mit der Größe der Eingabe.
Beispiel 1: Berechnung der Fibonacci-Zahlen
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Es wird die Rechenzeit in Sekunden
abhängig vom Eingabewert n dargestellt.
Hängt die Kurve vom Rechner ab?
Der grobe Verlauf“ der Kurven ist gleich.
”
Er ist ähnlich der Kurve von f pnq “ 2n .
Und ziemlich genau der der Kurve von f pnq “ 0.00000155 ¨ 1.61n
bzw. f pnq “ 0.0000004 ¨ 1.61n .
4.1.2
Beobachtungen
Die Rechenzeit von Programmen wächst mit der Größe der Eingabe.
Beispiel 1: Berechnung der Fibonacci-Zahlen
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Es wird die Rechenzeit in Sekunden
abhängig vom Eingabewert n dargestellt.
Hängt die Kurve vom Rechner ab?
Der grobe Verlauf“ der Kurven ist gleich.
”
Er ist ähnlich der Kurve von f pnq “ 2n .
Und ziemlich genau der der Kurve von f pnq “ 0.00000155 ¨ 1.61n
bzw. f pnq “ 0.0000004 ¨ 1.61n .
Alle Kurven sind anscheinend welche von Funktionen f pnq “ c ¨ 1.61n
für verschiedene c.
n
Deshalb sagen wir: die Rechenzeit von fibpnq ist „ 1.61 .
4.1.2
Beispiel 2: Quadrieren einer Matrix
def quadratM(A):
C = stdarray.create2D(len(A),len(A),0)
for z in range(len(A)):
for s in range(len(A)):
for i in range(len(A)):
C[z][s] += A[z][i]*A[i][s]
return C
Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und
Spalten, wobei wir größer werdende Werte in die Matrix eintragen.
Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe
”
der zu multiplizierenden Zahlen ab.
4.1.3
Beispiel 2: Quadrieren einer Matrix
def quadratM(A):
C = stdarray.create2D(len(A),len(A),0)
for z in range(len(A)):
for s in range(len(A)):
for i in range(len(A)):
C[z][s] += A[z][i]*A[i][s]
return C
Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und
Spalten, wobei wir größer werdende Werte in die Matrix eintragen.
Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe
”
der zu multiplizierenden Zahlen ab.
Messen wir dagegen die Zeit abhängig von der Seitenlänge der Matrix, steigt
die Rechenzeit stark an.
4.1.3
Beispiel 2: Quadrieren einer Matrix
def quadratM(A):
C = stdarray.create2D(len(A),len(A),0)
for z in range(len(A)):
for s in range(len(A)):
for i in range(len(A)):
C[z][s] += A[z][i]*A[i][s]
return C
Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und
Spalten, wobei wir größer werdende Werte in die Matrix eintragen.
Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe
”
der zu multiplizierenden Zahlen ab.
Messen wir dagegen die Zeit abhängig von der Seitenlänge der Matrix, steigt
die Rechenzeit stark an.
Wir vermuten, dass die Kurve der von f pnq “ n3 ähnelt.
Die Rechenzeit von quadratMpAq ist „ n3 . Die Eingabegröße n ist dabei die
Seitenlänge der Matrix A.
4.1.3
Beispiel 2: Quadrieren einer Matrix
def quadratM(A):
C = stdarray.create2D(len(A),len(A),0)
for z in range(len(A)):
for s in range(len(A)):
for i in range(len(A)):
C[z][s] += A[z][i]*A[i][s]
return C
Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und
Spalten, wobei wir größer werdende Werte in die Matrix eintragen.
Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe
”
der zu multiplizierenden Zahlen ab.
Messen wir dagegen die Zeit abhängig von der Seitenlänge der Matrix, steigt
die Rechenzeit stark an.
Wir vermuten, dass die Kurve der von f pnq “ n3 ähnelt.
Die Rechenzeit von quadratMpAq ist „ n3 . Die Eingabegröße n ist dabei die
Seitenlänge der Matrix A.
4.1.3
Beispiel 3: Sortieren eines Arrays mit MinSort
def minsort(liste):
for i in range(len(liste)-1):
min = i
for j in range(i+1,len(liste)):
if liste[min] > liste[j]: min = j
t = liste[min]
liste[min] = liste[i]
liste[i] = t
return liste
Wir nehmen als Eingabegröße n gleich die Länge des Arrays.
4.1.4
Beispiel 3: Sortieren eines Arrays mit MinSort
def minsort(liste):
for i in range(len(liste)-1):
min = i
for j in range(i+1,len(liste)):
if liste[min] > liste[j]: min = j
t = liste[min]
liste[min] = liste[i]
liste[i] = t
return liste
Wir nehmen als Eingabegröße n gleich die Länge des Arrays.
Der Vergleich mit f pnq “ n3 scheint nicht zu gehen.
4.1.4
Beispiel 3: Sortieren eines Arrays mit MinSort
def minsort(liste):
for i in range(len(liste)-1):
min = i
for j in range(i+1,len(liste)):
if liste[min] > liste[j]: min = j
t = liste[min]
liste[min] = liste[i]
liste[i] = t
return liste
Wir nehmen als Eingabegröße n gleich die Länge des Arrays.
Der Vergleich mit f pnq “ n3 scheint nicht zu gehen.
Der Vergleich mit f pnq “ n2 sieht vielversprechend aus.
4.1.4
Beispiel 3: Sortieren eines Arrays mit MinSort
def minsort(liste):
for i in range(len(liste)-1):
min = i
for j in range(i+1,len(liste)):
if liste[min] > liste[j]: min = j
t = liste[min]
liste[min] = liste[i]
liste[i] = t
return liste
Wir nehmen als Eingabegröße n gleich die Länge des Arrays.
Der Vergleich mit f pnq “ n3 scheint nicht zu gehen.
Der Vergleich mit f pnq “ n2 sieht vielversprechend aus.
Die Rechenzeit von minsortpAq ist „ n2 . Die Eingabegröße n ist dabei die
Länge des Arrays A.
4.1.4
Beispiel 4: Suche des Maximums
def maxsuche(liste):
max = liste[0]
for i in range(1,len(liste)):
if liste[i] > max: max = liste[i]
return max
Wir nehmen als Eingabegröße n die Anzahl der Einträge im Array.
4.1.5
Beispiel 4: Suche des Maximums
def maxsuche(liste):
max = liste[0]
for i in range(1,len(liste)):
if liste[i] > max: max = liste[i]
return max
Wir nehmen als Eingabegröße n die Anzahl der Einträge im Array.
Der Vergleich mit f pnq “ n scheint nicht zu gehen.
4.1.5
Beispiel 4: Suche des Maximums
def maxsuche(liste):
max = liste[0]
for i in range(1,len(liste)):
if liste[i] > max: max = liste[i]
return max
Wir nehmen als Eingabegröße n die Anzahl der Einträge im Array.
Der Vergleich mit f pnq “ n scheint nicht zu gehen.
Die Rechenzeit von maxsuche(liste) ist „ n. Die Eingabegröße n ist dabei
die Länge von liste.
4.1.5
Zusammenfassung der Beobachtungen
Wir haben die Rechenzeiten verschiedener Programme durch einfache
Funktionen charakterisieren können.
Funktion
fib(m)
quadratM(A)
minsort(A)
maxsuche(A)
Eingabegröße n
Wert von m
Seitenlänge von A
Länge von A
Länge von A
Rechenzeit „
n
1, 61
Bezeichnung der Rechenzeit
exponentiell
n
3
kubisch
n
2
quadratisch
n ¨ log n
quasi-linear
n
linear
Diese Art der Analyse der Rechenzeit beruht auf Experimenten (Ermittlung
der Rechenzeit für einige Eingaben) und Herumprobieren, bis ein hinreichend
ähnlicher Funktionsgraph gefunden wurde.
Wie kann man ohne Experimente die Rechenzeit abschätzen?
4.1.6
Mathematische Analyse
Die Rechenzeit einer einfachen Wertzuweisung an eine Variable dauert Zeit c,
ist also „ 1.
Beispiel:
a = b + 2*c
if liste[min] > liste[j]: min = j
Eine Schleife (while, for) besteht aus einer Bedingung und einem
Anweisungsblock.
a = 0
while n > 1:
n = n/2
a = a+1
for i in
min =
for j
if
range(len(liste)-1):
i
in range(i+1,len(liste)):
liste[min] > liste[j]: min = j
t = liste[min]
liste[min] = liste[i]
liste[i] = t
Die Rechenzeit einer Schleife ist
die Summe der Rechenzeiten jeder Wiederholung des Anweisungsblocks .
4.1.7
Programm
Rechenzeit „
n = int(sys.argv[1])
1
a = 0
1
Wiederholungen der Schleife
log n
while n > 1:
n = n/2
1
a = a+1
1
Die Rechenzeit des Programms ist „ 1 ` 1 ` 2 ¨ log n.
Faustregeln für Abschätzungen mit „:
§
Formeln so weit wie möglich zusammenfassen.
§
Bei Summen streicht man alles bis auf den größten Summanden.
Eine Summe 1 ` 1 ` 1 ` 1 ist „ 1.
§
Bei Produkten streicht man alle Konstanten Faktoren.
Damit bleibt übrig: die Rechenzeit des Programms ist „ log n.
Das ist im Prinzip“ die Anzahl der Wiederholungen der Schleife.
”
4.1.8
Im folgenden Programm ist die Eingabegröße n die Länge von liste.
Programm
max = liste[0]
for i in range(1,len(liste)):
if liste[i] > max: max = liste[i]
Rechenzeit „
1
Wiedh. der Schleife
n´1
1
Die Rechenzeit des Programms ist „ 1 ` pn ´ 1q „ n.
Das ist im Prinzip“ wieder die Anzahl der Wiederholungen der Schleife.
”
4.1.9
Im folgenden Programm ist die Eingabegröße n die Länge von liste.
Programm
for i in range(len(liste)-1):
min = i
for j in range(i+1,len(liste)):
if liste[min] > liste[j]: min = j
t = liste[min]
liste[min] = liste[i]
liste[i] = t
Rechenzeit „
Wiedh.
n´2
1
n´i ´1
1
1
1
1
Die Rechenzeit des Programms ist „
pn ´ 2q ¨ 4 ` pn ´ 2q ` pn ´ 3q ` pn ´ 4q ` . . . ` 1
“ pn ´ 2q ¨ 4 `
n´2
ÿ
i
“
pn ´ 2q ¨ 4 `
i“1
1
“ 4n ´ 8 ` ¨ pn2 ´ 3n ` 2q
2
„
1
¨ pn ´ 1q ¨ pn ´ 2q
2
n ` n2 ´ n ` 1
„
n2
Damit bleibt übrig: die Rechenzeit des Programms ist „ n2 .
Im Prinzip“:
”
zwei ineinandergeschachtelte Schleifen mit „ n Wiederholungen haben Rechenzeit „ n2 .
4.1.10
Im folgenden Programm ist die Eingabegröße n die Seitenlänge von A.
# quadratM(A) gibt das Quadrat A**2 der quadratischen Matrix A
# als Ergebnis zurück.
def quadratM(A):
C = stdarray.create2D(len(A),len(A),0)
for z in range(len(A)):
for s in range(len(A)):
for i in range(len(A)):
C[z][s] += A[z][i]*A[i][s]
return C
Die Rechenzeit für einen Funktionsaufruf von quadratM(A) ist „ n ¨ n ¨ n “ n3 .
Faustregel“:
”
drei ineinandergeschachtelte Schleifen mit „ n Wiederholungen haben Rechenzeit „ n3 .
Die Analyse der Rechenzeit rekursiver Funktionen kann komplizierter sein.
def fib(n):
if n==0 or n==1: return n
else: return fib(n-1) + fib(n-2)
Wir definieren eine Funktion T , so dass T pnq die Rechenzeit von fibpnq ist.
§ Für T p0q “ 1 und T p1q “ 1.
§ Für n ě 2 ist T pnq “ T pn ´ 1q ` T pn ´ 2q.
Man sieht: T pnq “ fibpn ` 1q.
Wir erinnern uns:
˜ˆ
? ˙n ˆ
? ˙n ¸
1` 5
1´ 5
1
´
fibpnq “ ? ¨
2
2
5
Dann ist
1
T pnq „ ? ¨
5
˜ˆ
? ˙n`1 ˆ
? ˙n`1 ¸
1` 5
1´ 5
´
2
2
„ 1, 61n`1 ´ 0, 6n`1 „ 1, 61n
4.1.12
Wir haben bei der mathematischen Analyse die gleichen Ergebnisse
gefunden wie bei der Analyse mittels Herumprobieren.
Ein weiterer Vorteil der mathematischen Analyse ist, dass man die Rechenzeit
eines Algorithmus analysieren kann, ohne dass man ihn in einer
Programmiersprache implementiert haben muss.
Eine mathematische Bemerkung:
Wir schreiben „ an Stelle der mathematischen O-Notation ( Oh-Notation“).
”
Die O-Notation dient dem Abschätzen des asymptotischen Wachstums von
Funktionen.
Wir schreiben f „ g statt f P Opg q.
Deshalb bedeutet f „ g : f wächst asymptotisch nicht stärker als g .
Die Rechenzeit ist „ n3“ bedeutet dann:
”
die Rechenzeit wächst nicht stärker als n3 .
Die formale Definition von f P Opg q ist
Dc Dn0 @n ě n0 : f pnq ď c ¨ g pnq ` c.
4.1.13
Zusammenfassung
Wir haben gesehen, dass es zur Abschätzung von Rechenzeiten von
Programmen ein realistisches und gut benutzbares mathematisches
Modell gibt.
Die folgende Tabelle enthält typische Rechenzeiten und den Zuwachs an
Rechenzeit eines Programm, das bei Eingabegröße m wenige Sekunden
braucht, bei einer Eingabe der Größe 100 ¨ m.
Rechenzeit
Verlängerung der Rechenzeit (s.o.)
linear
wenige Minuten
quasi-linear
wenige Minuten
quadratisch
einige Stunden
kubisch
ein paar Wochen
exponentiell
ewig
4.1.14
Herunterladen