Vorlesung Informatik 1
Fachhochschule für Technik Esslingen Studiengang Wirtschaftsinformatik
Algorithmen
Dr. rer. nat. Andreas Rau
http://www.hs-esslingen.de/~rau
andreas.rau@hs-esslingen.de
In diesem Foliensatz sollen verschiedene Algorithmen genauer untersucht werden. Dabei soll für jeden Algorithmus Problem, Lösungsidee und technische Umsetzung betrachtet werden um so Einblicke zur Entwicklung eigener Lösung zu gewinnen.
Klassische Probleme der Informatik sind u.a.
● Sortieren
● Suchen
Weitere Übungen anhand des „interaktiven Labors“...
Inhalt
Sortieralgorithmen
Problemstellung
Sowohl Menschen als auch Maschine (vgl. Abschnitt über Suchen) tun sich mit sortierten Daten leichter. Man denke nur an ein unsortiertes Telefonbuch... Daher ist die Aufgabe, eine gegebene Datenmenge zu sortieren ein klassisches Problem der Datenverarbeitung.
Naturgemäß gibt es mehr als eine Lösung für dieses Problem. Jedoch sind nicht alle Lösungen gleich gut. Zur Bewertung kann man z.B. folgende Eigenschaften heranziehen:
Zeitverhalten
Wie schnell steigt die Rechenzeit in Abhängigkeit der Datenmenge an?
Schlechte Algorithmen brauchen für doppelt soviele Daten viermal solange...
Platzbedarf
Wie schnell steigt der Speicherplatz in Abhängigkeit der Datenmenge an?
Üblicherweise wird gefordert, daß ohne zusätzlichen Speicherplatz sortiert wird!
Stabilität
Damit ist gemeint, ob gleiche Elemente umsortiert werden oder nicht.
Dies ist z.B. relevant, wenn man hintereinander nach verschiedenen Kriterien sortiert.
Sortieren(1)
Anwendungsfälle
Sortierverfahren können in folgenden Anwendungsfällen eingesetzt werden:
● Komplettes Sortieren einer ungeordneten Datenmenge
● Umsortieren einer umgekehrt sortierten Datenmenge
● Einsortieren eines neuen Datensatzes in eine bereits sortierte Menge
Der zweite Fall ist im Grunde ein pathologischer Sonderfall des ersten Falls bei dem
manche Sortierverfahren besonders schlecht funktionieren (wenn man den Fall erkennen würde, wäre es natürlich einfach).
Grundoperationen
Wie wir sehen werden werden zur Sortierung die folgenden Grundoperationen benötigt:
● Zugreifen / Vergleichen
● Vertauschen
● Einfügen
Diese funktionieren mit unterschiedlichen Datenstrukturen unterschiedlich gut...
Sortieren(2)
Sortieren(3) Vorüberlegungen
Überlegen Sie kurz wie sie in folgenden Fällen selbst beim sortieren vorgehen (würden)
● Sortieren von Karten beim Kartenspiel
● Sortieren von Büchern / Filmen / CDs in einem Regal
● Sortieren eines Briefen (Stapel) nach Eingangsdatum
● Sortieren von ...
Praktische Übung
Führen sie nun folgende praktische Übung durch:
● Eine DIN A4 Seite längs falten und reißen
● Die Teile noch 2x falten und reißen um schließlich 8 Teile zu erhalten
● Die 8 Teile mit „Zufallszahlen“ beschriften und mischen
● Die oben überlegten Lösungsverfahren praktisch ausprobieren
Sortieren(4) Vorbereitung
Für die Evaluierung der verschiedenen Sortierverfahren werden wir Arrays mit Zufallszahlen verwenden. Zur Generierung soll die Klasse „Generator“ dienen.
public class Generator {
public static void printNumbers( int[] numbers) { boolean delim = false;
for ( int i : numbers) {
if ( delim) System.out.print( ", ");
System.out.printf( "%3d", i);
delim = true;
}
System.out.println();
}
public static int[] generateRandomNumbers( int n, int min, int max) { int[] numbers = new int[n];
for ( int i=0; i<n; ++i) {
numbers[i] = (int)(Math.random()*(max-min+1) + min);
}
return numbers;
}
// ...see next page
Sortieren(5)
// ...continued from previous page
public static int[] generateAscendingNumbers(int n, int min, int max, int jitter) { int[] numbers = new int[n];
for ( int i=0; i<n; ++i) {
min = min + (int)(Math.random()*jitter + 1);
numbers[i] = Math.min( min, max);
}
return numbers;
}
public static int[] generateDescendingNumbers(int n, int min,int max, int jitter) { int[] numbers = new int[n];
for ( int i=0; i<n; ++i) {
max = max - (int)(Math.random()*jitter + 1);
numbers[i] = Math.max( min, max);
}
return numbers;
}
public static void main( String[] args) {
printNumbers( generateRandomNumbers( 10, 1, 100));
printNumbers( generateAscendingNumbers( 10, 1, 100, 20));
printNumbers( generateDescendingNumbers( 10, 1, 100, 20));
} }
Sortieren(6) Rahmenprogramm
Der Rahmen zur Implementierung der Sortieralgorithmen sieht dann wie folgt aus:
public class SortBy<Verfahren> {
public static int[] sort( int[] numbers) { // Implementierung des Algorithmus
return numbers;
}
public static void main( String[] args) { System.out.println( "SortBy<Verfahren>\n");
int[] numbers = Generator.generateRandomNumbers( 10, 1, 100);
// int[] numbers = Generator.generateAscendingNumbers( 10, 1, 100, 20);
// int[] numbers = Generator.generateDescendingNumbers( 10, 1, 100, 20);
Generator.printNumbers( numbers);
System.out.println();
long t1 = System.currentTimeMillis();
sort( numbers);
long t2 = System.currentTimeMillis();
System.out.println();
Generator.printNumbers( numbers);
System.out.println( "\nDone in " + (t2-t1) + "ms");
}
}
Sortieren(7) Sortieren durch direktes Einfügen
Idee: Sortieren wie beim Kartenspielen
Wir vergleichen jedes Element mit allen Vorgängern und fügen es am richtigen Platz ein.
Dadurch wird der Anfang der Wertemenge vorsortiert und Stück für Stück ergänzt.
public static int[] sort( int[] numbers) { for ( int i=1; i<numbers.length; ++i) {
int x = numbers[i]; aktuelles Element „nehmen“
int j = i-1;
while ( j>=0) { // alle Vorgänger betrachten
if ( numbers[j]<=x) break; // wenn Vorgänger nicht größer abbrechen (=stabil) numbers[j+1] = numbers[j]; // sonst nachrücken und weiter
j--;
}
numbers[j+1] = x; // aktuelles Element „einfügen“
Generator.printNumbers( numbers); // Zwischenergebnis ausgeben }
return numbers;
}
Machen sie sich die Arbeitsweise des Algorithmus anhand der Ausgabe der Zwischenschritte klar. Spielen Sie anschließend verschiedene Szenarien durch.
ACHTUNG: Aussagekräftige Zeitmessungen nur bei auskommentierten Ausgaben.
Sortieren(8) Sortieren durch direktes Auswählen
Idee: Systematische Suche
Wir suchen nacheinander das kleinste, zweitkleinste, ... Element und fügen es am richtigen Platz ein. Dadurch wird die Wertemenge Stück für Stück final sortiert.
public static int[] sort( int[] numbers) { for ( int i=0; i<numbers.length-1; ++i) { int x = i; // aktuelles Element auswählen for ( int j=i+1; j<numbers.length; ++j) {
if ( numbers[j]<numbers[x]) x = j; // anderes Element auswählen }
// Gefundenes Element durch Austausch an richtige Position bringen int t = numbers[i];
numbers[i] = numbers[x];
numbers[x] = t;
Generator.printNumbers( numbers); // Zwischenergebnis ausgeben }
return numbers;
}
Machen sie sich die Arbeitsweise des Algorithmus anhand der Ausgabe der Zwischenschritte klar. Spielen Sie anschließend verschiedene Szenarien durch.
ACHTUNG: Aussagekräftige Zeitmessungen nur bei auskommentierten Ausgaben.
Sortieren(9)
Sortieren durch Wandern (aka Bubblesort)
Idee: Bei der Suche bereits kleinere Umstellungen vornehmen
Wir suchen nacheinander das größte, zweitgrößte, ... Element indem wir vom gegenüberliegenden Ende aus Schritt für Schritt vergleichen und uns „hochtauschen“.
public static int[] sort( int[] numbers) { for ( int i=numbers.length-1; i>0; --i) { for ( int j=0; j<i; ++j) {
if ( numbers[j]>numbers[j+1]) {
// Gefundenes Element durch Austausch wandern lassen int t = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = t;
} }
Generator.printNumbers( numbers); // Zwischenergebnis ausgeben }
return numbers;
}
Machen sie sich die Arbeitsweise des Algorithmus anhand der Ausgabe der Zwischenschritte klar. Spielen Sie anschließend verschiedene Szenarien durch.
ACHTUNG: Aussagekräftige Zeitmessungen nur bei auskommentierten Ausgaben.
Sortieren(10)
Sortieren durch „geniale Idee“ (aka Quicksort)
Idee: Tauschen über große Distanz mit rekursivem „teilen und herrschen“
public static int[] sort( int[] numbers, int l, int r) {
int x = numbers[(l+r)/2]; // "willkürliches" Vergleichselement wählen int i = l, j = r;
while (i<=j) {
while ( numbers[i]<x) i++; // linken Tauschpartner suchen while ( numbers[j]>x) j--; // rechten Tauschpartner suchen if (i<=j) { // Tausch durchführen
int t = numbers[i];
numbers[i] = numbers[j];
numbers[j] = t;
i++;
j--;
}
} // jetzt liegt Partitionierung in Teile<=k und Teile>=k vor System.out.printf( "%2d-%2d(%2d):", l, r, x);
Generator.printNumbers( numbers);
if (l<j) sort( numbers, l, j); // Teilintervall sortieren wenn nicht leer if (i<r) sort( numbers, i, r); // Teilintervall sortieren wenn nicht leer return numbers;
}
public static int[] sort( int[] numbers) {
return sort( numbers, 0, numbers.length-1); // Einstieg in Rekursion }
Sortieren(11)
Quicksort ist natürlich „starker Tobak“ und erschließt sich (wenn dann) nur nach mehrmaligem Durchdenken und/oder manuellem Nachvollziehen.
Festzuhalten ist jedoch, daß Quicksort zwar (im Mittel) der schnellste Algorithmus für zufällig sortierte Werte (daher der Name) jedoch beileibe nicht das Maß aller Dinge ist. So hat Quicksort die unangenehme Eigenschaft, in bestimmten Fällen besonders viel Zeit oder besonders viel Speicher zu benötigen (Details siehe Literatur).
Dabei spielt u.a. die Wahl des Vergleichselements eine besondere Rolle. Für das Sortieren kleiner Datenmengen ist Quicksort kaum schneller (und weniger „berechenbar“
als einfache Sortiermethoden).
Für den Anwendungsfall „Einfügen eines einzelnen Elements“ sind einfache Sortierverfahren bzw. geeignete Suchverfahren ohnehin die bessere Wahl.
Suchalgorithmen
Suchen(1) Problemstellung
Neben dem Sortieren ist Suchen die zweite klassische Aufgabe bei der elektronischen Datenverarbeitung. Die Sortierung ist sozusagen nur eine Vorstufe davon, da man in sortierten Daten schneller suchen kann. Bei der Bewertung von Suchalgorithmen zählt vor allem das
Zeitverhalten
Wie schnell steigt die Rechenzeit in Abhängigkeit der Datenmenge an?
Dagegen spielt der Platzbedarf i.d.R. keine Rolle da ja direkt in den Daten gesucht wird.
Wie hoch ist wohl der maximale Zeitbedarf im worst-case?
Grundoperationen
Beim Suchen gibt es im wesentlichen eine Grundoperation
● Vergleichen
dies klingt zwar zunächst trivial, kann aber bei komplexen Objekten auch teuer sein!
Suchen(2) Anwendungsfälle
Von dem eigentlichen Grundproblem „finde einen gesuchten Wert in einer vorgegebenen Menge“ gibt es kleine Variationen:
● prüfe, ob der gesuchte Wert enthalten ist
● zähle, wie oft der gesuchte Wert enthalten ist
● finde das erste Vorkommen des Werts (und liefere die Position zurück)
● finde das nächste Vorkommen des Werts
● finde das letzte Vorkommen des Werts
● finde alle Vorkommen des Werts
Einige dieser Fragen lassen implizit die Beantwortung anderer Fragen zu. Welche?
Vorüberlegungen
Wie würden Sie in der realen Welt folgende Suchprobleme lösen
● Suchen einer CD im Regal
● Suchen eines Motivs in einer Diasammlung
● Suchen einer Telefonnummer im Telefonbuch
Offensichtlich ist eine gewisse Ordnung / Sortierung hilfreich bei der Suche!
Suchen(3) Rahmenprogramm
Zur Untersuchung der Suchalgorithmen können wir wiederum Zufallszahlen verwenden.
Der Rahmen zur Implementierung der Suchalgorithmen sieht dann wie folgt aus:
public class SearchBy<Verfahren> {
public static int search( int[] numbers, int number) { // Implementierung des Algorithmus
return index;
}
public static void main( String[] args) {
System.out.println( "SearchBy<Verfahren>\n");
int[] numbers = Generator.generateRandomNumbers( 10, 1, 100);
int number = numbers[ (int)(Math.random()*10)]; // zufällige Zahl wählen long t1 = System.currentTimeMillis();
int index = search( numbers, number); // versuchen die Zahl wieder zu finden long t2 = System.currentTimeMillis();
Generator.printNumbers( numbers);
if ( -1 != index) {
System.out.printf( "%" + (5*index+3) + "s=%d(%d)", "^^^", number, index);
}
System.out.println( "\nDone in " + (t2-t1) + "ms");
}
}
Suchen(4) Lineare Suche
Idee: Jedes Element betrachten bis das gesuchte Element gefunden ist.
Voraussetzung: Man weiss nichts über die Elemente und deren Ordnung public static int search( int[] numbers, int number) { for ( int i = 0; i<numbers.length; ++i) {
if ( numbers[i]==number) { return i;
} }
return -1;
}
Bei der linearen Suche braucht man im worst-case n, im Mittel n/2 Vergleiche. Da man nichts über die durchsuchten Daten weiß, bleibt einem auch gar nichts anders übrig!
Suchen(5) Binäre Suche
Idee: Das gesuchte Element gezielt „einkreisen“ (teilen und herrschen, vgl. Zahlenraten) Voraussetzung: Die Elemente sind in bekannter Weise geordnet.
public static int search( int[] numbers, int number) { int lo = 0, hi = numbers.length-1;
do {
int index = (hi+lo)/2;
if ( numbers[index]<number) { lo = index+1;
continue;
}
if ( numbers[index]>number) { hi = index-1;
continue;
}
return index;
} while ( lo<=hi);
return -1;
}
Man schließt also mit Hilfe der Sortierung bei jedem Schritt möglichst viele Elemente aus.
Ordnung hat also große Vorteile! Sie muss aber vorhanden sein, sonst klappt's nicht!
Referenzen
Literaturhinweise:
[1] Niklaus Wirth, Algorithmen und Datenstrukturen, Teubner Verlag
[2] Robert Sedgewick, Algorithms in <your favorite language>, Addison Wesley
Links:
[1] http://de.wikipedia.org/wiki/Sortierverfahren
Analytischer Vergleich verschiedener Verfahren mit Links zu Detailbeschreibungen [2] http://www.ldv.ei.tum.de/media/files/lehre/gi/praktikum/interaktiv/sort/sort.html
Interaktive Animation von Sortieralgorithmen
[3] http://ivs.cs.uni-magdeburg.de/~dumke/EAD/Skript10.html Pseudocode Darstellung von Such- und Sortieralgorithmen [4] http://www-igm.univ-mlv.fr/~lecroq/string/
Algorithmen zum Stringvergleich (~Suche von Teilfolgen)