Algorithmen und Datenstrukturen - Studienbriefshop der Agentur für

Werbung
Leseprobe
Blakowski
Algorithmen und Datenstrukturen
INFORMATIK
Studienbrief 2-050-0208
1. Auflage 2007
HDL
HOCHSCHULVERBUND DISTANCE LEARNING
Verfasser:
Prof. Dr. Gerold Blakowski
Professor für Wirtschaftsinformatik,
insbesondere Telekommunikation und Multimedia
im Fachbereich Wirtschaft
an der Fachhochschule Stralsund
Der Studienbrief wurde auf der Grundlage des Curriculums für das Studienfach „Wirtschaftsinformatik“ verfasst. Die Bestätigung des Curriculums erfolgte durch den
Fachausschuss „Grundständiges Fernstudium Wirtschaftsingenieurwesen“,
dem Professoren der folgenden Fachhochschulen angehörten:
HS Anhalt, FHTW Berlin, TFH Berlin, HTWK Leipzig, HS Magdeburg-Stendal, HS Merseburg,
HS Mittweida, FH Schmalkalden, FH Stralsund, TFH Wildau und WH Zwickau.
1. Auflage 2007
Redaktionsschluss: März 2007
 2007 by Service-Agentur des Hochschulverbundes Distance Learning mit Sitz an der FH Brandenburg.
