Grundkurs Skript Teil 6: Beurteilung von Algorithmen

Werbung
6 Algorithmenanalyse und Grenzen der Berechenbarkeit
6.1 Sortieralgorithmen
Gegeben ist im Folgenden ein 20-spaltiges Feld mit natürlichen Zahlen als Einträgen.
Bsp: 55 7 23 14 2 5 4 27 15 0 8 1 36 3 18 45 9 10 19 6
Diese Zahlen sollen nun vom Computer beginnend mit der kleinsten Zahl der Größe nach
geordnet werden. Doch zunächst bringen Sie bitte in möglichst kurzer Zeit alle Zahlen in die
richtige Reihenfolge.
Diese relativ "einfache" Aufgabe kann auf ganz verschiedene Arten gelöst werden. Wir
unterscheiden im Folgenden verschiedene Sortierverfahren, die sich hinsichtlich ihrer
Arbeitsweise, Effektivität und Komplexität unterscheiden.





Sortieren durch direktes Auswählen
Sortieren durch direktes Einfügen
Sortieren mit Bubble-Sort
Sortieren mit Quick-Sort
Sortieren mit Merge-Sort
6.1.1 Sortieren durch direktes Auswählen („selection sort“)
Dieses Sortierverfahren ist recht nahe liegend. An der ersten Position muss die kleinste Zahl
stehen. Also wird alles durchsucht, bis die kleinste Zahl gefunden wurde. Diese wird mit der
Zahl an der ersten Position vertauscht. Nun wird zur nächsten Position vorgegangen und diese
wird zur ersten Position des restlichen Feldes.
Bsp.: 16 7 12 14 2 11 4 13 15 0 8 1 17 3 18 5 9 10 19 6
Die kleinste Zahl ist die 0, also werden 0 und 16 miteinander vertauscht. Nun ist die kleinste
Zahl die 1, also werden 1 und 7 miteinander vertauscht. Dieses Verfahren wiederholt sich, bis
das ganze Feld abgearbeitet wurde, dann ist es zwangsläufig sortiert.
16
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
7 12 14 2 11 4 13 15 0 8 1 17 3 18 5 9 10 19 6
7 12 14 2 11 4 13 15 16 8 1 17 3 18 5 9 10 19 6
1 12 14 2 11 4 13 15 16 8 7 17 3 18 5 9 10 19 6
1 2 14 12 11 4 13 15 16 8 7 17 3 18 5 9 10 19 6
1 2 3 12 11 4 13 15 16 8 7 17 14 18 5 9 10 19 6
1 2 3 4 11 12 13 15 16 8 7 17 14 18 5 9 10 19 6
1 2 3 4 5 12 13 15 16 8 7 17 14 18 11 9 10 19 6
1 2 3 4 5 6 13 15 16 8 7 17 14 18 11 9 10 19 12
1 2 3 4 5 6 7 15 16 8 13 17 14 18 11 9 10 19 12
1 2 3 4 5 6 7 8 16 15 13 17 14 18 11 9 10 19 12
1 2 3 4 5 6 7 8 9 15 13 17 14 18 11 16 10 19 12
1 2 3 4 5 6 7 8 9 10 13 17 14 18 11 16 15 19 12
1 2 3 4 5 6 7 8 9 10 11 17 14 18 13 16 15 19 12
1 2 3 4 5 6 7 8 9 10 11 12 14 18 13 16 15 19 17
1 2 3 4 5 6 7 8 9 10 11 12 13 18 14 16 15 19 17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 18 16 15 19 17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 19 18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0
1
2
3
4
5
6
7
8
9 10 11 12 13 14 15 16 17 18 19
Arbeitsauftrag:
Programmieren Sie das Sortierverfahren durch Auswahl. Dabei soll jede Zwischenzeile in
eine Listbox oder Memo ausgegeben werden (eigene Prozedur). Gestalten Sie dabei die
Formularoberfläche so, dass Sie später noch andere Sortierverfahren ergänzen können.
6.1.2 Sortieren durch direktes Einfügen („insertion sort“)
Gegeben ist wieder eine unsortierte Liste:
3 8 10 19 16 1 0 12 13 18 7 9 5 2 6 14 11 17 15 4
Nun geht man die Liste von Anfang bis Ende durch. Irgendwann findet man ein Element, dass
hinter einer größeren Zahl steht, in obigen Beispiel die 16. Also nimmt man die 16 heraus,
schiebt alle größeren Zahlen (hier nur die 19) nach hinten und fügt die herausgenommene 16
wieder ein.
Ausführlich:
3 8 10 19 16 1 0 12 13 18
3 8 10 19
1 0 12 13 18
3 8 10
19 1 0 12 13 18
16
Die neue Liste sieht so aus:
3 8 10 16 19 1 0 12 13 18
7 9 5 2 6 14 11 17 15 4
7 9 5 2 6 14 11 17 15 4
7 9 5 2 6 14 11 17 15 4
7 9 5 2 6 14 11 17 15 4
Nun wiederholt man den Vorgang. Die erste Zahl, die vor sich größere stehen hat ist die 1.
3 8 10 16 19 1 0 12 13 18 7 9 5 2 6 14 11 17 15 4
3 8 10 16 19
0 12 13 18 7 9 5 2 6 14 11 17 15 4
3 8 10 16 19 0 12 13 18 7 9 5 2 6 14 11 17 15 4
1
Der ganze Sortierungsprozess sieht dann in unserem Beispiel so aus:
3
3
1
0
0
0
0
0
0
0
0
0
0
0
0
0
8 10 19 16 1 0 12 13 18 7 9 5 2 6 14 11 17
8 10 16 19 1 0 12 13 18 7 9 5 2 6 14 11 17
3 8 10 16 19 0 12 13 18 7 9 5 2 6 14 11 17
1 3 8 10 16 19 12 13 18 7 9 5 2 6 14 11 17
1 3 8 10 12 16 19 13 18 7 9 5 2 6 14 11 17
1 3 8 10 12 13 16 19 18 7 9 5 2 6 14 11 17
1 3 8 10 12 13 16 18 19 7 9 5 2 6 14 11 17
1 3 7 8 10 12 13 16 18 19 9 5 2 6 14 11 17
1 3 7 8 9 10 12 13 16 18 19 5 2 6 14 11 17
1 3 5 7 8 9 10 12 13 16 18 19 2 6 14 11 17
1 2 3 5 7 8 9 10 12 13 16 18 19 6 14 11 17
1 2 3 5 6 7 8 9 10 12 13 16 18 19 14 11 17
1 2 3 5 6 7 8 9 10 12 13 14 16 18 19 11 17
1 2 3 5 6 7 8 9 10 11 12 13 14 16 18 19 17
1 2 3 5 6 7 8 9 10 11 12 13 14 16 17 18 19
1 2 3 5 6 7 8 9 10 11 12 13 14 15 16 17 18
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
19
4
4
4
4
4
4
4
4
4
4
4
4
4
4
4
4
0
1
2
3
4
5
6
7
8
9 10 11 12 13 14 15 16 17 18 19
Arbeitsauftrag:
Programmieren Sie das Sortierverfahren durch Auswahl.
6.1.3 Sortieren durch Vertauschen (Bubble-Sort)
Das einfachste Sortierverfahren überhaupt ist Bubble-Sort:
Steht eine größere Zahl vor einer kleineren Zahl, so werden die beiden Zahlen miteinander
vertauscht. Dies wird solange wiederholt, bis bei einem Durchlauf durch alle Zahlen keine
Vertauschung mehr erforderlich war.
19 11 18 17 5 1 9 10 6 8 2 16 15 12 4 7 3 14 13 0
11 18 17 5 1 9 10 6 8 2 16 15 12 4 7 3 14 13 0 19
11 17 5 1 9 10 6 8 2 16 15 12 4 7 3 14 13 0 18 19
11 5 1 9 10 6 8 2 16 15 12 4 7 3 14 13 0 17 18 19
5 1 9 10 6 8 2 11 15 12 4 7 3 14 13 0 16 17 18 19
1 5 9 6 8 2 10 11 12 4 7 3 14 13 0 15 16 17 18 19
1 5 6 8 2 9 10 11 4 7 3 12 13 0 14 15 16 17 18 19
1 5 6 2 8 9 10 4 7 3 11 12 0 13 14 15 16 17 18 19
1 5 2 6 8 9 4 7 3 10 11 0 12 13 14 15 16 17 18 19
1 2 5 6 8 4 7 3 9 10 0 11 12 13 14 15 16 17 18 19
1 2 5 6 4 7 3 8 9 0 10 11 12 13 14 15 16 17 18 19
1 2 5 4 6 3 7 8 0 9 10 11 12 13 14 15 16 17 18 19
1 2 4 5 3 6 7 0 8 9 10 11 12 13 14 15 16 17 18 19
1 2 4 3 5 6 0 7 8 9 10 11 12 13 14 15 16 17 18 19
1 2 3 4 5 0 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1 2 3 4 0 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1 2 3 0 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1 2 0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1 0 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Die Zahl an der ersten Stelle, die 19, wird solange mit ihrem Nachfolger vertauscht, solange
dieser Nachfolger kleiner ist. Damit rutscht die 19 ans Ende, alle anderen Zahlen dabei um
eine Position nach vorne. In der zweiten Zeile bleibt die 11 vorne stehen, da ihr Nachfolger
größer ist. Dieser, die 18 rutscht dann wieder bis nach hinten auf die vorletzte Position. Dort
wird nicht mehr vertauscht, da ja schon die größte Zahl am Ende steht. In der dritten Zeile
geschieht Vergleichbares mit der 17. In der vierten Zeile wird zunächst die 11 bis vor die 16
geschoben, dort bleibt sie hängen. Dafür wird von dort die 16 bis auf ihre endgültige Position
Arbeitsauftrag:
Programmieren Sie das Sortierverfahren durch Vertauschen.
6.1.4 Sortieren durch Quicksort
Das Grundprinzip dieses Algorithmus ist "Teile und Herrsche". Die Datei wird in zwei Teile
zerlegt, die dann unabhängig voneinander sortiert werden. Für die beiden Teile wird dieses
Verfahren erneut angewandt, es ist also rekursiv.
12
12
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
2 13 11 17 16
2 13 11 1 3
2 6 8 1 3
2 3 1 4 6
1 3 2 4 6
1 3 2 4 6
1 2 3 4 6
1 2 3 4 6
1 2 3 4 6
1 2 3 4 6
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
7 10
7 10
7 5
7 5
7 5
7 5
7 5
7 5
7 5
7 5
7 6
7 6
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
6 7
4
4
4
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
5
5
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
19
14
14
14
14
14
14
14
14
14
14
14
14
14
14
14
10
10
10
10
10
10
10
10
10
10
10
8
8
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
11
6
6
13
13
13
13
13
13
13
13
13
13
13
13
13
13
13
13
13
12
12
12
12
12
12
12
12
0
0
12
12
12
12
12
12
12
12
12
12
12
12
12
12
12
12
12
13
13
13
13
13
13
13
13
9
9
10
10
10
10
10
10
10
10
10
10
10
10
10
10
14
14
14
14
14
14
14
14
14
14
14
18
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
15
14
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
17
16
16
16
3
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
16
17
17
17
1
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
17
18
18
18
18
15
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
18
19
19
19
19
Hier wird die 15 als das zerlegende Element gewählt. Nun wird von links her die erste Zahl
gesucht, die größer ist, dies ist die 17. Von rechts her gesucht ist die 1 die erste kleinere Zahl.
Die 17 und die 1 werden nun miteinander vertauscht, denn wenn 15 das zerlegende Element
ist, dürfen links davon nur kleinere, rechts davon nur größere Zahlen stehen.
Dieser Vorgang wird wiederholt, dabei werden die 16 und die 3 miteinander vertauscht, im
nächsten Durchgang die 19 und die 14.
Die Suche von links nach größeren Zahlen als der 15 endet nun bei der 18, dort steht aber
auch gerade die Suche von rechts. Daher werden die 18 und die 15 miteinander vertauscht.
Nun kommt die Rekursion ins Spiel. Dasselbe Verfahren wie oben beschrieben wird nun
erneut angewandt, zunächst auf den linken Teil, dann auf den rechten Teil. In der zweiten
Zeile wird nun die 9 als das zerlegende Element gewählt. Bei der Wiederholung des oben
beschriebenen Vorganges werden die 12 und die 0, die 13 und die 6, die 11 und die 8
miteinander vertauscht, schließlich noch die 10 und die 5. Im letzten Schritt werden dann 10
und 9 miteinander vertauscht.
Nun wieder Rekursion: Dasselbe Verfahren wird nun wieder zuerst auf den linken Teil, dann
auf den rechten Teil angewandt. Dies geschieht solange, bis alles sortiert ist.
Programmieren Sie dieses Verfahren!
6.1.5 Sortieren mit Merge-Sort
Mergesort (engl. merge für verschmelzen und sort für sortieren) ist ein stabiler
Sortieralgorithmus. Er arbeitet nach dem Prinzip Teile und herrsche. Der Algorithmus kann
sowohl rekursiv als auch iterativ realisiert werden.[1] Er wurde erstmals 1945 durch John von
Neumann vorgestellt.
Mergesort betrachtet die zu sortierenden Daten als Liste und zerlegt sie in kleinere Listen, die
jede für sich sortiert werden. Die sortierten kleinen Listen werden dann im
Reißverschlussverfahren zu größeren Listen zusammengefügt (engl.: (to) merge), bis wieder
eine sortierte Gesamtliste erreicht ist. Das Verfahren arbeitet bei Arrays in der Regel nicht inplace, es sind dafür aber (trickreiche) Implementierungen bekannt. Verkettete Listen sind
besonders geeignet zur Implementierung von Mergesort, dabei ergibt sich die in-placeSortierung fast von selbst.
Das Bild veranschaulicht die drei wesentlichen Schritte eines Teile-und-herrsche-Verfahrens,
wie sie im Rahmen von Mergesort umgesetzt werden. Der Teile-Schritt ist ersichtlich trivial
(die Daten werden einfach in zwei Hälften aufgeteilt). Die wesentliche Arbeit wird beim
Verschmelzen (merge) geleistet - daher rührt auch der Name des Algorithmus. Bei Quicksort
ist hingegen der Teile-Schritt aufwendig und der Merge-Schritt einfacher (nämlich eine
Konkatenierung).
Bei der Betrachtung des in der Grafik dargestellten Verfahrens sollte man sich allerdings
bewusst machen, dass es sich hier nur um eine von mehreren Rekursionsebenen handelt. So
könnte etwa die Sortierfunktion, welche die beiden Teile 1 und 2 sortieren soll, zu dem
Ergebnis kommen, dass diese Teile immer noch zu groß für die Sortierung sind. Beide Teile
würden dann wiederum aufgeteilt und der Sortierfunktion rekursiv übergeben, so dass eine
weitere Rekursionsebene geöffnet wird, welche dieselben Schritte abarbeitet. Im Extremfall
(der bei Mergesort sogar der Regelfall ist) wird das Aufteilen so weit fortgesetzt, bis die
beiden Teile nur noch aus einzelnen Datenelementen bestehen.
Die folgende Grafik illustriert die Arbeitsweise des Algorithmus:
unit UMergeSort;
interface
procedure MergeSort(var A: array of Integer);
Mergesort ist im Gegensatz zu Quicksort stabil, wenn der Merge-Schritt richtig implementiert
ist. Seine Komplexität (Worst-, Best- und Average-Case-Verhalten) beträgt in der LandauNotation ausgedrückt stets Θ(nlog(n)). Damit ist Mergesort hinsichtlich der Komplexität dem
Quicksort gewissermaßen überlegen, da der Quicksort (ohne besondere Vorkehrungen) ein
Worst-Case-Verhalten von Θ(n2) besitzt. Die Wahrscheinlichkeit, dass dieser Fall auftritt, ist
jedoch so gering, dass Quicksort in der Praxis bessere Ergebnisse erzielt.
Implemetieren Sie Merge-Sort!
6.2 Algorithmenanalyse
ZUVERLÄSSIGKEIT VON ALGORITHMEN
Den Bubble hab´ ich im Kopf,
Quicksort schreibe ich ab.
(Bill Gates)
Wichtigste Forderung an ein Programm ist, dass es zuverlässig ist, d.h. die gewünschte
Leistung tatsächlich erbringt.
Häufig liefert aber Software, die seit Jahren ( scheinbar ) zuverlässig arbeitet, plötzlich
fehlerhafte Resultate.
Beispiel
1. Abrechnungspanne der Deutschen Telekom am 1.1.1996 : Durch ein fehlerhaftes
Programm wurde fast allen Kunden bei der Umstellung der Tarifstruktur eine zu hohe
Abrechnung erstellt, obwohl vorher umfangreiche Probeläufe absolviert worden
waren.
2. Startprogramm der BOEING 737 : unerklärliche Startabbrüche bei v = 60 Knoten
(Ursache : im Programm waren nur die Fälle v < 60 Knoten bzw. v > 60 Knoten
berücksichtigt. Bei Übermittlung von v = 60 Knoten durch die Sensoren erkannte das
Programm einen undefinierten Zustand und brach daher ab )
3. Jahr-2000-Problem ( Ursache : Um den damals sehr teuren Speicherplatz zu sparen,
wurde die Jahreszahl in den Datumsangaben anfänglich meist nur mit zwei Ziffern
dargestellt. Daher kann ein solches Programm nicht zwischen 1900 und 2000
unterscheiden. Vergleiche von Datumsangaben führen auf falsche Resultate )
Je komplexer ein Programm ist, um so sicherer enthält es Fehler. ( Schätzung : ≥ 1 Fehler je
10 KByte Programmtext )
Definition
Der Nachweis, dass ein Informatiksystem zuverlässig arbeitet, heißt Validierung.
Bemerkung
In der Regel handelt es sich nicht um einen exakten Beweis, sondern um eine empirische
Bestätigung auf Widerruf.
Bei den Prüfmethoden unterscheidet man formale Beweise ( Verifikation ), Tests und weitere
Methoden (z.B. Inspektionen ).
Bei der formalen Methode ( Verifikation ) wird die Einhaltung der Bedingungen an die
Zwischen- und Endergebnisse des Algorithmus bzw. Programms mittels logischer
Herleitungen nachgewiesen.
Bei der empirischen Methode ( Testen ) wird der Algorithmus bzw. das Programm mit
bestimmten ausgesuchten Daten erprobt, zu denen die Ergebnisse bekannt sind. Dabei kann
lediglich das Vorhandensein von Fehlern entdeckt, aber nicht die Fehlerfreiheit nachgewiesen
werden.
Definition
Ein Algorithmus heißt korrekt, wenn er seiner Spezifikation genügt, d.h. wenn er zu allen
Eingabedaten, die einer bestimmten Vorbedingung genügen, diejenigen Ausgabedaten
erzeugt, welche eine bestimmte Nachbedingung erfüllen. Die Überprüfung erfolgt
hauptsächlich durch die beiden o.g. Verfahren.
EFFIZIENZ VON ALGORITHMEN
Definition
Zwei Algorithmen heißen äquivalent, wenn sie gleichen Eingabedaten gleiche Ausgabedaten
zuordnen, also derselben Spezifikation genügen. Von zwei äquivalenten Algorithmen heißt
derjenige effizienter, der mit weniger Laufzeit und / oder weniger Speicherplatz auskommt.
Bemerkung
Man kann einen Algorithmus nicht in beiderlei Hinsicht beliebig verbessern. Ab einer
gewissen Schwelle wird die Beschleunigung eines Algorithmus durch erhöhten
Speicherbedarf erkauft ( Claus VOLCKER : Informations-Unschärferelation )
EINFACHE MAßNAHMEN ZUR STEIGERUNG DER EFFIZIENZ
-
Vermeiden unnötiger Berechnungen (Beispiel: Mehrfache Berechnungen einer Größe
bei verschiedenen Schleifendurchläufen )
Ausnutzen mathematischer Eigenschaften
Neue Lösungsidee bei gleicher Datenstruktur
Änderung der Datenstruktur
GRÖSSENORDNUNG
Bezeichnung
Ein Problem der Größe n ist ein Spezialfall eines Problems, bei dem die Eingabedaten aus n
Elementen bestehen. Die Zahl O( n ) der Rechenschritte, die ein Algorithmus zur Lösung
eines Problems der Größe n benötigt, heißt Zeitkomplexität des Algorithmus.
Zur Ermittlung der Zeitkomplexität nimmt man folgende Vereinfachungen vor :
1. Es wird nur die dominante Operation betrachtet, von der die Laufzeit wesentlich
abhängt ( z.B. eine zentrale Schleife ).
2. Man betrachtet Spezialfälle : günstigster ( + ) / ungünstigster ( - ) / mittlerer ( o ) Fall.
3. Durch Weglassen additiver oder multiplikativer Konstanten geht man zur
Größenordnung der Laufzeit O( n ) über.
Wir analysieren unsere bisher erarbeiteten Sortieralgorithmen hinsichtlich ihres Zeitbedarfs
und ihres Speicherplatzbedarfs.
http://www.matheprisma.de/Module/Sortieren/Vergleich.htm
Sortierverfahren
Zeitkomplexität
(worst case)
Zeitkomplexität
(average case)
Zeitkomplexität
(best case)
stabil
inplace
Selection
O(n2)
O(n2)
O(n2)
nein
ja
Insertion
O(n2)
O(n2)
O(n)
ja
ja
Bubble
O(n2)
O(n2)
O(n)
ja
ja
Quick
O(n2)
O(n*log(n))
O(n*log(n))
nein
ja
Merge
O(n*log(n))
O(n*log(n))
O(n*log(n))
ja
nein
Auschlaggebend für die Zeitkomplexität ist stets der Aufbau der zu sortierenden
Datenstruktur. So sind Insertion – und Bubblesort sogar linear schnell, wenn eine bereits
sortierte Liste vorliegt. Dagegen verlangsamt sich Quicksort auf quadratische Größenordnung,
wenn sich eine ungünstige Wahl des Pivotelementes (z.B. größtes oder kleinstes Element
durch die Anordnung der Daten ergibt. Die Teilliste besteht dann stets nur aus einem Element,
wodurch sich keine logarithmische Komplexität mehr erreichen lässt.
Stabil ist ein Verfahren dann, wenn es die Reihenfolge von Datensätzen mit gleichem
Schlüsselwert nicht verändert. Wenn beispielsweise eine Liste alphabetisch sortierter
Personendateien nach dem Geburtsdatum neu sortiert wird, dann bleiben unter einem stabilen
Sortierverfahren alle Personen mit gleichem Geburtsdatum alphabetisch sortiert.
Bubblesort kann somit durchaus eine sinnvolle Wahl darstellen, wenn zum Beispiel wenige
neue Daten in einen bereits sortierten Bestand eingefügt werden müssen. Hier besteht sein
Vorteil – und damit seine Geschwindigkeitsgewinn – darin, dass andere Verfahren, z.B.
Quicksort, die bereits sortierten Daten wieder völlig durcheinanderwirbeln, während die
neuen Daten bei Bubblesort einfach an ihren Platz innerhalb des Bestands „blubbern“ können,
ohne diesen neu zu strukturieren.
Merke:
Die Komplexität des besten Sortierverfahrens ist von der Größenordnung n*log(n). Es gibt
keinen Algorithmus, der weniger Vergleiche benötigt!
Anmerkung: In der Informatik meint “log” stets den Logarithmus zur Basis 2.
Mathematische Beweise: vgl. z.B.: http://de.wikipedia.org/wiki/Sortierverfahren
6.3 Grenzen der Berechenbarkeit
6.3.1 Praktische Grenzen der Berechenbarkeit
6.3.1.1 Algorithmen mit exponentieller Laufzeit
6.3.1.1.1 Das Rundreiseproblem
Das Problem des Handlungsreisenden (auch Rundreiseproblem, engl. Traveling Salesman
Problem oder Traveling Salesperson Problem kurz TSP) ist ein kombinatorisches
Optimierungsproblem des Operations Research und der theoretischen Informatik. Die
Aufgabe besteht darin, eine Reihenfolge für den Besuch mehrerer Orte so zu wählen, dass die
gesamte Reisestrecke des Handlungsreisenden nach der Rückkehr zum Ausgangsort
möglichst kurz ist.
Seit seiner ersten Erwähnung als mathematisches Problem im Jahre 1930 haben sich viele
Forscher damit befasst und neue Optimierungsverfahren daran entwickelt und erprobt, die
momentan auch für andere Optimierungsprobleme eingesetzt werden. Heute steht eine
Vielzahl von heuristischen und exakten Methoden zur Verfügung, mit denen auch schwierige
Fälle mit mehreren tausend Städten optimal gelöst wurden.
Das Problem des Handlungsreisenden tritt schon in seiner Reinform in vielen praktischen
Anwendungen auf, beispielsweise in der Tourenplanung, in der Logistik oder im Design von
Mikrochips. Noch häufiger tritt es allerdings als Unterproblem auf, wie zum Beispiel bei der
Verteilung von Waren, bei der Planung von Touren eines Kunden- oder Pannendienstes oder
bei der Genom-Sequenzierung. Dabei sind die Begriffe „Stadt“ und „Entfernung“ nicht
wörtlich zu nehmen, vielmehr repräsentieren die Städte beispielsweise zu besuchende
Kunden, Bohrlöcher oder DNA-Teilstränge, während Entfernung für Reisezeit, Kosten oder
den Grad der Übereinstimmung zweier DNA-Stränge steht. In vielen praktischen
Anwendungen müssen zudem Zusatzbedingungen wie Zeitfenster oder eingeschränkte
Ressourcen beachtet werden, was die Lösung des Problems erheblich erschwert.
Komplexitätstheoretisch gehört das TSP zur Klasse der NP-äquivalenten (nicht in
polynomialer Zeitkomplexität lösbar) Probleme. Es wird daher sehr stark angenommen, dass
die Worst-case-Laufzeit jedes deterministischen Algorithmus, der für dieses Problem stets
optimale Lösungen liefert, im besten Fall exponentiell von der Anzahl der Städte abhängt.
Schon für wenige Städte kann die benötigte Laufzeit eines solchen Algorithmus also
unpraktikabel viel Zeit beanspruchen.
Das Problem des Handlungsreisenden kann exakt gelöst werden, indem man die Weglängen
aller möglichen Rundreisen berechnet und dann eine mit der kleinsten Weglänge auswählt.
Das ist aber schon bei einer kleinen Zahl von Städten nicht mehr praktisch durchführbar. Bei
der einfachsten Variante, dem symmetrischen TSP mit n Städten, gibt es (n − 1)! / 2
verschiedene Rundreisen, das sind bei 15 Städten über 43 Milliarden und bei 18 Städten
bereits über 177 Billionen. Wie schnell die Rechenzeit mit wachsender Anzahl von Städten
wächst, zeigt das folgende Beispiel: hat man einen Rechner, der die Lösung für 30 Städte in
einer Stunde berechnet, dann braucht dieser für zwei zusätzliche Städte annähernd die
tausendfache Zeit; das sind mehr als 40 Tage.
Dem intuitiven Vorgehen eines Handlungsreisenden entspricht wohl am ehesten die NearestNeighbor-Heuristik (nächster Nachbar). Von einer Stadt ausgehend wählt diese jeweils die
nächstgelegene als folgenden Ort aus. Dieses wird sukzessive fortgesetzt, bis alle Städte
bereist wurden und der Handlungsreisende zum Ausgangsort zurückgekehrt ist. Die hiermit
verwandte Farthest-Neighbor-Heuristik besucht in jedem Schritt die am entferntesten
gelegene Stadt. In jeder Stadt muss also der kürzeste bzw. weiteste ausgehende Weg gesucht
werden. Maximal kann es pro Stadt nur so viele ausgehende Kanten geben, wie Knoten im
Graphen vorhanden sind. Daraus ergibt sich eine algorithmische Komplexität von O(n²), die
Anzahl der Rechenschritte hängt also quadratisch von der Zahl der Städte ab. Dass diese
Heuristik im Allgemeinen jedoch nicht die beste Lösung liefert, liegt daran, dass die Distanz
zwischen der Ausgangsstadt und der letzten besuchten Stadt bis zuletzt nicht berücksichtigt
wird. Die Nearest- und die Farthest-Neighbor-Heuristik können beliebig schlechte Ergebnisse
liefern, das heißt, es gibt keinen konstanten, instanzunabhängigen Approximationsfaktor für
den Lösungswert im Vergleich zum Optimalwert.
Wissenschaftler des Queen Mary and Westfield College der Universität London fanden
heraus, dass Hummeln bei der Nahrungssuche in der Lage sind, das Problem des
Handlungsreisenden besser und effizienter zu lösen, als dies mit den bislang bekannten
mathematischen Computerverfahren möglich ist. Wie Hummeln diese Ergebnisse erzielen
können, ist dabei noch nicht geklärt.
6.3.2 Theoretische Grenzen der Berechenbarkeit
Lange Zeit haben Philosophen und Mathematiker geglaubt, dass jedes mathematische
Problem algorithmisch lösbar sei. So vertrat z.B. David Hilbert (1862- 1943) die Auffassung,
“dass ein jedes bestimmte mathematische Problem einer strengen
Erledigung notwendig fähig sein müsse, sei es, dass es gelingt, die
Beantwortung der gestellten Frage zu geben, sei es, dass die
Unmöglichkeit seiner Lösung und damit die Notwendigkeit des
Misslingens aller Versuche dargetan wird.“
Erst in unserem Jahrhundert erkannte man, dass nicht alle Probleme, die sich computergerecht
spezifizieren lassen, auch algorithmisch lösbar sind. Ernüchternd war vor allem die
Erkenntnis, dass die Lösung dieser Probleme nicht an den technischen Unzulänglichkeiten der
verfügbaren Geräte scheitert, sondern dass dem menschlichen Denken selbst offenbar
Grenzen gesetzt sind
Beispiel: Das Goldbach-Problem (C. Goldbach, 1742)
Eines der bekanntesten bis heute ungelösten Probleme der Mathematik ist die sogenannte
Goldbachsche Vermutung:
Jede gerade Zahl >= 4 lässt sich als Summe zweier Primzahlen darstellen.
4=2+2
6=3+3
8=5+3
10 = 7 + 3
Bezeichnung:
Eine gerade Zahl n >= 4 hat die Goldbach-Eigenschaft, wenn es eine Zahl k < n gibt mit :
k und n-k sind Primzahlen
Für die Informatik reduzieren wir das Goldbach-Problem auf die Fragestellung:
Gibt es einen Algorithmus, der für eine gegebene gerade Zahl n >= 4 entscheidet, ob sie die
Goldbach-Eigenschaft hat oder nicht ?
Es gibt ihn:
Algorithmus: Test auf Goldbach-Eigenschaft
Eingabe : n {gerade Zahl >= 4}
FÜR k := 2 BIS n DIV 2 WIEDERHOLE
WENN Prim(k) UND Prim(n-k)
DANN Ausgabe : "ja"
SONST Ausgabe : "Nein"
FUNKTION Prim (Zahl : integer) : boolean
WENN Zahl = 2
DANN Prim := TRUE
SONST
{Zahl ist Primzahl genau dann ,wenn Zahl zwischen
2 und Wurzel aus Zahl keine Teiler besitzt}
P := TRUE
i := 2
WIEDERHOLE
WENN Zahl MOD i = 0 DANN P := FALSE
inc( i)
BIS (NOT P) ODER i > Wurzel aus Zahl
ENDE_SONST
Prim := P
Die Goldbach-Eigenschaft ist also entscheidbar. Damit ist das Goldbach-Problem für den
Informatiker gelöst, aber die Goldbachsche Vermutung lässt sich damit weder beweisen noch
widerlegen.
Aufgabe:
Entwickeln Sie ein Programm, das gerade Zahlen größer gleich 4 auf ihre GoldbachEigenschaft testet.
Ein anderes Beispiel stellen die „wundersamen Zahlen“ dar.
Definition:
Eine Menge M natürlicher Zahlen heißt entscheidbar, wenn es einen Algorithmus gibt, der für
jede natürliche Zahl feststellt, ob sie Element von M ist oder nicht. Dieser Algorithmus heißt
dann ein zweiseitiges Entscheidungsverfahren. Ist die Menge M durch eine Eigenschaft
gegeben, so nennen wir diese Eigenschaft entscheidbar.
Bezeichnung:
Die Funktion
g : N ---> N,
g( n) := 1/2 n , falls n gerade und
g( n) := 3 n + 1 , falls n ungerade
heißt Collatz-Funktion(L. Collatz, um 1930).
Bezeichnung:
Eine natürliche Zahl n heißt wundersam, wenn es eine natürliche Zahl k, so dass die CollatzFunktion k mal hintereinander ausgeführt 1 ergibt. :
Beispiele:
n = 5: ________________________________________________________________
n = 15: ________________________________________________________________
(auch Collatz-Folge genannt)
Lothar Collatz stellte die Vermutung auf, daß jede natürliche Zahl wundersam ist. Auch diese
Vermutung ist bis heute weder bewiesen noch widerlegt. Analog zum Goldbach-Problem
schwächen wir die Fragestellung für die Informatik ab:
Gibt es einen Algorithmus, der für eine gegebene natürliche Zahl n entscheidet, ob sie
wundersam ist oder nicht ?
Naheliegend ist folgender Algorithmus: Test auf Wundersamkeit
Eingabe : n
SOLANGE n > 1 WIEDERHOLE
WENN n gerade DANN n := n DIV 2 SONST n := 3 * n + 1
ENDE_SOLANGE BIS n=1
Ausgabe : "Wundersam"
Dieser Algorithmus stellt aber kein zweiseitiges Entscheidungsverfahren dar: Ist n
wundersam, so liefert dieser Algorithmus nach endlich vielen Schritten die Ausgabe
"Wundersam". Gibt man aber eine "unwundersame" Zahl ein, kommt der Algorithmus zu
keinem Ende und damit zu keiner Entscheidung. Allerdings hat bis heute noch niemand eine
unwundersame Zahl gefunden.
Definition:
Eine Menge M natürlicher Zahlen heißt partiell entscheidbar, wenn es einen Algorithmus gibt,
der für jede natürliche Zahl feststellt, ob sie Element von M ist. Dieser Algorithmus heißt
dann ein einseitiges Entscheidungsverfahren. Ist die Menge M durch eine Eigenschaft
gegeben, so nennen wir diese Eigenschaft partiell entscheidbar.
Ein zweiseitiges Entscheidungsverfahren für die Eigenschaft "Wundersam" ist bisher nicht
bekannt.
Beispiel: Game of Life (J. Conway)
Gegeben ist ein zweidimensionales Feld aus n Zeilen und m Spalten, dessen Komponenten
Zellen genannt werden. Jede Zelle besitzt zwei Zustände (lebend, tot). Die zu Beginn
lebenden Zellen bilden die Anfangsgeneration. Eine Folgegeneration entsteht nach folgenden
Regeln :
Eine tote Zelle wird lebendig, wenn drei ihrer acht Nachbarn leben. Eine Zelle stirbt, wenn
mindestens vier Nachbarn leben (Übervölkerung). Eine Zelle stirbt, wenn höchstens ein
Nachbar lebt (Vereinsamung).
Gesucht ist ein Algorithmus, der für eine beliebige gegebene Anfangsgeneration des
Lebensspiels entscheidet, ob diese unsterblich ist oder nicht.
Der Algorithmus könnte so aussehen, dass er den Lebenslauf der Anfangsgeneration Schritt
für Schritt nachvollzieht. Stirbt sie, so ist die Sterblichkeit festgestellt. Stirbt sie aber nicht,
gelangt der Algorithmus zu keinem Ende und damit auch zu keiner Entscheidung. Der
Algorithmus stellt also nur ein einseitiges Entscheidungsverfahren dar. Im Gegensatz zum
Problem der Wundersamkeit ist jedoch für das Game of Life inzwischen bewiesen, dass kein
zweiseitiges Entscheidungsverfahren existiert. Die Sterblichkeit einer Anfangsgeneration im
Game of Life ist also zwar partiell entscheidbar, aber nachgewiesenermaßen nicht
entscheidbar.
Wie man solche Nachweise führt, soll an folgendem Satz gezeigt werden:
SATZ VON TURING (eingeschränkt für Pascal-Programme):
Es gibt kein Pascal-Programm, das für ein beliebiges Pascal-Programm P und eine beliebige
Eingabe x die Frage beantwortet, ob P auf x angewendet nach endlich vielen Schritten
terminiert oder nicht, d.h. die Halte-Eigenschaft für Pascal-Programme ist unentscheidbar und
nur partiell entscheidbar.
Beweis:
Annahme: Es gibt es eine boolesche Funktion "Haelt", die folgendes leistet:
Ist P ein Pascal-Programm und x eine Zeichenfolge, so liefert Haelt nach Eingabe von P und x
den Wert TRUE, wenn P auf x angewendet nach endlich vielen Schritten hält, andernfalls den
Wert FALSE.
FUNCTION Haelt( P, x : Text) : boolean;
BEGIN
IF {P terminiert bei Eingabe von x}
THEN Haelt := TRUE
ELSE Haelt := FALSE;
END;
Diese Funktion wird nun in ein Programm "Seltsam" eingebettet:
PROGRAM Seltsam;
FUNCTION Haelt(P, x : Text) : boolean;
{wie oben }
BEGIN
{Lies Programm P}
WHILE Haelt(P, P) DO; {Endlosschleife}
writeln('Fertig !');
END.
Das heißt:
Hält das Programm P, wenn ihm sein eigener Quelltext eingegeben wird, so begibt sich das
Programm "Seltsam" in eine Endlosschleife, andernfalls hält es an. Da P beliebig ist, kann
insbesondere auch P = "Seltsam" sein. In diesem Fall gilt :
Ist Haelt( Seltsam, Seltsam) = TRUE, so begibt sich "Seltsam" in eine Endlosschleife.
Ist Haelt( Seltsam, Seltsam) = FALSE, so terminiert "Seltsam".
D.h. Das Programm "Seltsam" terminiert bei der Eingabe seines eigenen Quelltextes genau
dann, wenn es nicht terminiert.
Die obige Annahme der Existenz der Funktion "Haelt" führt demnach zu einem
WIDERSPRUCH !
Was heißt das für die Praxis?
Dies bedeutet zum Beispiel, dass es kein universelles Testwerkzeug für Endloszyklen in
einem beliebigen zu untersuchenden Programm geben kann und niemals geben wird. Für
spezielle Programme sind dagegen Verifikationstechniken bekannt, die solche Prüfungen
vornehmen können.
Gödelscher Unvollständigkeitssatz:
In jedem widerspruchsfreien Axiomensystem gibt es Sätze, die nicht mit den Mitteln
dieses Systems bewiesen werden können.
Herunterladen