Stichpunktezettel fürs Tutorium

Werbung
Stichpunktezettel fürs Tutorium
Moritz und Dorian
13. Januar 2010
1 Komplexitätsanalyse
1.1 Einführendes Beispiel
Gegeben ist der folgende Algorithmus, der eine Liste mit Zahlen sortiert:
void sort(list_t* list)
{
3
int count = length(list);
1
2
4
while (--count > 0)
{
for (int i = 0; i < count; ++i)
{
if (at(list, i) > at(list, i+1))
swap(list, i, i+1);
}
}
5
6
7
8
9
10
11
12
13
}
Ist der Algorithmus besser oder schlechter als der folgende Algorithmus, der ebenfalls diese
Aufgabe erfüllt?
1
void _sort(list_t* list, int left, int right)
2 {
3
while (left < right)
4
{
5
int pivot = left,
6
first = left;
1
7
do
{
8
9
if (at(list, first) < at(list, right))
{
swap(list, pivot, first);
++pivot;
}
10
11
12
13
14
15
++first;
}
while (first < right);
16
17
18
19
swap(pivot, right);
20
21
_sort(list, left, pivot-1);
left = pivot+1;
22
23
}
24
25
}
26
void sort(list_t* list)
28 {
29
_sort(list, 0, length(list) - 1);
30 }
27
Die Theorie der Komplexität von Algorithmen ist ein Ansatz, Algorithmen untereinander
vergleichbar zu machen. Eine weitere Anwendung findet sie bei der Beantwortung der
Frage, wie schwer lösbar Probleme in Wirklichkeit sind. Die Komplexitätsklasse bietet dort
einen Anhaltspunkt für den Vergleich unterschiedlicher Problemarten.
Wir wollen uns in diesem Tutorium mit unterschiedlichen Problemen der Informatik
beschäftigen, ihre Komplexität einschätzen, und lernen, die Güte einfacher Algorithmen
zu analysieren.
2
1.2 Landau-Notation
Die Landau-Symbole werden in der Informatik verwendet, um das asymptotische Verhalten
von Algorithmen zu beschreiben. Sie abstrahieren von einer konkreten Rechengeschwindigkeit oder einem konkreten Instruktionsset eines Prozessors bestimmter Bauart und betrachten lediglich das Wachstum der Anzahl der Schritte, die ein Algorithmus bis zu seiner
Terminierung benötigt, in Abhängigkeit von der Eingabe. Andere Anwendungen beziehen
sich auf den benötigten Speicherplatz oder weitere, beschränkte Ressourcen eines Computersystems, doch diese sollen heute nicht betrachtet werden.
Die folgende Tabelle listet die einzelnen Symbole, ihre Definitionen sowie die intuitiven
Bedeutungen auf:
Symbol
f ∈ O(g)
f ∈ o(g)
f ∈ Ω(g)
f ∈ ω(g)
f ∈ Θ(g)
Definition
∃c > 0 ∃x0 ∀x > x0 : |f (x)| ≤ c · |g(x)|
f wächst nicht wesentlich schneller als g
∀c > 0 ∃x0 ∀x > x0 : |f (x)| < c · |g(x)|
f wächst langsamer als g
∃c > 0 ∃x0 ∀x > x0 : |f (x)| ≥ c · |g(x)|
f wächst nicht wesentlich langsamer als g
∀c > 0 ∃x0 ∀x > x0 : |f (x)| > c · |g(x)|
f wächst schneller als g
∃c0 > 0 ∃c1 > 0 ∃x0 ∀x > x0 : c0 · |g(x)| ≤ |f (x)| ≤ c1 · |g(x)|
f wächst genauso schnell wie g
Konstante Faktoren oder Terme geringeren Wachstums werden in der Landau-Notation
ignoriert. Wie leicht zu erkennen ist, beschreibt jedes Symbol eine Menge von Funktionen, trotzdem wird gewöhnlicherweise das Gleichheitszeichen für die Zugehörigkeit zur
entsprechenden Menge verwendet (also beispielsweise f (n) = O(n) anstatt f (n) ∈ O(n)).
1.3 Bubblesort
Der erste Sortieralgorithmus weiter oben ist sogenanntes Bubblesort, dessen Komplexität
bestimmt werden soll, wobei die Annahme gilt, dass at() und swap() konstante Laufzeit
haben und length() höchstens linear viel Zeit benötigt. Wir berechnen die Komplexität in Abhängigkeit von der Länge n der übergebenen Liste und betrachten dabei nur die
Anzahl der Vergleiche, die der Algorithmus benötigt, weil diese Operation im Zweifelsfall
teuer ist. Die Schleife zwischen Zeile 5 und 12 wird genau n mal durchlaufen. Innerhalb
dieser Schleife befindet sich eine weitere, die bei jeder Iteration ein Mal weniger durchlaufen wird. Bei der ersten Iteration werden noch
P n Vergleiche durchgeführt, danach n − 1,
n − 2, usw. Insgesamt brauchen wir also ni=1 i viele Vergleiche. Nach der Gauß’schen
Summenformel gilt:
3
n
X
i=
i=1
n · (n + 1)
n2 + n
=
2
2
Da konstante Faktoren und Terme geringerer Ordnung in der Landau-Notation verschwinden, folgt
n2 + n
∈ O(n2 )
2
Das bedeutet, der Algorithmus hat quadratische Komplexität.
Bemerkung. Die Laufzeit von length() musste nicht betrachtet werden, da sie außerhalb
der Schleifen steht und höchstens O(n) viele Schritte braucht.
Die Analyse der Laufzeit des zweiten Algorithmus (Quicksort) gestaltet sich etwas schwieriger, weshalb wir diese Analyse euren Dozenten überlassen wollen. Die erwartete Laufzeit
liegt bei diesem Algorithmus bei O(n · log n), allerdings kann sie auch O(n2 ) betragen (im
worst case). Damit liegt sie nie über der Laufzeit von Bubblesort, weshalb dieser Algorithmus besser geeignet ist eine Liste von Zahlen zu sortieren als der erste.
1.4 Inkrementieren einer Zahl um eins
Das nächste Problem, das wir analysieren wollen, ist das Problem eine binär kodierte Zahl
um eins zu erhöhen. Dafür betrachten wir die folgende Turingmaschine:
M := (Q, Σ, Γ, δ, s, , {t}) mit
Q := {s, m, t}
Σ := {0, 1}
Γ := {0, 1, }
δ :=
Zustand
s
s
s
m
m
Symbol
0
1
0
→
→
→
→
→
neues Symbol
1
0
1
0
neuer Zustand
m
s
m
m
t
Bewegungsrichtung
R
L
R
R
L
Die Turingmaschine erwartet als Eingabe eine binär kodierte Zahl, die von links nach
rechts auf das Band geschrieben wurde, wobei der Schreib-/Lesekopf am rechten Ende der
Eingabe startet. Sie fährt das Band von rechts nach links ab und ändert dabei alle Einsen zu
Nullen, bis sie die erste null oder sieht. Das entsprechende Feld wird auf eins gesetzt und
4
der Schreib-/Lesekopf wieder in die Ausgangsposition gebracht. Danach kann sie erneut
gestartet werden.
Im ungünstigsten Fall stehen auf dem Band n Einsen und die Turingmaschine muss 2 · n
Felder abfahren, um die Zahl zu erhöhen. In der Landau-Notation bedeutet das: der worstcase hat eine Komplexität von O(n).
1.5 Amortisierte Laufzeitanalyse
Der folgende Abschnitt kommt praktisch direkt aus dem Skript zur Vorlesung „Graphen
und Algorithmen 1“ von Prof. Hougardy. Ich hätte es nicht besser formulieren können,
daher habe ich es erst gar nicht versucht.
„Bei der amortisierten Laufzeitanalyse geht es darum, die Laufzeit einer in einem Algorithmus auftretenden Folge von Operationen zu berechnen. Gegeben
sei ein Algorithmus A, der ausgehend von einer Ausgangskonfiguration D0 eine
Folge von m Operationen op1 , op2 , . . . , opm durchführt, wobei Operation i die
Konfiguration Di−1 nach Di überführt. Die Laufzeit für die Operation opi betrage ti . Gesucht ist nun die Gesamtlaufzeit der m Operationen, d. h. der Wert
der Summe
m
X
ti .
i=1
Falls es eine nicht von m abhängende Funktion a gibt, so dass für jede im
Algorithmus mögliche Folge von m Operationen gilt, dass
m
X
ti ≤ m · a,
i=1
so sagt man, dass die amortisierte Laufzeit einer jeden Operation höchstens a
ist. Die amortisierte Laufzeit einer Operation ist also die mittlere Laufzeit einer
einzelnen Operation in einer Folge von Operationen. Offensichtlich gilt
m
X
ti ≤ m · max ti ,
1≤i≤m
i=1
d. h. die maximale Laufzeit einer einzelnen Operation ist sicherlich eine obere
Schranke für die amortisierte Laufzeit. In vielen Fällen lassen sich aber wesentlich bessere Schranken angeben.“
Wir betrachten nochmal das Beispielproblem eine Zahl um eins zu erhöhen.
5
Zahl
geänderte Bits
0000
0001
0010
0011
0100
0101
0110
0111
1000
...
1
2
1
3
1
2
1
4
Die naive Analyse von oben liefert eine Gesamtlaufzeit von O(m · log m), da die größte
auftretende Zahl nach m Inkrementierungsschritten log m Bits hat. Damit ist also log m
eine obere Schranke für die amortisierte Laufzeit eines Inkrementierungsschrittes. Diese
Grenze lässt sich allerdings deutlich verbessern.
Wenn die Bits unabhängig voneinander betrachtet werden, sieht man, dass das erste
Bit jedes mal geändert wird, das zweite jedes zweite mal, das dritte jedes vierte mal und
allgemein, das n’te jedes 2n ’te mal. Bei m Anwendungen des Algorithmus wird das erste
Bit also m mal invertiert, das zweite m/2 mal, das dritte m/4 mal und das n’te m/2n mal. Das
ergibt für die gesamte Zahl veränderter Bits
blog mc j
X
i=0
∞
X 1
mk
<
m
·
= 2m
2i
2i
i=0
Die amortisierte Laufzeitanalyse zeigt, dass für m Inkrementierungsschritte 2m Operationen benötigt werden. Die amortisierten Kosten pro Schritt sind damit O(1).
2 Komplexitätsquiz
Welche der folgenden Probleme sind
(a) in der Klasse P (effizient lösbar)
(b) in der Klasse NP (schwer lösbar)
(c) unentscheidbar (gar nicht lösbar)
1. Eine Zahl um eins erhöhen.
2. Den kürzesten Weg in einem Netzwerk finden.
6
3. Sortieren einer Liste von Zahlen.
4. Testen, ob eine Zahl prim ist.
5. Testen, ob eine ALU richtig rechnet. (vgl. Pentium-FDIV-Bug)
6. Entscheiden, ob sich ein Graph überschneidungsfrei in die Ebene zeichnen lässt.
7. Entscheiden, ob sich ein Graph überschneidungsfrei auf die Oberfläche eines beliebig
komplexen Körpers zeichnen lässt.
8. Automatisierter Beweis, dass ein gegebenes Programm erwünschte Eigenschaften
aufweist.
9. Berechnen einer Zahl aus ihren Primfaktoren.
10. Berechnen der Primfaktoren einer Zahl.
3 Doppelt verkettete Liste mit nur einem Zeiger
Normalerweise würde eine doppelt verkettete Liste in jedem Element previous und next
Zeiger enthalten und die aktuelle Position würde durch einen einzigen Zeiger markiert
werden. Wenn stattdessen zwei Zeiger für die aktuelle Position benutzt werden, einer zum
ausgewählten Element, der zweite zum Vorgänger, ist es möglich nur einen einzigen Zeiger pro Knoten zu verwenden. Als Wert dieses Zeigers wird die Adresse des Vorgängers
xor dem Wert des Zeigers zum nächsten Element verwendet. Um in der Liste vorwärts zu
laufen, wird der Vorgängerzeiger der aktuellen Position mit dem gespeicherten Wert des
aktuellen Knotens via xor verknüpft und ergibt dann den Zeiger zum nächsten Element.
In die umgekehrte Richtung kommt man, wenn der Zeiger auf die aktuelle Position mit
dem gespeicherten Wert des Zeigers der Vorgängerposition mittels xor verknüpft wird.
Lösungen des Komplexitätsquiz
1. Ist in P wie oben gezeigt.
2. Ist in P. Der beste Algorithmus benötigt O(n·log n) viele Schritte (was der Komplexität
von Sortieren entspricht).
3. Ist in P. Mindestens O(n · log n) viele Schritte werden gebraucht.
4. Ist in P. Der Agrawal-Kayal-Saxena-Primzahlentest hat die Komplexität O((log n)6+ ),
besser ist allerdings der Miller-Rabin-Test (aber Monte-Carlo).
7
5. Ist in NP. Gatter in Chips lassen sich durch aussagenlogische Formeln darstellen; ein
unerwünschter Zustand ist dann ein Erfüllbarkeitsproblem.
6. Ist in P. Der Satz von Kuratowski zeigt ein einfaches Unterscheidungskriterium.
7. Ist in P wenn der Körper nicht Teil der Eingabe ist, nach einer Serie von Beweisen
von Robertson und Seymour.
8. Ist unentscheidbar nach dem Satz von Rice.
9. Ist in P. Einfache Multiplikation der Primfaktoren genügt.
10. Ist in NP. Der Algorithmus von Shor braucht O((log n)3 ) viele Schritte – auf einem
Quantenrechner.
8
Herunterladen