Das Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere das Recht der
Vervielfältigung und Verbreitung sowie der Übersetzung und des Nachdrucks, bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Kein Teil des Werkes darf in irgendeiner Form ohne schriftliche
Genehmigung der Service-Agentur des HDL reproduziert oder unter Verwendung elektronischer Systeme
verarbeitet, vervielfältigt oder verbreitet werden.
Service-Agentur des HDL
(Hochschulverbund Distance Learning)
Leiter: Dr. Reinhard Wulfert
c/o Agentur für wissenschaftliche Weiterbildung und Wissenstransfer e. V.
Magdeburger Straße 50, 14770 Brandenburg
Tel.: 0 33 81 - 35 57 40
E-Mail: [email protected]
Fax: 0 33 81 - 35 57 49
Inernet: http://www.aww-brandenburg.de
Informatik
Algorithmen und Datenstrukturen
Inhaltsverzeichnis
Randsymbole .............................................................................................................................. 5
Einleitung ................................................................................................................................... 7
Literaturhinweise ....................................................................................................................... 9
1
Algorithmen ............................................................................................................... 10
1.1
Einführung und Zielsetzung ......................................................................................... 10
1.2
Beschreibung eines Algorithmus .................................................................................. 11
1.3
1.3.1
1.3.2
1.3.3
1.3.4
1.3.5
Anforderungen an Algorithmen.................................................................................... 12
Allgemeinheit .............................................................................................................. 12
Ausführbarkeit ............................................................................................................. 12
Terminierung ............................................................................................................... 12
Finitheit ....................................................................................................................... 13
Determinismus und Determiniertheit............................................................................ 13
1.4
1.4.1
1.4.2
1.4.3
1.4.4
1.4.5
Entwurfsprinzipien ...................................................................................................... 13
Brute-Force (vollständige Suche) ................................................................................. 13
Backtracking................................................................................................................ 15
Dynamische Programmierung ...................................................................................... 19
Greedy-Algorithmen .................................................................................................... 21
Teile-und-Herrsche-Prinzip.......................................................................................... 23
1.5
1.5.1
1.5.2
1.5.3
1.5.4
1.5.5
1.5.6
1.5.7
Komplexität ................................................................................................................. 23
Konstanter Zeitaufwand O(1)....................................................................................... 24
Logarithmischer Zeitaufwand O(log(n)) ....................................................................... 24
Linearer Zeitaufwand O(n)........................................................................................... 26
Polynomialer Zeitaufwand O(nk) .................................................................................. 26
Exponentieller Zeitaufwand O(kn) ................................................................................ 27
Vergleich der Komplexitätsklassen .............................................................................. 28
Zusammenfassung und Aufgaben................................................................................. 29
2
Datentypen und -strukturen ...................................................................................... 29
2.1
Abstrakte Datentypen................................................................................................... 30
2.2
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
Stack (Stapel) .............................................................................................................. 31
Operationen des Stacks ................................................................................................ 31
Beispiel ....................................................................................................................... 32
Implementierung.......................................................................................................... 32
Zeitaufwand................................................................................................................. 34
Sonstiges ..................................................................................................................... 34
2.3
2.3.1
2.3.2
2.3.3
2.3.4
2.3.5
Queue (Schlange)......................................................................................................... 35
Operationen der Queue ................................................................................................ 35
Beispiel ....................................................................................................................... 36
Implementierung.......................................................................................................... 36
Zeitaufwand................................................................................................................. 38
Sonstiges ..................................................................................................................... 39
Algorithmen und Datenstrukturen
Informatik
2.4
List (Liste) ...................................................................................................................39
2.5
2.5.1
2.5.2
2.5.3
2.5.4
ArrayList (Random Access List) ..................................................................................39
Operationen der ArrayList ............................................................................................40
Beispiel ........................................................................................................................41
Implementierung ..........................................................................................................41
Zeitaufwand .................................................................................................................43
2.6
2.6.1
2.6.2
2.6.3
2.6.4
2.6.5
2.6.6
2.6.7
LinkedList (Verkettete Liste)........................................................................................44
Operationen der LinkedList ..........................................................................................44
Beispiel ........................................................................................................................45
Implementierung ..........................................................................................................45
Zugriff mit Index..........................................................................................................49
Zeitaufwand .................................................................................................................49
Betrachtung eines gemeinsamen Abstrakten Datentyps Liste ........................................50
Implementierung eines Stacks mit einer LinkedList ......................................................50
2.7
Iteratoren......................................................................................................................50
2.8
2.8.1
2.8.2
2.8.3
2.8.4
Set (Menge) .................................................................................................................52
Operationen des Sets ....................................................................................................52
Beispiel ........................................................................................................................52
Implementierung ..........................................................................................................53
Zeitaufwand .................................................................................................................55
2.9
2.9.1
2.9.2
2.9.3
2.9.4
Map (assoziatives Array)..............................................................................................55
Operationen der Map....................................................................................................56
Beispiel ........................................................................................................................56
Implementierung ..........................................................................................................56
Zeitaufwand .................................................................................................................57
2.10
2.10.1
2.10.2
2.10.3
Tree (Baum) .................................................................................................................57
Implementierung einer Map mit einem Baum ...............................................................58
Zeitaufwand .................................................................................................................59
Zusammenfassung und Aufgaben .................................................................................59
3
Rekursion und Sortierverfahren................................................................................60
3.1
Rekursion.....................................................................................................................61
3.2
3.2.1
3.2.2
3.2.3
3.2.4
Sortierverfahren ...........................................................................................................65
Sortieren durch Einfügen..............................................................................................66
Mergesort.....................................................................................................................67
Weitere Sortierverfahren ..............................................................................................71
Zusammenfassung und Aufgaben .................................................................................75
4
Java Collections Framework......................................................................................76
4.1
4.1.1
4.1.2
Datentypen in Java .......................................................................................................77
Fundamentale Datentypen ............................................................................................77
Generische Klassen ......................................................................................................79
4.2
4.2.1
4.2.2
4.2.3
Struktur des Java Collections Frameworks....................................................................79
Interfaces des Java Collections Frameworks .................................................................79
Implementierungen im Java Collections Framework .....................................................80
Anwendungsbeispiel Liste............................................................................................83
4
Informatik
4.2.4
4.2.5
4.2.6
4.2.7
Algorithmen und Datenstrukturen
Anwendungsbeispiel Map ............................................................................................ 87
Sortieren mit Methoden des Java Collections Frameworks ........................................... 90
Anwendungsbeispiel priorisierte Warteschlange .......................................................... 94
Zusammenfassung und Aufgaben................................................................................. 99
Antworten zu den Kontrollfragen und Lösungshinweise zu den Übungsaufgaben.............. 101
Literaturverzeichnis............................................................................................................... 108
Anhang (Elemente des verwendeten Pseudo-Codes) ................................................................ 109
Sachwortverzeichnis............................................................................................................... 111
Randsymbole
B
D
K
M
P
S
Ü
Z
Beispiel
Definition
Kontrollfragen
Merksatz
Programm
Studienziele
Übungsaufgaben
Zusammenfassung
5
Informatik
Algorithmen und Datenstrukturen
Einleitung
Eine grundlegende Aufgabe der Informatik ist die Entwicklung von Programmen zur Lösung von Problemen. Das Problem wird üblicherweise
dadurch formuliert, dass zu einer Eingabe die zugehörige Ausgabe festgelegt wird, z. B. Eingabe: a,b ∈ N Ausgabe: c ∈ N mit c ist der größte gemeinsame Teiler ggT von a,b. Vor dem Schreiben des Programms muss
der Programmierer ein Verfahren zur Lösung des Problems finden, eine
Lösungsidee entwickeln. Diese lässt sich unabhängig von der zugrunde
liegenden Programmiersprache als Algorithmus beschreiben.
Zur Lösung von verschiedenen Problemstellungen können generalisierte
Entwurfsprinzipien für Algorithmen wiederholt angewendet werden, also
Lösungsideen wiederverwendet werden. Zur Auswahl von Algorithmen
ist in der Praxis die Betrachtung der Komplexität des Algorithmus wichtig. Darunter wird der Zeit- und Speicherplatzaufwand zur Ausführung
eines Algorithmus verstanden.
In diesem Studienbrief wird in Kapitel 1 zunächst der Begriff Algorithmus präzisiert, es werden dann Anforderungen an Algorithmen behandelt, die die Umsetzbarkeit in real ausführbare Programme sicherstellen.
Danach werden ausgewählte häufig verwendete Entwurfsprinzipien anhand von Beispielen erläutert und anschließend die Klassifikation von
Algorithmen gemäß ihres Zeitaufwands anhand von Beispielen vorgestellt.
Algorithmen operieren auf Daten. Die Daten können dabei sehr einfach
strukturiert sein, wie im obigen Beispiel des ggT-Algorithmus mit den
natürlichen Zahlen a,b,c als Ein- bzw. Ausgabedaten. Oftmals sind die
Daten jedoch komplex strukturiert, z. B. bei einem Algorithmus zum Sortieren einer Liste von Kunden. Dabei stellt sowohl der Kunde mit Kundennummer, Vorname, Nachname etc. als auch die Liste, die eine Sammlung von Kunden umfasst, eine Datenstruktur dar.
Eine Datenstruktur kann unabhängig von der zugrunde liegenden Programmiersprache beschrieben werden, und zwar durch Angabe des Typs
der gespeicherten Daten, der Operationen auf den Daten mit ihren Einund Ausgabeparametern sowie der Spezifikation, was eine Operation bewirkt.
In Kapitel 2 werden häufig benötigte wieder verwendbare Datenstrukturen speziell für Sammlungen von Objekten (Sammlungsdatentypen) dargestellt sowie deren beispielhafte Umsetzung aufgezeigt, inklusive der
Algorithmen zur Realisierung der Operationen. Dabei erfolgt auch eine
Betrachtung des Zeitbedarfs der Operationen, da dies für die praktische
Anwendung von hoher Relevanz ist.
Kapitel 3 umfasst zunächst die Erläuterung der Rekursion als wichtiges
Entwurfsprinzip für Algorithmen. Bei einer Rekursion wird der Algorithmus wiederholt auf sich selbst angewendet. Zum Beispiel kann die
Fakultät der Zahl n, also n!, dadurch berechnet werden, dass man n mit
der Fakultät (n −1)! multipliziert, also n! = n*(n − 1)!. Dieses Entwurfsprinzip, welches eine einfache Umsetzung eines Algorithmus für eine
Vielzahl von Problemen ermöglicht, wird an Beispielen erläutert. Des
7
Algorithmen und Datenstrukturen
Informatik
Weiteren werden Algorithmen zum Sortieren von Daten und deren Komplexität bei der Ausführung betrachtet, da das Sortieren eines der am häufigsten in Rechnern ablaufenden Verfahren ist.
In den wichtigsten existierenden Programmierumgebungen sind bereits
Implementierungen der behandelten Datenstrukturen verfügbar. Am Beispiel des Java Collections Frameworks wird in Kapitel 4 die praktische
Verwendung einer solchen Implementierung behandelt. Dies umfasst eine
Erläuterung der vorhandenen Datenstrukturen mit ihren verschiedenen
zugrunde liegenden Implementierungen und deren moderner programmiersprachlichen Umsetzung mit parametrisierbaren Datentypen, den so
genannten generischen Klassen. Des Weiteren wird die Verwendung von
Sortierverfahren im Java Collections Framework zur Sortierung von Daten nach verschiedenen Kriterien und die Realisierung einer Nachrichtenwarteschlange auf Basis einer zur Verfügung stehenden Datenstruktur
beispielhaft aufgezeigt.
Zum Verständnis, insbesondere von Kapitel 4, ist die Kenntnis einer aktuellen objektorientierten Programmiersprache, wie Java oder C# sinnvoll. Eine integrierte Einführung würde den Rahmen des Studienbriefs
überschreiten. Verweise auf einführende Literatur und Online-Quellen
sind in den Literaturhinweisen aufgeführt.
Nach Durcharbeiten des Studienbriefs soll der Leser
S
• die Anforderungen an und grundlegende Entwurfsprinzipien von Algorithmen kennen, Algorithmen gemäß dieser Prinzipien entwerfen
und die Laufzeit von Algorithmen beurteilen können.
• das Konzept des abstrakten Datentyps verstehen, die wichtigsten
Sammlungsdatentypen kennen, für konkrete Anwendungen gezielt den
Sammlungsdatentyp aufgrund der Zugriffstruktur und die Implementierung aufgrund der Zeitanforderungen an die Operationen auswählen
können.
• die wichtigsten Sortierverfahren mit ihren Randbedingungen und ihrem Zeitaufwand kennen und damit gezielt einsetzen können.
• das Prinzip eines Frameworks zur Realisierung der Sammlungsdatentypen und Sortieralgorithmen exemplarisch am Java Collections Framework verstehen und das Java Collections Framework für eigene
Anwendungen verwenden können.
Zur Vertiefung des Lehrstoffs ist es hilfreich, die Beispiele im Text detailliert nachzuvollziehen und bei den Beispielen aus Kapitel 4 auch ausführen zu lassen und dabei mit dem Java Collections Framework zu experimentieren. Zur Übersetzung und Ausführung der Beispiele benötigen
Sie eine Java-Entwicklungsumgebung. Es sind zahlreiche freie Entwicklungsumgebungen für den privaten Gebrauch über das Internet erhältlich,
genannt seien Eclipse [4] und NetBeans [7]. Die Programme des Studienbriefs sind unabhängig von der Entwicklungsplattform lauffähig. Vorausgesetzt wird nur das Java Development Kit 5.0 [3].
8
Algorithmen und Datenstrukturen
K
Ü
Informatik
K 2.1
Was umfasst die Spezifikation eines ADTs?
K 2.2
Erläutern Sie den zentralen Unterschied zwischen Stack und
Queue!
K 2.3
Worin unterscheiden sich die Implementierungen von ArrayList und LinkedList im Wesentlichen bezüglich des Zeitverhaltens?
Ü 2.1
Zeigen Sie, wie mit der LinkedList eine Queue realisiert werden kann!
Ü 2.2
Ergänzen Sie die ArrayList-Implementierung aus Abschnitt
2.5.3 um
– eine Operation int indexOf(Object e), die den Index von
Element e in der Liste zurückgibt. Falls das Element nicht
in der Liste enthalten ist, soll -1 zurückgegeben werden!
– eine Operation boolean contains(Object e), die true zurückgibt, wenn das Objekt in der Liste enthalten ist, false andernfalls!
Ü 2.3
Skizzieren Sie die Implementierung einer Map mit Hilfe von
zwei ArrayLists, wovon eine ArrayList die Keys enthält und
die andere die Values. Verwenden Sie die Methode indexOf
aus Ü 2.2! Geben Sie den Zeitaufwand für die Implementierung der Operation put (key k, Object e) an!
3
Rekursion und Sortierverfahren
In diesem Kapitel wird das wichtige Konzept der Rekursion in Algorithmen und das Sortieren als zentrale, ressourcenaufwändige Aufgabe in
vielen Anwendungen behandelt.
Das Konzept der Rekursion wird an Beispielen erläutert und es wird der
Zeitaufwand für diese Anwendungen diskutiert.
Es folgt die Erläuterung der Funktionsweise und die Analyse des Zeitaufwands für ausgewählte Sortieralgorithmen.
Der Leser soll nach dem Studium dieses Kapitels
S
• Rekursion als Entwurfsprinzip verstehen, zur Lösung von Problemen
anwenden können sowie beurteilen können, wann der Einsatz sinnvoll
ist.
• ausgewählte Sortieralgorithmen bezüglich ihrer Eigenschaften verstehen und damit zielgerichtet für eigene Anwendungen auswählen können.
• das Teile-und-Herrsche-Entwurfsprinzip verstehen.
60
Informatik
3.1
Algorithmen und Datenstrukturen
Rekursion
In den bisherigen Beispielen des Studienbriefs wurde die wiederholte
Ausführung von Programmelementen durch iterative Schleifen realisiert.
Die gesamte Ausführung des Algorithmus ergibt sich dabei aus einer Abfolge von Aufrufen verschiedener Methoden. Bei der Rekursion ruft sich
eine Methode selbst wieder auf, wobei die Komplexität bei jedem Aufruf
reduziert wird, bis eine Abbruchbedingung für die Rekursion erfüllt wird
oder eine vorgegebene Anzahl von Rekursionen durchgeführt wurde.
Durch Rekursion können bestimmte komplexe Probleme einfach algorithmisch umgesetzt werden.
Fakultät
Ein einfaches einführendes Beispiel ist die Berechnung der Fakultät:
n ! = n * ( n − 1) * ( n − 2 ) *...*1. Für n = 0 gilt per Definition 0! = 1. Dies ist
auch die Abbruchbedingung.
Es ist leicht zu erkennen, dass folgende Rekursionsgleichung gilt
 n * (n − 1)!
