software entwicklung 1 - softech.cs.uni-kl.de · entferne einen minimalen eintrag min aus der menge...
TRANSCRIPT
Software Entwicklung 1
Annette Bieniusa / Arnd Poetzsch-Heffter
AG SoftechFB Informatik
TU Kaiserslautern
Klassische Sortier- und Suchalgorithmen
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 2/ 39
Motivation
Suchen und Sortieren sind Teilprobleme, die in einer Vielzahl vonProgrammen auftreten
Beispiel: Musiksammlungen, Profile in sozialen Netzwerken, Berechnungvon Median, Erstellen von Histogrammen
Große Datenmengen erfordern effiziente Algorithmen (Laufzeit undSpeicherbedarf)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 3/ 39
Thematischer Uberblick
Binare Suche
Sortierverfahren auf Arrays (Sortieren durch Auswahl, durch Einfugen,Quicksort)
Laufzeitanalyse der Sortierverfahren
Datenstruktur Heap und Heap-Sort
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 4/ 39
Binare Suche
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 5/ 39
Motivation
Ein Ratespiel
Wie kann man mit moglichst wenigen Frageneine Zahl x ∈ N zwischen 0 und N erraten?
Erlaubte Fragen: “Ist die Zahl kleiner als ?”Antwort: “Ja / Nein”
Wie viele Fragen sind dazu notwendig?
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 6/ 39
Algorithmische Idee
Verwende ein Intervall [l , h) mit x ∈ [l , h), das in jedem Schritt halbiertwird
Es gilt: l ≤ x und x < h
Anfangsintervall ist [0,N + 1)
Rekursive Strategie:
Basisfall: Wenn h − l = 1, dann ist x = lRekursiver Schritt:
Frage, ob die Zahl kleiner ist als m = l + (h − l)/2Falls ja, suche die Zahl im Intervall [l ,m)Andernfalls, suche die Zahl im Intervall [m, h)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 7/ 39
Implementierung: Ratespielpublic class Ratespiel {
// Sucht die Zahl im Interval [lo,hi)public static int search(int lo, int hi) {
// Basisfallif ((hi-lo) == 1) {
return lo;}// Rekursiver Schrittint mid = lo + (hi -lo) / 2;StdOut.println("Ist die Zahl kleiner als " + mid + "? (j/n)");if (StdIn.readString ().equals("j")) {
return search(lo ,mid);} else {
return search(mid ,hi);}
}public static void main(String [] args) {
// Befehlzeilenparameter: Obere Grenze!int N = Integer.parseInt(args [0]);StdOut.println("Rate eine Zahl zwischen 0 und " + N-1 +"!");int x = search(0, N);StdOut.println("Die gesuchte Zahl ist " + x);
}}
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 8/ 39
Korrektheit des Verfahrens
Vereinfachende Annahme: N = 2n fur ein n ∈ N
Beobachtungen fur die Aufrufe von search(h,l):
Das Intervall enthalt immer die gesuchte Zahl.
Die Intervallgroßen, d.h. die Differenz h − l , sind Zweierpotenzen, diemit jedem rekursiven Aufruf kleiner werden, beginnend mit 2n undendend mit 20 = 1.[Beweis durch Induktion]
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 9/ 39
Laufzeitanalyse: Wie viele Fragen werden benotigt?
Nach Annahme: N = 2n fur ein n ∈ N, d.h. n = log2(N).
Sei T (N) die Anzahl der Fragen.
In jedem Rekursionsschritt wird eine Frage gestellt und das Problem aufeinem Intervall halber Große gelost:
T (N) = T (N/2) + 1
Im Basisfall ist keine Frage mehr notig: T (1) = 0
Insgesamt:
T (N) = T (2n)
= T (2n−1) + 1 = T (2n−2) + 2 = · · · = T (1) + n − 1
= n
= log2(N)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 10/ 39
Binare Suche in sortiertem Array
Idee der Binaren Suche auch beim Suchen in sortiertenDatensammlungen anwendbar, die direkten Zugriff auf einen Eintragerlauben
Alltagsbeispiele: Worterbuch, Telefonbuch
Problembeschreibung:Gegeben sei eine Folge s0, . . . , sN−1 von sortierten Datensatzen.
Jeder Datensatz sj hat einen zugehorigen Schlussel kj .Wir gehen hier davon aus, dass die Schlussel Integer sind.
Fur sortierte Datensatze gilt:
k0 ≤ k1 ≤ · · · ≤ kN−1
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 11/ 39
Beschreibung der Daten
class DataSet {
int key;
String data;
DataSet (int key , String data) {
this.key = key;
this.data = data;
}
}
Fur bessere Ubersichtlichkeit auf den Folien verzichten wir hier aufGetter/Setter-Methoden.
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 12/ 39
Implementierungpublic class BinarySearch {
// Liefert null , wenn Datensatz mit Index k nicht in f enthalten ist;// andernfalls die Referenz auf den gefundenen Datensatzpublic static DataSet search (int k, DataSet [] f) {
return search(k, f, 0, f.length);}
public static DataSet search (int k, DataSet [] f, int lo, int hi) {// Element nicht gefundenif (hi <= lo) {
return null;}int mid = lo + (hi -lo) /2;if (k < f[mid].key) {
// Suche im Intervall [lo, mid)search (k, f, lo, mid);
}if (k > f[mid].key) {
// Suche im Intervall [mid+1, hi)search (k, f, mid+1, hi);
}// Element gefundenreturn f[mid];
}}
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 13/ 39
Sortieren
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 14/ 39
Sortieren
Sortieren ist eine Standardaufgabe, die Teil vieler spezieller oder auchumfassenderer Aufgabenstellungen ist.
Untersuchungen zeigen, dass”mehr als ein Viertel der kommerziell
verbrauchten Rechenzeit auf Sortiervorgange entfallt“(Ottmann, Widmayer: Algorithmen und Datenstrukturen, Kap. 2).
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 15/ 39
Begriffsklarung: Sortierproblem
Aufgabe des Sortierproblems ist es, eine Permutation π zu finden, so dassdie Umordnung der Datensatze gemaß π folgende Reihenfolge auf denSchlusseln ergibt:
kπ(0) ≤ kπ(2) ≤ · · · ≤ kπ(N−1)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 16/ 39
Bemerkung
Offene Aspekte der Formulierung des Sortierproblem:
Was heißt, eine Folge ist”gegeben“?
Ist der Bereich der Schlussel bekannt?
Welche Operationen stehen zur Verfugung, um π zu bestimmen?
Was genau heißt”Umordnung“?
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 17/ 39
Vorgehen
Wir betrachten hier vier wichtige Sortieralgorithmen:
Sortieren durch Auswahlen
Sortieren durch Einfugen
Quicksort
Heapsort (nachste Vorlesung)
Die algorithmische Grundidee ist unabhangig vom verwendetenProgrammierparadigma.
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 18/ 39
Sortieren durch Auswahl (Selection Sort)
Algorithmische Idee:
Entferne einen minimalen Eintrag min aus der Menge der Datensatze.
Sortiere die Menge der Datensatze, aus der min entfernt wurde.
Fuge min als erstes Element vorne an die sortierte Ergebnismenge an.
Implementierung auf Arrays:
1 Finde einen Index imin des Arrays, so dass f [imin] ein minimalesElement von f [0] bis f [N − 1] enthalt
2 Vertausche f [imin] und f [0]
3 Sortiere dann den Bereich f [1] bis f [N − 1] auf gleiche Art
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 19/ 39
Implementierung: Sortieren durch Auswahl
static void selectionsort (DataSet [] f) {
for (int i = 0; i < f.length - 1; i++) {
// Finde Index fuer minimales Element
int imin = i;
for (int j = i+1; j < f.length; j++) {
if (f[j].key < f[imin].key) {
imin = j;
}
}
// Vertausche Elemente
swap(f,i,imin);
}
}
static void swap (DataSet [] f, int i, int j) {
DataSet tmp = f[i];
f[i] = f[j];
f[j] = tmp;
}
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 20/ 39
Laufzeitanalyse von Sortieren durch Auswahl
Kostenmodell
Wir betrachten die Anzahl der Schlusselvergleiche C und der Zuweisungen Mvon Datensatzen in Abhangigkeit von der Anzahl N der Datensatze. 1
→ pro innerem Schleifendurchlauf ein Schlusselvergleich
→ pro außerem Schleifendurchlauf 3 Datensatzzuweisungen
Schlusselvergleiche: C (N) =∑N−2
i=0
∑N−1j=i+1 1 ∈ O(N2)
Datensatzzuweisungen: M(N) =∑N−2
i=0 3 ∈ O(N)
1Fur eine detailliertere Analyse: Siehe Folien 16 “Algorithmenanalyse”Bieniusa/Poetzsch-Heffter Software Entwicklung 1 21/ 39
Sortieren durch EinfugenAlgorithmische Grundidee:
Sortiere zunachst eine Teilmenge (Basisfall: leere Menge).
Fuge dann die verbleibenden Elemente nacheinander in die bereitssortierte Teilmenge an der richtigen Stelle ein.
Implementierung auf Arrays:
Sortierter Teil des Arrays wachst in jedem Schritt um einen Eintrag
Einfugen durch schrittweises Verschieben (ausgehend vom großtenElement)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 22/ 39
Implementierung: Sortieren durch Einfugen
static void insertionSort(DataSet [] f) {
DataSet tmp; // einzufuegender Datensatz
for (int i = 1; i < f.length; i++) {
int j = i;
tmp = f[j];
// Finde neue Position fuer aktuellen Datensatz tmp
while( j >= 1 && f[j-1]. key > tmp.key ) {
// Verschiebe Datensaetze nach hinten
f[j] = f[j-1];
j--;
}
// Setze tmp an neue Position
f[j] = tmp;
}
}
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 23/ 39
Laufzeitabschatzung I
Gunstigster Fall
Array ist bereits aufsteigend sortiert
→ kein innerer Schleifendurchlauf
→ pro außerem Schleifendurchlauf ein Schlusselvergleich
→ pro außerem Schleifendurchlauf zwei Datensatzzuweisungen
Schlusselvergleiche: Cmin(N) = N − 1
Datensatzzuweisungen: Mmin(N) = 2(N − 1)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 24/ 39
Laufzeitabschatzung II
Ungunstigster Fall
Array ist absteigend sortiert
→ pro außerem Schleifendurchlauf i Schlusselvergleiche
→ pro außerem Schleifendurchlauf (i + 2) Datensatzzuweisungen
Schlusselvergleiche: Cmax(N) =∑N−1
i=1 i ∈ O(N2)
Datensatzzuweisungen: Mmax(N) =∑N−1
i=1 (i + 2) ∈ O(N2)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 25/ 39
Quicksort
Algorithmische Grundidee
Wahle einen beliebigen Datensatz mit Schlussel k aus (Pivotelement)
Teile die Datensatze in zwei Teilmengen:
1. Teil enthalt alle Datensatze mit Schlusseln < k
2. Teil enthalt die Datensatze mit Schlusseln ≥ k
Wende Quicksort rekursiv auf die Teillisten an
Fuge die resultierenden, nun sortierten Teillisten und das Pivotelementwieder zusammen
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 26/ 39
Beispiel: Partitionierung17 12 6 19 23 8 5 10
Wahle als Pivotelement das letzte Element (10):
17 12 6 19 23 8 5 10
Sortiere die ubrigen Elemente:
17 12 6 19 23 8 5 10
Vertausche 5 und 17:
5 12 6 19 23 8 17 10
Vertausche 8 und 12:
5 8 6 19 23 12 17 10
Keine Vertauschung mehr moglich:
5 8 6 19 23 12 17 10
Tausche nun das Pivotelement zwischen die beiden Partitionen:
5 8 6 10 23 12 17 19
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 27/ 39
Quicksort im Detail I
Implementierung auf Arrays
Bearbeite rekursiv Teilbereiche des Arrays von Index ug bis Index og
Realisiere das Teilen der Datensatzmenge durch Vertauschen
→ Indexzahler left, right laufen von links bzw. rechts und suchenEintrage, die vertauscht werden konnen
→ Fur die zu tauschenden Eintrage gilt:f[left].key ≥ pivot.key und f[right].key < pivot.key
→ In jedem Schritt gilt:
Fur alle i in [ug, left-1] : f[i].key < pivot.key
Fur alle i in [right+1, og] : pivot.key ≤ f[i].key
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 28/ 39
Quicksort im Detail II
Solange left < right:
Vertausche f[left] und f[right], falls f[left].key ≥ pivot.key undf[right].key < pivot.key, inkrementiere left und dekrementiereright, und fahre dann mit der Suche nach vertauschbarenElementpaaren fort.
ug og
↗ ↖left right
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 29/ 39
Quicksort im Detail III
Umsortierung des (Teil-)Arrays ist abgeschlossen, sobald left > right
ug og
↗↖left right
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 30/ 39
Implementierung: Quicksort Istatic void sortieren (DataSet [] f) {
quicksort(f, 0, f.length - 1);
}
static void quicksort (DataSet [] f, int ug, int og) {
if (ug < og) {
int ixsplit = partition(f, ug, og);
/* Es gilt:
ug ≤ ixsplit ≤ og
&& f[ixsplit ].key == pivotKey
&& ( ∀ i: ug ≤ i < ixsplit
⇒ f[i].key ≤ pivotKey )
&& ( ∀ i: ixsplit < i ≤ og
⇒ f[i].key ≥ pivotKey ) */
quicksort( f, ug, ixsplit -1 );
quicksort( f, ixsplit+1, og );
}
}
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 31/ 39
Implementierung: Quicksort II
static int partition (DataSet [] f, int ug , int og){
int left = ug;
int right = og - 1;
int pivot = f[og].key;
while (true) {
while (f[left].key < pivot) { left ++; }
while (left <= right && f[right].key >= pivot){ right --; }
if( left > right ) {
break;
} else {
swap(f, left , right);
left ++; right --;
}
}
swap(f, left , og); // Pivotelement kommt zwischen
return left; // die ermittelten Bereiche
}
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 32/ 39
Laufzeitabschatzung von Quicksort I
Bei der Partitionierung wird wahrend des Scans jedes Arrayelementeinmal mit dem Pivot verglichen (und ggf. vertauscht).
Anzahl der rekursiven Aufrufe und die Struktur des Aufrufbaums hangtvon den Pivotelementen ab
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 33/ 39
Laufzeitabschatzung von Quicksort II
Ungunstigster Fall:Das Pivotelement ist immer das kleinste bzw. großte Element des aktuell zupartitionierenden Teilarrays.
Beim Partitionieren ist dann jeweils eines der Teilarrays leer. Daher hat derAufrufbaum der rekursiven Aufrufe die Tiefe N, also gilt fur dieSchlusselvergleiche:
Cmax(N) ∈ O(N2)
Im ungunstigsten Fall mussen ausserdem alle Elemente bei derPartitionierung vertauscht werden:
Mmax(N) ∈ O(N2)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 34/ 39
Laufzeitabschatzung von Quicksort III
Gunstigster FallDas Pivotlement ist der Median des aktuell zu partitionierenden Teilarrays.
Beim der Partitionierung entstehen jeweils zwei etwa gleich große Teilarrays.Dann hat der Aufrufbaum die Tiefe logN, also gilt:
Cmin(N) ∈ O(N logN)
Im gunstigsten Fall muss ausserdem jedesmal das Pivotelement getauschtwerden, sonst aber keine Elemente:
Mmin(N) ∈ O(N)
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 35/ 39
Bemerkungen
In der Praxis einer der schnellsten und wichtigsten Sortieralgorithmen!
Genauere Analysen zeigen, dass Quicksort im Mittel O(N logN)Vergleiche und Tauschoperationen benotigt.
Der ungunstigste Fall ist sehr unwahrscheinlich.
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 36/ 39
Optimierungen
Die vorgestellte Quicksort-Fassung arbeitet schlecht auf schon sortiertenArrays.
→ Shuffle des Arrays, um Vorsortierung aufzuheben→ Randomisierte Auswahl des Pivot-Elements
(Beispiel: Median von 3 Elementen)
Reduzierung der rekursiven Aufrufe durch Verwendung von InsertionSort,wenn Teil-Arrays nur noch wenige Elemente enthalten
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 37/ 39
Divide-and-Conquer Strategie
Quicksort ist ein typischer Algorithmus, der eineDivide-and-Conquer-Strategie verfolgt:
Zerlege das Problem in Teilprobleme.
Wende den Algorithmus rekursiv auf die Teilprobleme an.
Fuge die Teilergebnisse wieder zusammen.
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 38/ 39
Zusammenfassung
Binare Suche (⇒ O(logN) )
Sortierverfahren auf Arrays
Selection Sort (⇒ O(N2))Insertion Sort (⇒ O(N2))Quicksort (⇒ O(N2))
Ausblick:
Heap Sort (⇒ O(N logN))
Abstrakte Datentypen: Stack und Queue
Parametrisierte Datentypen in Java: Generics
Bieniusa/Poetzsch-Heffter Software Entwicklung 1 39/ 39