n! = 
1

für n > 0
für n = 0
Daraus ergibt sich unmittelbar folgender Algorithmus:
Eingabe: n ∈ Ν
Ausgabe: n !∈ Ν
P
Verfahren:
long fakultät(int n) {
if (n = = 0)
return 1;
else
return n * fakultät(n-1);
}
61
Algorithmen und Datenstrukturen
Informatik
Bild 3.1 zeigt den Ablauf der rekursiven Berechnung von fakultät(3):
Ergebnis: fakultät(3) { …
return 3 * fakultät(2);
3*2
}
2*1
fakultät(2) { …
return 2 * fakultät(1);
}
1*1
fakultät(1) { …
return 1 * fakultät(0);
}
1
Bild 3.1
fakultät(0) { …
return 1; // n == 0
…}
Rekursive Berechnung der Fakultät von 3
Der Zeitaufwand hängt linear von der Größe der Zahl n ab. Es werden
genau n+1 Aufrufe von fakultät benötigt mit jeweils konstantem Zeitaufwand. Somit beträgt der Zeitaufwand O(n).
Türme von Hanoi
Ein sehr bekanntes Beispiel für Rekursion ist die Lösung des Türme-vonHanoi-Problems. Die Aufgabe ist, n Steine von aufsteigender Größe von
einer Ausgangsplattform auf eine Zielplattform unter Nutzung einer Zwischenplattform umzubauen. Dabei darf jedoch immer nur ein Stein auf
einmal bewegt werden und niemals ein größerer auf einem kleineren
Stein liegen.
Bild 3.2 zeigt die Lösung für n = 3. Betrachtet man die Zwischenschritte
genauer, stellt man fest, dass sich das Problem rekursiv lösen lässt.
Schritt 1: Schichte zuerst die Steine 1 bis n-1 auf die Zwischenplattform
(Bewegung1 bis 3 in der Abbildung).
Schritt 2: Schichte Stein n von der Ausgangs- zur Zielplattform (Bewegung 4).
Schritt 3: Schichte die Steine 1 bis n-1 von der Zwischenplattform zur
Zielplattform (Bewegung 5 bis 7).
62
Informatik
Algorithmen und Datenstrukturen
Bewegung
1
2
3
Ausgangsplattform
1
Zwischenplattform
Zielplattform
Zwischenplattform
Zielplattform
2
3
Ausgangsplattform
1
2
3
2
1
Ausgangsplattform
Zwischenplattform
Zielplattform
3
1
2
Ausgangsplattform
Zwischenplattform
3
4
Ausgangsplattform
Zielplattform
1
2
3
Zwischenplattform
Zielplattform
5
1
2
3
Ausgangsplattform
Zwischenplattform
Zielplattform
6
2
3
1
Ausgangsplattform
Zwischenplattform
7
Zielplattform
1
2
3
Ausgangsplattform
Bild 3.2
Zwischenplattform
Zielplattform
Türme von Hanoi für n = 3
Rekursiv lässt sich das Verfahren folgendermaßen formulieren:
hanoi(n,Ausgangsplattform,Zielplattform,Zwischenplattform) {
P
if (n= =1) {
Bewege Stein von Ausgangsplattform zur Zielplattform;
} else {
// Schritt 1: Bewege die Steine 1 bis n-1 von der
// Ausgangsplattform
// zur Zwischenplattform (unter Nutzung der Zielplattform als
// temporäre Zwischenplattform)
hanoi(n-1,Ausgangsplattform,Zwischenplattform,Zielplattform);
// Schritt 2: Bewege Stein n zur Zielplattform
63
Algorithmen und Datenstrukturen
Informatik
Bewege Stein n von Ausgangsplattform zur Zielplattform;
// Schritt 3: Bewege die Steine 1 bis n-1 von der
// Zwischenplattform zur Zielplattform (unter Nutzung der
// Ausgangsplattform als temporäre Zwischenplattform)
hanoi(n-1,Zwischenplattform,Zielplattform,Ausgangsplattform);
}
}
}
Bei jedem Aufruf von Hanoi wird ein Stein bewegt. Zusätzlich wird für
n > 1 zweimal Hanoi aufgerufen. Also ergibt sich als Zeitaufwand für
Hanoi für n Steine: 1 + 21 + 22 +…+ 2n−1. Der Zeitaufwand ist somit
O(2n).
Fibonacci-Zahlen
An diesem Beispiel soll erläutert werden, dass beim Einsatz von Rekursion neben der einfachen Umsetzung in einen Algorithmus auch der Zeitaufwand zu berücksichtigen ist.
Die Fibonacci-Zahlen sind folgendermaßen rekursiv definiert:
0


f (n), n ∈ Ν = 
1
 f (n − 1) + f (n − 2)

für
für
n=0
n =1
für
n≥2
Daraus leitet sich folgender rekursiver Algorithmus ab:
P
Eingabe: n
Ausgabe: f(n)
int fib(int n)
{
if (n<=1)
return n;
else
return fib(n-1) + fib(n-2);
}
Der Zeitaufwand zur Berechnung von fib(n) ist in dieser Implementierung O(2n), da für die Berechnung von fib(n) eine Berechnung von
fib(n-2) und eine Berechnung von fib(n-1) benötigt wird usw.
64
Informatik
Algorithmen und Datenstrukturen
Bild 3.3 verdeutlicht dies für das Beispiel fib(5). Insgesamt wird
1 * fib(5), 1 * fib(4), 2*fib(3), 3*fib(2), 5*fib(1) und 3*fib(0) berechnet.
Der Nachteil dieses rekursiven Algorithmus ist, dass unnötig mehrfache
Berechnungen vorgenommen werden, z. B. fib(3) und fib(2).
Hier bietet sich als Alternative eine iterative Lösung an, die sukzessive
fib(0), fib(1), fib(2) bis fib(n) berechnet und dabei jeweils die letzten
zwei Zwischenergebnisse speichert. Damit kann der Zeitaufwand O(n)
erreicht werden.
fib(5)
fib(4)
fib(3)
+
20
fib(2)
+ fib(1)
fib(3)
+
fib(2)
fib(2)
fib(1) + fib(0)
fib(1) + fib(0)
+
fib(1)
fib(1) + fib(0)
Bild 3.3
Aufrufbaum für rekursive Fibonacci-Implementierung
Rekursive Algorithmen sollten immer daraufhin untersucht werden, ob
die Zeitkomplexität mit den Rekursionsschritten abnimmt und keine unnötigen mehrfachen Berechnungen vorgenommen werden.
3.2
Sortierverfahren
Das Sortieren von Daten ist eine häufig wiederkehrende Aufgabe in Programmen, z. B. das Sortieren von Tabellen nach Namen, Kundennummern etc.
Es sind zahlreiche Algorithmen mit unterschiedlicher Laufzeiteffizienz
verfügbar. Im Folgenden werden wichtige in der Praxis eingesetzte Algorithmen vorgestellt. Dabei wird exemplarisch davon ausgegangen, dass
ein Array a natürlicher Zahlen der Kapazität n mit natürlichen Zahlen
aufsteigend sortiert werden soll.
65
Algorithmen und Datenstrukturen
3.2.1
Informatik
Sortieren durch Einfügen
Beim Sortieren durch Einfügen wird zunächst ein zusätzliches leeres Array s der Länge n angelegt, in das nacheinander a[0] bis a[n-1] in sortierter Reihenfolge eingefügt werden.
P
Eingabe: int []a: Array der Länge n
Ausgabe: int []s: Array der Länge n mit s[i]<=s[j] für 0<=i<j<=n-1
sortierenDurchEinfügen(int[] a, int[] s, int n) {
// Sortiere nacheinander die Elemente des Arrays a ein
for (int i=0;i<n;i++) {
// i-1 Elemente sind bereits sortiert eingefügt
int j = i;
// Suche jetzt mit Hilfe der Laufvariable j die richtige Position für
// das Element a[i]. Beginne den Vergleich mit dem größten bereits
// eingefügten Element s[i-1].
while (j>0 && s[j-1] > a[i]) {
// Element s[j-1] ist größer als Element a[i]
// Dann muss s[j-1] nach s[j] verschoben werden,
// um Platz für a[i] zu machen.
s[j]=s[j-1];
// Prüfe nächsten Index
j--;
}
// Einfügen von a[i] an der korrekten Position
s[j]=a[i];
}
}
66
Informatik
Algorithmen und Datenstrukturen
Bild 3.4 zeigt das Sortieren durch Einfügen an einem Array der Kapazität 5:
Array a
4
1
8
5
Array s
3
4
4
4
4
1
1
1
8
8
8
5
5
5
4
3
1
4
1
4
3
8
3
1
4
1
8
5
4
3
5
4
1
Bild 3.4
8
3
8
5
4
8
5
8
Beispiel Sortieren durch Einfügen
Der Zeitaufwand von Sortieren durch Einfügen ist O(n2).
In der inneren while-Schleife müssen im schlechtesten Fall alle bereits
einsortierten Elemente verschoben werden, um Platz für das einzufügende Element zu schaffen. Es ergibt sich somit ein Zeitaufwand von
1 + 2 + 3 +…+ n − 1, also O(n2).
Der schlechteste Fall tritt ein, wenn a absteigend sortiert ist. Im besten
Fall ist a bereits aufsteigend sortiert, dann beträgt der Zeitaufwand der
inneren Schleife immer nur 1, nämlich das Einfügen an der letzten Stelle
von s. Insgesamt ist der Zeitaufwand dann O(n).
Sortieren durch Einfügen zeigt insgesamt ein sehr gutes Verhalten, wenn
die Daten bereits weitestgehend vorsortiert sind, so dass der Zeitaufwand
sich O(n) annähert.
Sortieren durch Einfügen ist ein stabiles Sortierverfahren, da die Reihenfolge gleicher Elemente beim Sortieren erhalten bleibt.
3.2.2
Mergesort
Mergesort ist ein stabiles Sortierverfahren, basierend auf dem Teile-undHerrsche-Entwurfsprinzip:
– Zuerst wird das Problem in Teilprobleme zerlegt, die separat gelöst
werden.
67
Algorithmen und Datenstrukturen
Informatik
– Anschließend werden die Teillösungen zu einer Gesamtlösung zusammengesetzt.
Ausgangspunkt von Mergesort ist, dass zwei bereits sortierte Folgen a,b
mit linearem Zeitaufwand zu einer sortierten Folge merge zusammengefügt werden:
P
Eingabe: int []a: Array der Länge n mit a[i]<=a[j] für 0<=i<j<=n-1
int []b: Array der Länge n mit b[i]<=b[j] für 0<=i<j<=n-1
Ausgabe: int merge[]: Array der Kapazität 2n mit merge[i]<=merge[j]
für 1<=i<j<=2n und
{a[0],..., a[n-1], b[0],.., b[n-1]} = {merge[0],..., merge[n-1]}
Algorithmus:
merge(int[] a, int[] b) {
int[] merge = new int[2*n]; // Merge-Array erzeugen
int index_a = 0;
// Position in a
int index_b = 0;
// Position in b
int index_merge = 0; // Position in merge
//
while (index_a<n && index_b<n) {
// Solange a oder b noch nicht komplett durchlaufen sind
if (a[index_a]<b[index_b]) {
// Element aus a kleiner, deshalb in merge einsortieren
merge[index_merge]=a[index_a];
index_a++; // nächstes Element aus a betrachten
index_merge++;
} else {
// Element aus b kleiner, deshalb in merge einsortieren
merge[index_merge]=b[index_b];
index_b++; // nächstes Element aus b betrachten
index_merge++;
}
}
// Jetzt noch die restlichen Elemente aus a und b einsortieren
for (;index_a<n;index_a++) {
merge[index_merge++] = a[index_a];
}
68
Informatik
Algorithmen und Datenstrukturen
for (;index_b<n;index_b++) {
merge[index_merge++] = b[index_b];
}
return merge;
}
Die Sortierung in Mergesort erfolgt dadurch, dass
– die Hälften eines zu sortierenden Arrays jeweils getrennt sortiert werden und
– dann mittels Merge zu einer sortierten Folge zusammengefügt werden.
Eingabe: int []a: Array der Länge n, int start, int ende
P
Ausgabe: int []a: Array der Länge n mit a[i]<=a[j] für start<=i<j<=ende
mergesort(int[]a,int start,int ende) {
// start und ende sind die Indizes, die den Bereich im Array
// angeben, der sortiert werden soll
if (start>=ende)
return; // Ende der Rekursion
int mitte = (start+ende) / 2; // Array teilen
mergesort(a,start,mitte); // Linken Bereich sortieren
mergesort(a,mitte+1,ende); // Rechten Bereich sortieren
// Die Bereiche wieder zusammenmischen mit einer
// Variante von merge, die a[start,…,mitte] mit
// a[mitte+1,..,ende] vermischt.
merge(a,start,mitte,ende);
}
Bild 3.5 zeigt die Sortierung eines Arrays der Länge 7. Zunächst wird rekursiv das Array immer weiter zerlegt und dann wieder Stück für Stück
mittels merge zusammengefügt:
69
Algorithmen und Datenstrukturen
Informatik
ms(0,6)
Halbieren
11
4
1
28
15
3
ms(0,3)
11
4
ms(4,6)
1
28
ms(0,1)
11
4
1
ms(1,1)
11
4
1
11
ms(4,5)
28
ms(2,2)
1
3
15
ms(6,6)
3
9
ms(3,3)
ms(4,4)
ms(5,5)
28
15
3
28
3
9
15
Merge
4
15
ms(2,3)
ms(0,0)
9
1
4
1
Bild 3.5
11
28
3
4
3
9
11
15
9
15
28
Beispiel für Mergesort
Beim ersten Aufruf von Mergesort entsteht der Zeitaufwand für das Min
schen der sortierten halben Arrays, also 2 ⋅ = n Auf der zweiten Ebene
2
zweimal der Zeitaufwand des Mischens der jeweiligen Viertel der Arrays
n
4 ⋅ = n etc. Der Zeitaufwand entspricht somit der Anzahl der Aufruf4
ebenen i. i ist beschränkt auf log(n). Insgesamt ergibt sich daraus ein
Worst-Case-Zeitaufwand von O(n*log(n)).
Bei Verfahren, die wie Mergesort auf einem Vergleich von Elementen
beruhen, ist O(n*log(n)) auch die theoretische untere Schranke für die
Worst-Case- und Average-Case-Zeitkomplexität.
Mergesort kann optimiert werden, indem bei kleinen zu sortierenden Arrays unterhalb eines Schwellwertes statt Mergesort Sortieren durch Einfügen verwendet wird, was bei kleinen Arrays effizienter ist als die rekursiven Aufrufe bei Mergesort. Zudem kann vor dem Mischen zuerst
geprüft werden, ob das größte Element des linken Teilarrays kleiner ist
als das kleinste Element des rechten Teilarrays. In diesem Fall kann das
Mischen unterbleiben, da bereits eine Sortierung vorliegt.
70
Informatik
Algorithmen und Datenstrukturen
mergesort(int[]a,int start,int ende) { // optimiert
P
// start und ende sind die Indizes, die den Bereich im Array
// angeben, der sortiert werden soll
if (ende-start < schwellwert) {
sortieren_durch_einfügen(a,start,ende);
return;
}
int mitte = (start+ende) / 2; // Array teilen
mergesort(a,start,mitte); // Linken Bereich sortieren
mergesort(a,mitte+1,ende); // Rechten Bereich sortieren
if (a[mitte]<a[mitte+1])
return; // Linker und rechter Bereich zusammen schon sortiert
// Die Bereiche wieder zusammenmischen mit einer
// Variante von merge, die a[start,…,mitte] mit
// a[mitte+1,..,ende] vermischt.
merge(a,start,mitte,ende);
}
3.2.3
Weitere Sortierverfahren
Quicksort und Bucketsort sind zwei weitere bekannte Sortieralgorithmen,
die hier kurz betrachtet werden sollen.
Quicksort
Bei Quicksort wird zuerst ein Element p aus dem zu sortierenden Array
ausgewählt. Das Array wird im nächsten Schritt so umsortiert, dass p an
der gemäß der Endsortierung richtigen Position platziert wird, alle anderen Elemente mit einem kleineren Wert p unterhalb von p platziert werden und alle größeren Elemente oberhalb. Dieser Vorgang wird rekursiv
wiederholt, bis das gesamte Array sortiert ist.
71
Algorithmen und Datenstrukturen
P
Informatik
Eingabe: int []a: Array der Länge n, int start, int ende
Ausgabe: int []a: Array der Länge n mit a[i]<=a[j] für start<=i<j<=ende
quicksort(int a[],int start,int ende) {
if (ende-start >=1) {
int index = start;
// Jetzt umsortieren der Elemente im Array
for (int zeiger = start; zeiger < ende; zeiger++) {
if (a[zeiger] <= a[ende]) {
// a[zeiger] kleiner als Element am Ende!
// Dieses mit größerem Element auf
// kleinerem Index vertauschen.
tauscheWerte(index,zeiger);
index++;
// index-1 entspricht Anzahl Elemente
// kleiner gleich a[ende]
}
}
// Jetzt das Element a[ende] in die
// endgültige Position bringen
tauscheWerte(index,ende);
quicksort(a,start,index-1);
quicksort(a,index+1,ende);
}
}
72
Informatik
Algorithmen und Datenstrukturen
Bild 3.6 zeigt die Positionierung des letzten Elements des Arrays an die
zugehörige Position in der endgültigen Sortierung:
3
9
7
2
5
Index
9>5
3
9
7
2
Zeiger
5
3<5, tauschen Wert Index,Zeiger
3
9
7
2
5
7>5
2
3
7
9
5
2<5, tauschen Wert Index,Zeiger
3
2
7
9
5
tauschen Wert Index,Ende
2
3
5
9
7
5 ist richtig platziert, alle Elemente links davon sind kleiner,
alle Elemente rechts davon größer
Bild 3.6
Beispiel des Positionierens des letzten Elements
Quicksort gilt als sehr schnelles Verfahren mit einem AverageZeitaufwand von O(n*log(n)), allerdings hat es einen Worst-CaseZeitaufwand von O(n2), z. B. auf bereits sortierten Listen. Quicksort ist
nicht stabil.
Bucketsort
Im Gegensatz zu den vorher genannten Verfahren, kann mit Bucketsort
ein Average-Zeitaufwand von O(n) erreicht werden. Hierzu muss die
Voraussetzung erfüllt sein, dass die Werte der zu sortierenden Elemente
gleichverteilt in einem Intervall [unten,oben] liegen. Es wird ein zusätzliches Array b (Bucket-Array) der Länge n angelegt, das das Intervall [unten,oben] in b-gleichgroße Intervalle unterteilt, wobei jedes b[i] mehrere
Elemente aufnehmen kann, z. B. in einer Liste.
Im folgenden Beispiel werden n reale Zahlen aus dem Intervall [0,..,n)
sortiert. b[i] repräsentiert das Intervall [i,i+1).
73
Algorithmen und Datenstrukturen
P
Informatik
Eingabe: float a[]: Array der Länge n
Ausgabe: float a[]: Array der Länge n mit a[i]<=a[j] für 0<=i<j<n
bucketsort(float a[],int n) {
// Bucket-Array initial jeweils mit leerer Liste
Liste [] b = new Liste[n]; // b[i] sei leere Liste
for (int i=0;i<n;i++) { // Schritt 1
// Füge Elemente in passenden Bucket
b[(int)a[i]].add(a[i]);
}
for (int i=0;i<n;i++) { // Schritt 2
SortiereListe(b[i]); // anderes Sortierverfahren
}
for (int i=0;i<n;i++) { // Schritt 3
b[i] auslesen und sortiert in a eintragen
}
}
Bild 3.7 zeigt ein Beispiel für das Sortieren mit n = 5:
Zu sortierendes Array
3,6
1,5
4,6
2,2
4,3
Elemente in Bucket-Array einsortieren
[0,...,1)
[1,...,2)
1,5
[2,...,3)
2,2
[3,...,4)
3,6
[4,...,5)
4,6
4,3
ggf. Listen in Bucket-Array sortieren
[4,...,5)
4,3
4,6
Elemente sortiert aus Bucket-Array auslesen
1,5
Bild 3.7
74
2,2
3,6
Beispiel für Sortieren mit Bucketsort
4,3
4,6
Informatik
Algorithmen und Datenstrukturen
Der Worst-Case-Zeitaufwand tritt ein, wenn alle Elemente aus a im gleichen Bucket b[i] abgelegt werden. Dann entspricht der Zeitaufwand dem
des eingesetzten Sortierverfahrens in der zweiten Schleife. Der AverageCase-Zeitaufwand beträgt jedoch aufgrund der angenommenen Gleichverteilung O(n).
3.2.4
Zusammenfassung und Aufgaben
Die Verwendung von Rekursion wurde an drei Beispielen vorgestellt: Fakultät als einfaches einführendes Beispiel, die Türme von Hanoi als Beispiel für eine Anwendung der Rekursion, die eine einfache algorithmische
Umsetzung ermöglicht, bei der die Komplexität in jedem Rekursionsschritt
reduziert wird und ein bestmöglicher Zeitaufwand erreicht wird. Die rekursive Version der Fibonacci-Zahlen ist ein Beispiel für eine Anwendung, bei
der der Zeitaufwand gegen eine rekursive Lösung spricht.
Z
Bei den Sortierverfahren wurden die Funktionsweise, der Zeitaufwand
und Randbedingungen für den Einsatz für die Verfahren Sortieren durch
Einfügen, Mergesort, Quicksort und Bucketsort erläutert (s. Tabelle 3.1).
Mergesort und Quicksort sind zudem Beispiele für rekursive Teile-undHerrsche-Algorithmen.
Tabelle 3.1 Zeitaufwand der Sortierverfahren
Sortierverfahren
Worst-CaseZeitaufwand
2
Average-CaseZeitaufwand
2
Sortieren durch Einfügen
O(n )
O(n ), aber gut bei
vorsortierten Folgen
Mergesort
O(n*log(n))
O(n*log(n))
Quicksort
O(n ) bei vorsortierten
Folgen
O(n*log(n))
Bucketsort
(mit Mergesort zur Sortierung der Buckets)
O(n*log(n))
O(n)
bei Gleichverteilung
der Elemente
2
K 3.1
Was wird unter Rekursion verstanden?
K 3.2
Erläutern Sie, wann der Einsatz jeweils von Sortieren durch
Einfügen, Mergesort und Bucketsort günstig ist!
Ü 3.1
Formulieren Sie einen rekursiven Algorithmus, der feststellt,
ob ein Wort ein Palindrom ist, d. h. von vorne und hinten gelesen gleich ist, z. B. Rentner, Reittier!
Ü 3.2
Skizzieren Sie einen iterativen Algorithmus zur Berechnung
von fib(n)!
Ü 3.3
Vollziehen Sie schriftlich die Sortierung der Zahlenfolge
6,3,8,2,7,5 bei den Algorithmen Sortieren durch Einfügen,
Mergesort und Quicksort in den einzelnen Schritten nach!
K
Ü
75
Herunterladen