04.10.2013 Aufrufe

Überblick 5.1 Sequentielles Suchen

Überblick 5.1 Sequentielles Suchen

Überblick 5.1 Sequentielles Suchen

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

<strong>Überblick</strong><br />

5 <strong>Suchen</strong> und Sortieren<br />

<strong>5.1</strong> <strong>Sequentielles</strong> <strong>Suchen</strong><br />

5.2 Binäres <strong>Suchen</strong><br />

5.3 Was ist Sortieren? Wie schwer ist es?<br />

5.4 Einige Typen von Sortieralgorithmen<br />

5.5 Einfache Sortieralgorithmen mit Zeitaufwand !(n 2 )<br />

5.6 Eine untere Schranke "(n · log n)<br />

5.7 Quicksort<br />

5.8 Merge Sort<br />

5.9 Ist es möglich, in linearer Zeit zu sortieren?<br />

<strong>5.1</strong>0 Sortiernetzwerke<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

<strong>5.1</strong> <strong>Sequentielles</strong> <strong>Suchen</strong><br />

• Gegeben: Menge von n Elementen, z.B. ganze Zahlen,<br />

abgespeichert in einem Array<br />

int[] a; // a.length = n<br />

• Problem: Ist ein vorgegebenes Element x im Array a enthalten?<br />

• Einfachste Lösung:<br />

<strong>Sequentielles</strong> (oder lineares) Durchsuchen des Arrays<br />

• Triviales Beispiel für einen inkrementellen Algorithmus, der jeweils<br />

ein Datenobjekt nach dem anderen bearbeitet.<br />

• Bei erfolgreicher Suche wird der Index i, 0 ! i < a.length,<br />

zurückgegeben, an dessen Position x steht.<br />

• i = –1 signalisiert erfolglose Suche.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-1<br />

5-2


Algorithmus für sequentielles <strong>Suchen</strong><br />

• int find(int[] a, int x) {<br />

int i = a.length - 1;<br />

while ((i >= 0) && (x != a[i]))<br />

// Position (1)<br />

i--;<br />

// Position (2)<br />

return i;<br />

}<br />

• && (and mit SCE) wertet den booleschen Ausdruck von links nach<br />

rechts aus und bricht die Auswertung ab, sobald der Wert des<br />

booleschen Ausdrucks feststeht:<br />

Auswertung von (i >= 0) zu false verhindert, daß auf ein nicht<br />

existierendes Arrayelement (z.B. a[–1]) zugegriffen wird.<br />

• Invarianten bringen das für den Nachweis der formalen Korrektheit des<br />

Algorithmus Wesentliche zum Ausdruck, nämlich:<br />

In jeder Iteration der while-Schleife wird das Teilarray, von dem<br />

bekannt ist, daß es x nicht enthält, um ein Element vergrößert.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Invarianten und formale Korrektheit<br />

• Folgende Invariante gilt an Position (1):<br />

(I) (0 ! i < a.length) ! (" k, i ! k: a[k] " x)<br />

Invariante (I) gilt trivialerweise beim ersten Durchlauf der while-<br />

Schleife und bleibt wahr in jedem nachfolgenden Durchlauf.<br />

• Folgende Bedingung gilt an Position (2):<br />

(II) (" k, i < k: a[k] " x) !<br />

((i = –1) # ((0 ! i < a.length) ! (a[i] = x)))<br />

Bedingung (II) besagt, daß der Algorithmus auf zwei Arten terminiert:<br />

– i = –1 signalisiert, daß das ganze Array erfolglos durchsucht wurde.<br />

– x wurde an der Arrayposition i gefunden.<br />

• Formaler Korrektheitsbeweis muß zeigen, daß die Schleife wirklich<br />

terminiert:<br />

– i wird mit einem ganzzahligen Wert " 0 initialisiert.<br />

– i wird in jedem Schleifendurchlauf um 1 erniedrigt, muß also nach<br />

einer endlichen Anzahl von Durchläufen negativ werden.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-3<br />

5-4


Einsatz eines Platzhalters<br />

• Boolescher Ausdruck zur Kontrolle der while-Schleife enthält zwei<br />

Bedingungen: (i >= 0) und (x != a[i])<br />

• Erster Term ist verzichtbar, falls sichergestellt ist, daß x immer gefunden<br />

wird, bevor der Suchindex i das Array a verläßt, d.h. i < 0 wird.<br />

• Idee: Erweitere das Array um eine Zelle und speichere die eigentlichen<br />

n Elemente an den Positionen 1, 2, …, n. Benutze a[0] beim Suchprozess,<br />

indem das zu suchende Element x als Platzhalter (sentinel)<br />

zu Beginn des Suchprozesses in a[0] gespeichert wird $<br />

Suchprozess stoppt spätestens bei a[0] $<br />

i > 0 % erfolgreiche Suche i = 0 % erfolglose Suche<br />

• int find(int[] a, int x) {<br />

int i = a.length - 1;<br />

a[0] = x;<br />

while (x != a[i])<br />

i--;<br />

return i;<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Aufwand für sequentielles <strong>Suchen</strong><br />

• Annahme: n Elemente im Array gespeichert<br />

• Schlimmster Fall tritt ein bei erfolgloser Suche:<br />

Gesamtes Array wird durchlaufen $ Zeitaufwand T(n) # !(n)<br />

• Erfolgreiche Suche:<br />

Annahme: Alle im Array enthaltenen Elemente besitzen gleiche<br />

Wahrscheinlichkeit, daß nach ihnen gesucht wird $<br />

durchschnittliche Anzahl Iterationen der while-Schleife bei<br />

erfolgreicher Suche =<br />

1 !(1 + 2 +…+ n) = n +1<br />

n 2<br />

• <strong>Sequentielles</strong> <strong>Suchen</strong> benötigt !(n) Zeit sowohl im schlimmsten als<br />

auch im durchschnittlichen Fall.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-5<br />

5-6


<strong>Suchen</strong> für beliebige Objekttypen<br />

• Falls Datenobjekte vom Typ Element sind, so sollte die Klasse<br />

Element die Methode public boolean equals(Object obj)<br />

bereitstellen, die true zurückgibt, falls das aufrufende Objekt und<br />

das übergebene Objekt gleich sind im Sinne der Anwendung:<br />

class Element {<br />

...<br />

public boolean equals(Object obj) { ... }<br />

...<br />

}<br />

• Stellt die Klasse Element die Methode equals nicht bereit, so wird<br />

die entsprechende Methode der Klasse Object verwendet, die<br />

true zurückgibt, falls beide Objektreferenzen gleich sind!<br />

• Sequentieller Suchalgorithmus:<br />

int find(Object[] a, Object x) {<br />

int i = a.length - 1;<br />

while ((i >= 0) && (! x.equals(a[i])))<br />

i--;<br />

return i;<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5.2 Binäres <strong>Suchen</strong><br />

• Im Array a abgespeicherte Datenelemente seien geordnet bezüglich<br />

einer dem Wertebereich der Datenelemente zugrunde liegenden<br />

Ordnungsrelation !:<br />

" k, 0 ! k < n–1: a[k] ! a[k + 1]<br />

• Suche kann beschleunigt werden, da ein Vergleich des Suchelementes<br />

x mit einem Arrayelement mehr Information gibt als im<br />

ungeordneten Fall:<br />

x " a[m] schließt nicht nur a[m], sondern auch alle Elemente zur<br />

einen oder anderen Seite von m aus:<br />

– x < a[m] $ " i # m: a[i] " x<br />

– x > a[m] $ " i ! m: a[i] " x<br />

a[m] < x<br />

m<br />

Falls<br />

kann x nicht enthalten sein in<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

a[m] > x<br />

m<br />

5-7<br />

5-8


Algorithmus für binäres <strong>Suchen</strong><br />

int find(int[] a, int x) {<br />

int i = 0;<br />

int j = a.length - 1;<br />

while (i a[m])<br />

i = m + 1<br />

else // x = a[m]<br />

// Position (2)<br />

return m;<br />

}<br />

// Position (3)<br />

return -1;<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Invarianten, formale Korrektheit<br />

• Folgende Invariante gilt an Position (1):<br />

(I) (i ! j) ! (" k, 0 ! k < i: a[k] < x)<br />

! (" k, j < k < a.length: a[k] > x)<br />

• Invariante (I) gilt trivialerweise beim ersten Durchlauf der while-Schleife<br />

und bleibt wahr in jedem nachfolgenden Durchlauf.<br />

• Folgende Bedingung gilt an Position (2):<br />

(II) a[m] = x<br />

• Folgende Bedingung gilt an Position (3):<br />

(III) (i = j + 1) ! (" k, 0 ! k < i: a[k] < x)<br />

! (" k, j < k < a.length: a[k] > x)<br />

• i = j + 1 $ Unsicherheitsintervall ist leer!<br />

• In jedem Schleifendurchlauf wird i erhöht oder j erniedrigt $ nach<br />

endlich vielen Schleifendurchläufen wird i > j $ Algorithmus terminiert.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-9<br />

5-10


Zeitaufwand<br />

• Algorithmus funktioniert für jede Wahl von m mit i ! m ! j, ist aber am<br />

effizientesten für m = (i + j) / 2.<br />

• Zeitaufwand im schlimmsten Fall:<br />

&log 2 n' Schleifendurchläufe, d.h. T(n) # !(log n).<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Beispiel Interface für Comparable<br />

Binäres <strong>Suchen</strong><br />

• Falls Datenobjekte vom Typ Element int sind, find(int[] so sollte die a, Klasse int x) {<br />

int i = 0;<br />

Element das<br />

int j = a.length - 1;<br />

interface Comparable while (i a[m])<br />

x.compareTo(o)<br />

i = m + 1<br />

3 gibt 4 für 6x < 7o eine 16 negative 23 25 int, 29 für x = o den Wert else 0 und // x = a[m]<br />

für x > o eine positive int zurück.<br />

return m;<br />

}<br />

class Element implements Comparable{<br />

return -1;<br />

i ... m i m ixi iim m j<br />

j }<br />

public int compareTo(Element o) { ... }<br />

int[] a = {3,4,6,7,16,23,25,29};<br />

...<br />

int xi = find(a, 25);<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-11<br />

5-12


Binäres <strong>Suchen</strong> für beliebige Objekttypen<br />

int find(Comparable[] a, Comparable x) {<br />

int i = 0;<br />

int j = a.length – 1;<br />

while (i 0)<br />

i = m + 1<br />

else // comp = 0<br />

return m;<br />

}<br />

return –1;<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5.3 Was ist Sortieren? Wie schwer ist es?<br />

• Motivation: Binäres <strong>Suchen</strong> in geordneten Mengen ist sehr viel schneller<br />

als sequentielles <strong>Suchen</strong> in ungeordneten Mengen. Sortieren ist neben<br />

<strong>Suchen</strong> eine der am häufigsten ausgeführten Operationen.<br />

• Gegeben sei eine Folge S von n Elementen x 1 , x 2 , … , x n , die einem<br />

Wertebereich X entstammen, auf dem eine totale Ordnung ! definiert ist,<br />

d.h. eine Relation, die die folgenden Axiome erfüllt:<br />

! ist reflexiv: " x ( X: x ! x<br />

! ist antisymmetrisch: " x, y ( X: x ! y ! y ! x $ x = y<br />

! ist transitiv: " x, y, z ( X: x ! y ! y ! z $ x ! z<br />

! ist total: " x, y ( X $ x ! y # y ! x<br />

• Sortieren ist ein Prozess, durch den eine Folge x i1 , x i2 , … , x in erzeugt<br />

wird, so daß (i 1 , i 2 , … , i n ) eine Permutation der ganzen Zahlen von 1 bis<br />

n ist und folgende Eigenschaft gilt:<br />

" k, 1 ! k ! n–1: x ik ! x ik+1<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-13<br />

5-14


Sortieren = Finden einer Permutation<br />

• Beispiel:<br />

Sortierung der Folge<br />

x 1 = 9, x 2 = 4, x 3 = 13, x 4 = 2, x 5 = 21, x 6 = 15, x 7 = 2, x 8 = 5, x 9 = 11<br />

erzeugt die Folge<br />

x 4 = 2, x 7 = 2, x 2 = 4, x 8 = 5, x 1 = 9, x 9 = 11, x 3 = 13, x 6 = 15, x 5 = 21<br />

die durch die Permutation<br />

(4, 7, 2, 8, 1, 9, 3, 6, 5)<br />

der Ausgangsfolge festgelegt wird.<br />

• Abstrakte Formulierung:<br />

Sortieren besteht im Auffinden einer spezifischen Permutation (oder<br />

einer unter einigen wenigen Permutationen, wenn Elemente gleiche<br />

Werte haben dürfen) aus n! möglichen Permutationen der n gegebenen<br />

Elemente.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Randbedingungen<br />

• Menge S der zu sortierenden Elemente gegeben in einer Datenstruktur,<br />

die S implizit ordnet, aber nicht notwendigerweise bzgl. der gewünschten<br />

Ordnung !.<br />

• Typische Sortierprobleme:<br />

S gegeben in<br />

– Array mit direktem Zugriff auf jedes Element<br />

(z.B. a[i] im Array a) oder<br />

– sequentieller Datei (z.B. Magnetband) mit sequentiellem Zugriff (z.B.<br />

durch Zeiger auf gegenwärtige Position).<br />

Generierung des Resultats in der gleichen Datenstruktur.<br />

Zugriffsoperationen der zugrunde liegenden Datenstruktur bestimmen,<br />

welche Sortieralgorithmen möglich sind.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-15<br />

5-16


Eigenschaften von Sortieralgorithmen<br />

• Ein Sortieralgorithmus sortiert in place, falls während seines Ablaufs<br />

zu jedem Zeitpunkt höchstens eine konstante Anzahl zu sortierender<br />

Elemente außerhalb der Datenstruktur gespeichert wird.<br />

• Ein Sortieralgorithmus heißt stabil, falls nach der Sortierung die<br />

ursprüngliche Anordnung gleicher Elemente erhalten bleibt:<br />

8, 6', 2', 1, 6", 2", 9 % 1, 2', 2", 6', 6", 8, 9 ist stabil<br />

8, 6', 2', 1, 6", 2", 9 % 1, 2", 2', 6', 6", 8, 9 ist nicht stabil<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Sortieralgorithmen<br />

• Die meisten Sortieralgorithmen sind Verfeinerungen der folgenden Idee:<br />

) while (* (i, j): i < j ! a[i] > a[j]) a[i] :=: a[j];<br />

:=: bezeichnet den Austauschoperator!<br />

• Zwei Typen von Operationen sind zum Sortieren notwendig:<br />

– Sammeln von Information über die Ordnung der gegebenen Elemente<br />

– Ordnen der Elemente, z.B. durch Vertauschen zweier Elemente<br />

• Ziel bei der Entwicklung eines effizienten Sortieralgorithmus:<br />

Minimierung der Anzahl Operationen beider Typen $<br />

– Vermeide, redundante Informationen zu sammeln!<br />

– Versuche, Elemente so selten wie möglich zu bewegen!<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-17<br />

5-18


Nicht-deterministische Algorithmen<br />

• Beispiel:<br />

Nicht-deterministischer Algorithmus ) erlaubt beim Sortieren von<br />

x 1 = 5, x 2 = 2, x 3 = 3, x 4 = 4, x 5 = 1 (+)<br />

eine der folgenden 7 Austauschoperationen:<br />

x 1 :=: x i für 2 ! i ! 5 und x j :=: x 5 für 2 ! j ! 4<br />

Zustand (+) könnte entstanden sein durch exotische Strategie, die von<br />

der Mitte zu den beiden Enden sortiert, und es könnte bekannt sein,<br />

daß eine einzige Austauschoperation x 1 :=: x 5 den Sortierprozeß<br />

abschließen würde. Der nicht-deterministische Algorithmus ) bietet<br />

jedoch keine Möglichkeit, dieses Wissen auszudrücken und zu nutzen.<br />

$ Kein Nicht-Determinismus!<br />

Zu entwickelnde Kontrollstruktur muß sorgfältig die Reihenfolge der<br />

auszuführenden Operationen festlegen, um die bereits gesammelten<br />

Informationen möglichst optimal zu nutzen!<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Intrinsische Komplexität<br />

• Grenzen der Optimierung:<br />

Ohne irgendwelche bereits vorhandene Informationen muß jedes Element<br />

mindestens einmal inspiziert und i. a. mindestens einmal bewegt<br />

werden $ mindestens "(n) primitive Operationen sind notwendig.<br />

• Sammeln von Informationen nur durch Beantworten binärer Fragen<br />

(z.B. ja/nein Fragen, Vergleich zweier Elemente mit Antwort ! oder >) $<br />

mindestens n · log 2 n Fragen sind notwendig (siehe Abschnitt 5.6) $<br />

"(n · log n) ist untere Schranke in diesem Berechenbarkeitsmodell.<br />

• Umordnen von Elementen: durchschnittlicher Abstand eines Elementes<br />

von seiner korrekten Position $ n / 3 (siehe Abschnitt 4.7).<br />

Transfer eines Elementes kann z.B. durch 1 Schritt der Länge n / 3<br />

oder n / 3 Schritte der Länge 1 erfolgen.<br />

Umordnung nur durch Austausch benachbarter Elemente $<br />

durchschnittlich !(n 2 ) Austauschoperationen sind nötig $<br />

Kurze Schritte nicht ausreichend für effizienten O(n · log n) Algorithmus!<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-19<br />

5-20


Praktische Aspekte<br />

• Objekte statt Elemente<br />

Unsere Annahme: Zu sortierende Elemente entstammen einem total<br />

geordneten Wertebereich.<br />

In der Praxis wird die Ordnung der Elemente häufig<br />

– direkt durch Schlüssel bestimmt, die Bestandteil der zu sortierenden<br />

komplexen Datenobjekte sind, die neben dem Schlüssel noch<br />

weitere Daten umfassen, z.B. Konten mit Schlüssel Kontonummer.<br />

– indirekt aus den Eigenschaften der Objekte abgeleitet, z.B.<br />

Rechtecke, die durch ihre Seitenlängen gegeben sind und nach<br />

ihrem Flächeninhalt geordnet werden.<br />

• Vergleichsoperatoren =,


Insertion Sort und Selection Sort<br />

• Hauptunterschied zwischen Insertion Sort und Selection Sort: In<br />

welcher der beiden Mengen wird die meiste Arbeit während des<br />

Sortierens verrichtet?<br />

• Insertion Sort:<br />

Entferne das erste (oder am leichtesten zugreifbare) Element aus<br />

UNSORTED und durchsuche SORTED, um seine korrekte Position zu<br />

finden.<br />

• Selection Sort:<br />

Durchsuche UNSORTED, um das nächste an SORTED anzuhängende<br />

Element zu finden.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Beispiel für Insertion Sort<br />

Initialisierung: SORTED = ();<br />

UNSORTED = (25, 23, 3, 16, 4, 7, 29, 6);<br />

i<br />

1<br />

2<br />

i = 12<br />

34<br />

56<br />

78<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

SORTED<br />

(25)<br />

(23, 25)<br />

(3, 16, 4, 7, 29, 6)<br />

23 25 3 23 25 16 23 4 23 25 16 37 6 25 16 23 16 7 23 16 25 4 25 23 7 29 25 29 6<br />

(3, 23, 25)<br />

(16, 4, 7, 29, 6)<br />

(3, 16, 23, 25)<br />

sortiert sortiert sortiert sortiert sortiert sortiert sortiert unsortiert unsortiert<br />

(3, 4, 16, 23, 25)<br />

(3, 4, 7, 16, 23, 25)<br />

(3, 4, 7, 16, 23, 25, 29)<br />

(3, 4, 6, 7, 16, 23, 25, 29)<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

UNSORTED<br />

(23, 3, 16, 4, 7, 29, 6)<br />

(4, 7, 29, 6)<br />

(7, 29, 6)<br />

(29, 6)<br />

(6)<br />

()<br />

5-23<br />

5-24


Beispiel für Selection Sort<br />

Initialisierung: SORTED = ();<br />

UNSORTED = (25, 23, 3, 16, 4, 7, 29, 6);<br />

i<br />

1<br />

2<br />

(3, 4)<br />

(25, 23, 16, 7, 29, 6)<br />

i i i = = = 23<br />

74 51 6<br />

3<br />

25 3 23 4 25 36 (3, 4, 6)<br />

16 7 23 16 4 16 23 7 29 25 29 25 6<br />

(25, 23, 16, 7, 29)<br />

4<br />

5<br />

6<br />

7<br />

8<br />

SORTED<br />

(3)<br />

(3, 4, 6, 7)<br />

sortiert sortiert sortiert sortiert sortiert sortiert sortiert unsortiert<br />

(3, 4, 6, 7, 16)<br />

(3, 4, 6, 7, 16, 23)<br />

(3, 4, 6, 7, 16, 23, 25)<br />

(3, 4, 6, 7, 16, 23, 25, 29)<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Insertion Sort und Selection Sort<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

UNSORTED<br />

(25, 23, 16, 4, 7, 29, 6)<br />

(25, 23, 16, 29)<br />

(25, 23, 29)<br />

(25, 29)<br />

(29)<br />

• SORTED und UNSORTED teilen sich ein Array, die Grenze zwischen<br />

beiden wird durch einen Index i festgelegt, der mit steigender Anzahl<br />

sortierter Elemente zunimmt.<br />

• Insertion Sort: Füge im i-ten Schritt das i-te Element in die Folge der<br />

ersten (i–1) Elemente ein.<br />

1 2 ? i–1 i n<br />

… ! x ! … x …<br />

sortiert unsortiert<br />

• Selection Sort: Selektiere im i-ten Schritt das kleinste unter den n – i + 1<br />

noch nicht sortierten Elementen und bewege es an die i-te Position.<br />

1 2 i n<br />

… ! x<br />

x = minimum(a[i], …, a[n])<br />

sortiert unsortiert<br />

• Insertion Sort und Selection Sort erfordern wahlfreien Zugriff auf die<br />

Elemente, daher werden beide Verfahren für das interne Sortieren<br />

im Hauptspeicher verwendet.<br />

()<br />

5-25<br />

5-26


Merge Sort<br />

• Merge Sort (Sortieren durch Mischen) verarbeitet (Teil-) Folgen von<br />

Elementen sequentiell $<br />

Merge Sort ist sehr gut geeignet für externes Sortieren auf<br />

Sekundärspeichermedien, die nur sequentiellen Zugriff effizient<br />

unterstützen (z.B. Festplatten) oder erlauben (z.B. Magnetbänder).<br />

• Merge Sort ist auch gut geeignet für internes Sortieren.<br />

• Idee: Verschmelze zwei sortierte Folgen von Elementen, genannt<br />

Läufe (runs), in eine längere sortierte Folge: Lese von jedem der<br />

beiden Läufe das jeweils nächste Element, vergleiche die beiden<br />

Elemente miteinander und hänge das kleinere der beiden Elemente<br />

an die Ausgabefolge.<br />

…<br />

…<br />

9 7 4<br />

6 3 2<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Externer Merge Sort<br />

A 2 1<br />

B 4 3 Prozessor<br />

8<br />

7<br />

1&3<br />

2&4 Prozessor<br />

6 4<br />

• Prozessor liest zwei Magnetbänder (oder sequentielle Dateien) A und B.<br />

– A enthält Läufe 1 und 2, B Läufe 3 und 4.<br />

– Prozessor verschmilzt Läufe 1 und 3 in einen einzigen Lauf 1&3 auf<br />

Band C und Läufe 2 und 4 in einen einzigen Lauf 2&4 auf Band D.<br />

– In einem zweiten Merge-Schritt werden Läufe 1&3 und 2&4 in einen<br />

einzigen Lauf 1&3&2&4, d.h. in eine sortierte Folge, verschmolzen.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

3<br />

2<br />

1&3&2&4<br />

5-27<br />

5-28


Radix Sort …<br />

• Radix Sort<br />

(Sortieren durch Verteilen, Distribution Sort)<br />

– beruht nicht auf Vergleich und Vertauschung von Elementen,<br />

sondern auf deren Darstellung in einem Stellenwertsystem<br />

(z.B. Dezimalsystem).<br />

– benutzt primitive arithmetische Operationen wie<br />

"extrahiere die k-te Ziffer".<br />

• Beispiel:<br />

3-ziffrige Zahlen im Zahlensystem zur Basis 4<br />

301 023 230 322 100 321 213<br />

– Verteile zu sortierende Zahlen auf 4 Folgen gemäß ihrer rechten<br />

Ziffer und hänge die so entstehenden Folgen aneinander.<br />

– Wiederhole diesen Prozess für die mittlere und dann für die linke<br />

Ziffer.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Radix Sort<br />

zu sortierende Folge: 301 023 230 322 100 321 213<br />

301 023 230 322 100 321 213<br />

verteile<br />

verteile bezüglich<br />

bezüglich der rechten 100 321<br />

213<br />

der rechten Ziffer 100 230 321 301 322 213 023<br />

Ziffer 0<br />

230<br />

301 1 2 3<br />

322 023<br />

0 1 2 3<br />

zu sortierende Folge: 230 100 301 321 322 023 213<br />

230 100 301 321 322 023 213<br />

verteile<br />

bezüglich<br />

verteile der mittleren<br />

bezüglich Ziffer<br />

301<br />

100 213 023<br />

023<br />

322<br />

321 230<br />

der<br />

0 1 2<br />

301<br />

322<br />

mittleren<br />

100<br />

213<br />

321 230<br />

Ziffer<br />

zu sortierende Folge: 100 301 213 321 322 023 230<br />

0 100 301 1 213 321 322 2 023 230 3<br />

3<br />

verteile verteile<br />

bezüglich<br />

bezüglich<br />

der linken<br />

der linkenZiffer<br />

Ziffer 023<br />

023<br />

0 100<br />

100<br />

1<br />

230<br />

213<br />

230<br />

213<br />

2<br />

322<br />

321<br />

301<br />

322<br />

321<br />

301<br />

3<br />

sortierte Folge:<br />

023 100 213 230 301 321 322<br />

0 1 2 3<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-29<br />

5-30


5.5 Einfache Sortieralgorithmen mit Zeitaufwand !(n 2 )<br />

• Array von n Elementen, z.B. ganze Zahlen:<br />

int[] a; // a.length = n<br />

Sortiere die Elemente von a in aufsteigender Reihenfolge.<br />

In place Algorithmen benutzen bei der Sortierung neben dem Array a<br />

höchstens O(1) weiteren Speicherplatz.<br />

• Insertion Sort:<br />

void insertionSort(int[] a) {<br />

for (int i = 1; i < a.length; i++) {<br />

int tmp = a[i];<br />

int j = i - 1;<br />

while ((j >= 0) && (tmp < a[j])) {<br />

}<br />

}<br />

a[j + 1] = a[j];<br />

j = j - 1;<br />

}<br />

a[j + 1] = tmp;<br />

• Insertion Sort ist stabil!<br />

Austausch<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Insertion Sort<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

0 1 j i n–1<br />

… …<br />

sortiert unsortiert<br />

• v best , v average , v worst bezeichne die Anzahl Vergleichsoperationen in der<br />

Bedingung der while-Schleife, a best , a average , a worst die Anzahl Durchläufe<br />

des Rumpfes der while-Schleife, d.h. die Anzahl Austauschoperationen,<br />

im besten, durchschnittlichen und schlimmsten Fall.<br />

v best = n !1<br />

v = inv + (n !1) = average average n2 + 3 "n ! 4<br />

4<br />

v worst = i<br />

n!1<br />

# =<br />

i=1<br />

n2 ! n<br />

2<br />

a best = 0<br />

5-31<br />

a = inv = average average n2 ! n<br />

4<br />

a worst = i<br />

n!1<br />

" =<br />

i=1<br />

n2 ! n<br />

2<br />

• inv average bezeichnet die durchschnittliche Anzahl Inversionen in einer<br />

Permutation (siehe Abschnitt 4.7)<br />

• Insertion Sort ist im besten Fall ein !(n) Algorithmus, im<br />

durchschnittlichen und schlimmsten Fall ein !(n 2 ) Algorithmus.<br />

• Binäres <strong>Suchen</strong> der korrekten Einfügeposition von a[i] im bereits<br />

sortierten Teil würde die Zeitkomplexität nicht verbessern!<br />

5-32


Interface Comparable<br />

• Falls Datenobjekte vom Typ Element sind, so sollte die Klasse<br />

Element das<br />

interface Comparable<br />

aus java.lang implementieren, das die Methode<br />

public int compareTo(T o);<br />

enthält. Der Aufruf<br />

x.compareTo(o)<br />

gibt für x < o eine negative int, x = o den Wert 0 und für x > o eine<br />

positive int zurück.<br />

class Element implements Comparable{<br />

...<br />

public int compareTo(Element o) {<br />

...<br />

}<br />

...<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Insertion Sort für beliebige Objekttypen<br />

public static <br />

void insertionSort(T[] a) {<br />

for (int i = 1; i < a.length; i++) {<br />

T tmp = a[i];<br />

int j = i – 1;<br />

while ((j >= 0) &&<br />

(tmp.compareTo(a[j]) < 0)) {<br />

a[j + 1] = a[j];<br />

j = j – 1;<br />

}<br />

a[j + 1] = tmp;<br />

}<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-33<br />

5-34


Selection Sort …<br />

void selectionSort(int[] a) {<br />

for (int i = 0; i < a.length - 1; i++) {<br />

int minindex = i;<br />

for (int j = i+1; j < a.length; j++)<br />

if (a[j] < a[minindex])<br />

minindex = j;<br />

swap(a, i, minindex);<br />

}<br />

}<br />

void swap(int[] a, int i, int j) {<br />

int tmp = a[i];<br />

minindex<br />

a[i] = a[j];<br />

0 i j<br />

n–1<br />

a[j] = tmp;<br />

}<br />

…<br />

sortiert<br />

… …<br />

unsortiert<br />

• Obige in place Implementierung von Selection Sort ist nicht stabil!<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Selection Sort<br />

• Die von Selection Sort zu verrichtende Arbeit ist immer gleich, es gibt<br />

keinen besten, durchschnittlichen oder schlimmsten Fall.<br />

Sei v die Anzahl Vergleiche von Elementen, a die Anzahl<br />

Austauschoperationen.<br />

n!2<br />

"<br />

v = (n !1<br />

i=0<br />

a = n !1<br />

! i) = n2 ! n<br />

2<br />

• Selection Sort benötigt immer einen Zeitaufwand von !(n 2 ).<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-35<br />

5-36


5.6 Eine untere Schranke "(n · log n)<br />

• Zweiwertige binäre Fragen: Antwort ja / nein, true / false<br />

• Häufige Art einer zweiwertigen Frage: x ! y ?<br />

• Satz:<br />

Jeder Sortieralgorithmus, der die Information über die Anordnung der<br />

Elemente nur über zweiwertige Fragen und nichts anderem bezieht,<br />

benötigt im Durchschnitt (d.h. im Mittel über alle n! Permutationen) und<br />

somit auch im schlimmsten Fall mindestens n · log 2 (n + 1) – n / ln 2<br />

Vergleiche. Somit ist die Zeitkomplexität eines solchen Algorithmus im<br />

durchschnittlichen und im schlimmsten Fall "(n · log n).<br />

Beweis:<br />

Darstellung eines solchen Sortieralgorithmus durch einen binären<br />

Entscheidungsbaum:<br />

– Interner Knoten repräsentiert binäre Frage.<br />

– Blatt entspricht einem möglichen Resultat des<br />

Entscheidungsprozesses.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Eine untere Schranke "(n · log n) …<br />

• Fortsetzung Beweis:<br />

Entscheidungsbaum muß jede der möglichen n! Permutationen der<br />

Eingabedaten von jeder anderen unterscheiden $<br />

Entscheidungsbaum hat mindestens n! Blätter, mindestens eines für<br />

jede Permutation!<br />

Beispiel:<br />

Sortierung von drei Elementen x, y und z durch Vergleiche zwischen<br />

jeweils zwei Elementen<br />

x ! y ?<br />

yes no<br />

y ! z ? y ! z ?<br />

yes no<br />

yes no<br />

x ! y ! z<br />

x ! z ? x ! z ?<br />

yes no yes no<br />

x ! z < y z < x ! y y < x ! z y ! z < x<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

z < y < x<br />

5-37<br />

5-38


… Eine untere Schranke "(n · log n) …<br />

• Fortsetzung Beweis:<br />

Durchschnittliche Anzahl binärer Fragen,<br />

die ein gegebener Sortieralgorithmus benötigt<br />

= durchschnittliche Tiefe der Blätter des Entscheidungsbaums<br />

n " %<br />

log n! = log i<br />

2 2 !<br />

#<br />

$<br />

&<br />

' = log2 i<br />

Lemma (siehe nächste Folie) $ durchschnittliche Tiefe # log2 n!<br />

n<br />

(<br />

i=1<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

i=1<br />

) (n +1)* log (n +1) + 2 n<br />

ln 2 + log (n +1)<br />

2<br />

= n * log 2 (n +1) + n<br />

ln 2<br />

Im Durchschnitt werden also mindestens n · log 2 (n + 1) – n / ln 2<br />

binäre Fragen benötigt, d.h. die Zeitkomplexität eines solchen<br />

Sortieralgorithmus beträgt "(n · log n) im durchschnittlichen und<br />

daher auch im schlimmsten Fall.<br />

… Eine untere Schranke "(n · log n) …<br />

• Lemma:<br />

In einem binären Baum mit k Blättern beträgt die durchschnittliche<br />

Tiefe der Blätter mindestens log 2 k.<br />

Beweis:<br />

durch Widerspruch!<br />

Annahme: Die Behauptung trifft nicht zu!<br />

Der binäre Baum T sei das Gegenbeispiel mit der kleinsten Anzahl<br />

Knoten.<br />

Fall I:<br />

T =<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

w<br />

T besteht aus einem einzigen Knoten $<br />

die Tiefe dieses einzigen Blattes ist 0, die durchschnittliche Tiefe ist<br />

also mindestens log 2 1 = 0 $<br />

Behauptung trifft auf T zu $<br />

T kann nicht nur aus einem einzigen Knoten bestehen.<br />

5-39<br />

5-40


… Eine untere Schranke "(n · log n) …<br />

• Fortsetzung Beweis: w<br />

Fall II:<br />

Fall III:<br />

T =<br />

T =<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

T'<br />

Wurzel w von T hat nur ein Kind $<br />

T' enthält weniger Knoten, aber alle k Blätter von T, die in T' eine<br />

geringere durchschnittliche Tiefe haben als in T $<br />

T' ist Gegenbeispiel mit geringerer Anzahl Knoten als T $<br />

Widerspruch zur Wahl von T $<br />

Wurzel w von T kann nicht nur ein Kind haben.<br />

T L<br />

w<br />

Wurzel w von T hat zwei Kinder: k L > 0 Blätter im linken Teilbaum T L ,<br />

k R > 0 Blätter im rechten Teilbaum T R mit k = k L + k R<br />

… Eine untere Schranke "(n · log n)<br />

• Fortsetzung Beweis:<br />

T Gegenbeispiel mit kleinster Anzahl Knoten $<br />

k L Blätter von T L<br />

k L<br />

log 2 k L<br />

T R<br />

haben durchschnittliche Tiefe<br />

von mindestens<br />

! log k + ! log k +1 (")<br />

2 L 2 R k + k k + k L R<br />

L R<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

k R<br />

k R Blätter von T R<br />

log 2 k R<br />

$ die durchschnittliche Tiefe der k Blätter von T beträgt mindestens<br />

(+) nimmt minimalen Wert log 2 k für k L = k R = k / 2 an<br />

$ durchschnittliche Tiefe der k Blätter von T beträgt mindestens log 2 k<br />

$ Widerspruch zur Wahl von T als Gegenbeispiel!<br />

Fall I, II, III $ Behauptung des Lemmas!<br />

5-41<br />

5-42


5.7 Quicksort<br />

• C. A. R. Hoare: Quicksort, Computer Journal 5(1), 10 – 15 (1962).<br />

• Quicksort kombiniert divide & conquer mit einer effizienten Strategie,<br />

die Elemente mit nur wenigen Austauschoperationen zu bewegen.<br />

• divide:<br />

– Partitioniere das Array in zwei disjunkte Teile, die "kleinen" Elemente<br />

auf der linken Seite und die "großen" Elemente auf der rechten Seite.<br />

conquer:<br />

– Sortiere jedes der zwei Teilarrays rekursiv.<br />

combine:<br />

– Keine Arbeit notwendig, um die beiden Teillösungen zu einer Lösung<br />

des Gesamtproblems zu kombinieren.<br />

kleine Elemente große Elemente<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Quicksort …<br />

… …<br />

• Effizienz von Quicksort hängt entscheidend davon ab, daß die in<br />

der divide-Phase entstehenden Teilarrays sich in der Größe nicht<br />

allzu stark unterscheiden!<br />

• Wähle einen Schwellenwert m $<br />

Definiere klein als ! m, groß als # m<br />

kleine Elemente<br />

! m<br />

… …<br />

große Elemente<br />

# m<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-43<br />

5-44


… Quicksort …<br />

• Partitioniere ein zu sortierendes Teilarray a[L .. R] durch einen<br />

links-nach-rechts Durchlauf L-R (durch Inkrementierung eines Index i )<br />

und einen gleichzeitigen rechts-nach-links Durchlauf R-L (durch<br />

Dekrementierung eines Index j ).<br />

L i j<br />

R<br />

… …<br />

…<br />

! m # m<br />

• L-R stoppt, sobald das erste Element mit a[ i ] " m angetroffen wird.<br />

• R-L stoppt, sobald das erste Element mit a[ j ] ! m angetroffen wird.<br />

• Nach Stop beider Durchläufe $<br />

a[ i ] :=: a[ j ] (Austausch) und setze die Durchläufe fort.<br />

• j < i $ Partitionierung ist beendet.<br />

• Rufe Quicksort rekursiv auf für<br />

! a[L .. j ] falls j > L<br />

"<br />

# a[ i .. R] falls i < R<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Quicksort …<br />

• •zu Beispiel: sortierende Folge: (25, 23, 3, 16, 4, 7, 29, 6)<br />

Partitionierung Partitionierung mit mit m m = = 16 16<br />

25 23 3 16 4 7 29 6<br />

i j<br />

6 23 25 36 23 716<br />

34 16 4 7 16 4 29 23 7 25 29 25 6<br />

i j<br />

6 7 3<br />

i<br />

16<br />

i i<br />

4<br />

ij<br />

23<br />

ij<br />

29<br />

j<br />

25<br />

j<br />

i j<br />

6 7 3 4 16 23 29 25<br />

j i<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

j<br />

5-45<br />

5-46


… Quicksort …<br />

• Wahl des Schwellenwertes muß die Korrektheit und die Effizienz von<br />

Quicksort sicherstellen!<br />

• Korrektheit:<br />

{x i : x i ! m} = $ oder {x j : x j " m} = $ $ Quicksort terminiert nicht! $<br />

Forderung: min(x i ) ! m ! max(x i )<br />

• Effizienz: m sollte nahe am Median liegen!<br />

• Der Median einer Menge von n Elementen ist das Element m, so daß<br />

gleich viele (bis auf eins) Elemente links und rechts von m liegen.<br />

• Wie findet man den Median von n Elementen?<br />

• Der exakte Median kann im schlimmsten Fall in O(n) Zeit bestimmt<br />

werden:<br />

M. Blum, R. W. Floyd, V. R. Pratt, R. L. Rivest, R. E. Tarjan:<br />

Time bounds for selection,<br />

Journal of Computer and System Sciences 7(4), 448 – 461 (1972).<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Quicksort …<br />

• Bei Verwendung des exakten Medians werden die zu sortierenden<br />

Elemente in zwei gleich große Teilmengen aufgeteilt $<br />

Zeitaufwand T(n) von Quicksort beträgt<br />

( )<br />

T(n) = 2 !T<br />

und man erhält<br />

T(n) = a · n · log2 n + (T(1) + b) · n – b<br />

n + a !n + b<br />

2<br />

d.h. vom theoretischen Standpunkt aus betrachtet benötigt Quicksort<br />

selbst im schlimmsten Fall nur !(n·log n) Zeit.<br />

• Aber: Die multiplikative Konstante des Algorithmus zum Auffinden<br />

des Medians ist sehr groß $ a ist sehr groß $ Quicksort ist in der<br />

Praxis nicht sehr effizient, falls der exakte Median benutzt wird!<br />

Es lohnt sich nicht, den hohen Aufwand zum Auffinden des exakten<br />

Medians zu treiben!<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-47<br />

5-48


… Quicksort<br />

• Techniken zur Bestimmung eines vermuteten Medians, der eine<br />

akzeptable Approximation liefert:<br />

– Ein Arrayelement in fixer Position, z.B. a[(L + R) / 2].<br />

Vorsicht: Benutze kein Element nahe a[L] oder a[R], da dies zu<br />

Ineffizienz im Falle von partiell sortierten Elementen führt.<br />

– Ein Arrayelement in zufällig gewählter Position $ gibt gute<br />

Resultate!<br />

– Den Median von drei oder fünf Elementen an fixen oder zufällig<br />

gewählten Positionen.<br />

– Den Mittelwert des kleinsten und größten Elementes $ erfordert<br />

zusätzlichen Durchlauf zu Beginn, danach kann der Mittelwert für<br />

jedes Teilarray während des vorherigen Aufteilungsprozesses<br />

bestimmt werden.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Quicksort: Rekursive Implementierung<br />

void quicksort(int[] a, int L, int R) {<br />

int m = a[(L + R) / 2];<br />

// min(a[L], … , a[R]) ! m ! max(a[L], … , a[R])<br />

int i = L;<br />

int j = R;<br />

while (i


Implementierung von Quicksort<br />

• Initialer Aufruf:<br />

quicksort(a, 0, a.length - 1);<br />

mit a.length > 1 garantiert, daß L < R in jedem rekursiven Aufruf<br />

von quicksort gilt!<br />

• Quicksort ist ein in place Algorithmus!<br />

• Quicksort ist nicht stabil!<br />

• Für sehr kleine Arrays (n ! 20) ist Quicksort nicht so effizient wie<br />

Insertion Sort.<br />

Quicksort rekursiv $<br />

Quicksort wird sehr häufig für kleine Arrays aufgerufen!<br />

In der Praxis: Modifikation von Quicksort, so daß für kleine Teilarrays<br />

Insertion Sort aufgerufen wird!<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Analyse von Quicksort …<br />

• T(n) sei der von Quicksort benötigte Zeitaufwand $<br />

T(n) = T(k) + T(n – k) + a · n + b (+)<br />

wenn das Array aufgeteilt wird in ein Teilarray mit k Elementen und<br />

ein Teilarray mit n – k Elementen.<br />

– Konstante b: Kosten für den Aufruf von Quicksort für das zu<br />

sortierende Array.<br />

– a · n: Kosten für die Partitionierung.<br />

– T(k) und T(n – k): Kosten der rekursiven Aufrufe für die beiden<br />

Teilarrays.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-51<br />

5-52


… Analyse von Quicksort …<br />

• Beispiel: Aufteilung mit m = 16<br />

25 13 3 16 22 18 29 6<br />

i j<br />

6 13 3 16 22 18 29 25<br />

i j<br />

6 13 3 16 22 18 29 25<br />

j i<br />

$ von Quicksort benötigter Zeitaufwand<br />

T(n) = T(k) + T(n – k – 1) + a · n + b<br />

Diese Rekurrenz hat gleiche asymptotische Lösung wie (+), daher<br />

Beschränkung auf (+).<br />

T(n) = T(k) + T(n – k) + a · n + b (+)<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Analyse von Quicksort …<br />

• Verhalten im besten Fall:<br />

Partitionierung des Arrays in zwei gleich große Teilarrays $<br />

Abschnitt 4.6 $<br />

( )<br />

T(n) = 2 !T n<br />

2<br />

+ a !n + b<br />

T(n) = a · n · log 2 n + (T(1) + b) · n – b<br />

= a · n · log 2 n + g(n) mit g(n) # O(n)<br />

$ Quicksort's Zeitkomplexität im besten Fall ist !(n · log n).<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-53<br />

5-54


… Analyse von Quicksort …<br />

• Verhalten im schlimmsten Fall:<br />

Partitionierung des Arrays in ein Teilarray mit n – 1 Elementen und<br />

ein Teilarray mit einem Element $<br />

T(n) = T(1) + T(n – 1) + a · n + b<br />

Substitution:<br />

T(n – 1) = T(1) + T(n – 2) + a · (n – 1) + b<br />

$ T(n) = 2 · T(1) + T(n – 2) + a · n + a · (n – 1) + 2 · b<br />

Wiederholte Substitution ergibt<br />

$ Quicksort's Zeitkomplexität im schlimmsten Fall ist !(n 2 T(n) = n !T(1) + a !<br />

).<br />

n2 + n " 2 + b !(n "1)<br />

2<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Analyse von Quicksort<br />

• Durchschnitt über alle n! Permutationen ist schwer zu ermitteln,<br />

daher Analyse des Verhaltens im typischen Fall, d.h. unter der<br />

Annahme: Für alle k, 1 ! k < n, ist die Wahrscheinlichkeit 1 / (n – 1),<br />

daß das Array aufgeteilt wird in Teilarrays der Größen k und n – k.<br />

• Verhalten im typischen Fall:<br />

Der im Durchschnitt von Quicksort benötigte Zeitaufwand wird<br />

beschrieben durch<br />

T(n) = 1<br />

n !1<br />

n!1<br />

" (T(k ) +T(n ! k )) + a # n + b = 2<br />

n !1<br />

k=1<br />

Lösung durch trial-and-error (Abschnitt 4.5):<br />

T(n) = (ln 4) · a · n · log 2 n + g(n) mit ln 4 # 1.386 und g(n) # O(n)<br />

$ Quicksort's asymptotisches Verhalten ist im typischen Fall nur etwa<br />

40% schlechter als im besten Fall<br />

$ Quicksort's Zeitkomplexität im typischen Fall ist !(n·log n).<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

n!1<br />

"<br />

k=1<br />

T(k ) + a # n + b<br />

5-55<br />

5-56


5.8 Merge Sort<br />

• Bisher:<br />

Interne Sortieralgorithmen mit direktem Zugriff auf jedes Element!<br />

• Annahme bei der Algorithmenanalyse: Zugriff auf a[i] ist primitive<br />

Operation mit konstanten Kosten, d.h. unabhängig von i und n.<br />

• Annahme gilt nicht für Sekundärspeichermedien wie Festplatten oder<br />

Magnetbänder!<br />

• Externes Sortieren:<br />

Zu sortierende Elemente sind in einer sequentiellen Datei (File)<br />

gespeichert.<br />

– Zu jedem Zeitpunkt kann man über einen Zeiger nur auf genau ein<br />

Element zugreifen.<br />

– Zugriff auf andere Elemente erfordert Neupositionierung des<br />

Zeigers in der Datei. Zeiger kann nur in einer Richtung (vorwärts)<br />

oder in beide Richtungen (vorwärts und rückwärts) wandern.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Externer Merge Sort …<br />

• Annahme: Zeit zur Repositionierung des Zeigers ist proportional zur<br />

zurückgelegten Distanz.<br />

• Annahme<br />

– begünstigt Algorithmen, die nur benachbarte Elemente verarbeiten<br />

(vergleichen, austauschen).<br />

– benachteiligt Algorithmen, die wahlfreien Zugriff auf die Elemente<br />

benötigen (z.B. Quicksort).<br />

• f k bezeichne das k-te Element, das in Datei f gespeichert ist.<br />

• Ein Lauf (Run) ist eine sortierte Teilfolge von Elementen f i , f i+1 , …, f j ,<br />

die aufeinanderfolgend in Datei f gespeichert sind und für die<br />

f k ! f k+1 für alle k mit i ! k ! j – 1 gilt.<br />

• Verfügbarer Puffer im Hauptspeicher mit Kapazität m Elemente<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-57<br />

5-58


… Externer Merge Sort …<br />

• Externer Sortieralgorithmus, basierend auf dem Prinzip des Merge Sort:<br />

– Initiale Phase:<br />

Erzeuge im Hauptspeicher initiale Läufe der Länge m (evtl. weniger<br />

Elemente im letzten Lauf) und speichere die Läufe in Datei f.<br />

– Kopierphase (Copy):<br />

Kopiere die Hälfte der Läufe von f in Datei g, die andere Hälfte in<br />

Datei h.<br />

– Verschmelzungsphase (Merge):<br />

Verschmelze jeden Lauf von g mit genau einem Lauf von h und<br />

schreibe den resultierenden Lauf nach f.<br />

– Wiederhole Kopier- und Verschmelzungsphase, bis f nur noch einen<br />

einzigen Lauf enthält.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Externer Merge Sort<br />

• Realisierung der initialen Phase:<br />

– Lese aus Eingabedatei e die m Elemente e r·m+1 , e r·m+2 , …, e r·m+m in<br />

den Puffer (r = 0, 1, …).<br />

– Sortiere die Elemente im Puffer mit einem internen Sortierverfahren<br />

(z.B. Quicksort).<br />

– Schreibe den so erzeugten r-ten Lauf in die Datei f, die den gleichen<br />

physischen Speicherbereich belegen kann wie die ursprüngliche<br />

Datei e.<br />

$ f ist partiell sortiert: " k, r · m + 1 ! k < r · m + m: f k ! f k+1<br />

• Jeder Copy-Merge-Zyklus halbiert die Anzahl Läufe:<br />

copy<br />

g: s/2 Läufe<br />

f: s Läufe f: s/2 Läufe<br />

h: s/2 Läufe<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

merge<br />

Nach &log 2 (n/m)' Copy-Merge-Zyklen besteht f aus einem einzigen<br />

Lauf, der die sortierte Folge aller Elemente darstellt.<br />

5-59<br />

5-60


Interner Merge Sort …<br />

void mergeSort(int[] a, int L, int R) {<br />

int M = (L + R) / 2;<br />

if (L < M)<br />

mergeSort(a, L, M);<br />

if (M + 1 < R)<br />

mergeSort(a, M + 1, R);<br />

merge(a, L, M, R);<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Interner Merge Sort …<br />

void merge(int[] a, int L, int M, int R) {<br />

int[] b = new int[R–L+1];<br />

int i = L;<br />

int j = M + 1;<br />

int k;<br />

for (k = L; k M) || ((j


… Interner Merge Sort …<br />

• Interner Merge Sort ist kein in place Algorithmus, da er ein<br />

zusätzliches Array benötigt.<br />

• Interner Merge Sort ist stabil!<br />

• Interner Merge Sort ist ein divide & conquer Algorithmus:<br />

7<br />

7 5<br />

5 7<br />

7 5 3 2<br />

5<br />

2 3 5 7<br />

7 5 3 2 1 8 6 4<br />

divide<br />

3 2<br />

divide<br />

divide<br />

1 2 3 4 5 6 7 8<br />

1 8<br />

1 8 6 4<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Interner Merge Sort<br />

• Zeitaufwand<br />

( )<br />

T(n) = 2 !T n<br />

2<br />

6 4<br />

3 2 1 8 6 4<br />

merge<br />

2 3<br />

1 8 4 6<br />

merge<br />

merge<br />

1 4 6 8<br />

+ a !n + b<br />

Abschnitt 4.6 $<br />

T(n) = a · n · log 2 n + (T(1) + b) · n – b<br />

= a · n · log 2 n + g(n) mit g(n) # O(n)<br />

$ Zeitkomplexität von Merge Sort ist in jedem Fall !(n · log n).<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-63<br />

5-64


5.9 Ist es möglich, in linearer Zeit zu sortieren?<br />

• Untere Schranke "(n · log n) gilt für Sortieralgorithmen, die Information<br />

über die Anordnung der Elemente nur über zweiwertige Fragen und<br />

nichts anderem beziehen!<br />

• Diese untere Schranke gilt nicht unbedingt für andere Situationen!<br />

• Beispiel 1: Sortieren einer Permutation<br />

Sortieren einer Permutation der ganzen Zahlen von 1 bis n in !(n) Zeit<br />

durch Speichern des Elementes i im Arrayelement mit Index i – 1.<br />

• Beispiel 2: Counting Sort<br />

Annahme: Jedes der n zu sortierenden Elemente ist eine ganze Zahl<br />

zwischen 0 und k – 1.<br />

Idee: Bestimme für jedes Element x die Anzahl der Elemente ! x $<br />

Jedes Element kann direkt an seine korrekte Position im Ausgabearray<br />

gebracht werden.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Counting Sort …<br />

void countingSort(int[] a, int k) {<br />

int[] b = new int[a.length];<br />

int[] c = new int[k];<br />

int i;<br />

for (i = 0; i < k; i++)<br />

c[i] = 0;<br />

for (i = 0; i < a.length; i++)<br />

c[a[i]] = c[a[i]] + 1;<br />

// c[i] enthält jetzt die Anzahl Elemente = i<br />

for (i = 1; i < k; i++)<br />

c[i] = c[i] + c[i - 1];<br />

// c[i] enthält jetzt die Anzahl Elemente ! i<br />

for (i = a.length - 1; i >= 0; i--) {<br />

b[c[a[i]] - 1] = a[i];<br />

c[a[i]] = c[a[i]] - 1;<br />

}<br />

for (i = 0; i < a.length; i++)<br />

a[i] = b[i];<br />

}<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-65<br />

5-66


… Counting Sort<br />

• Counting Sort ist kein in place Algorithmus.<br />

• Counting Sort ist stabil!<br />

• Zeitkomplexität von Counting Sort ist !(n + k).<br />

• Counting Sort wird in der Praxis eingesetzt, wenn k # O(n) $<br />

Zeitkomplexität !(n).<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Sortieren in linearer Zeit?<br />

• Widersprechen Beispiele 1 & 2 der unteren Schranke "(n · log n)?<br />

• Nein, da das Sammeln von Informationen durch das Stellen von (in<br />

den Zugriffen auf die einzelnen Arrayelemente versteckten) Fragen<br />

erfolgt, die mächtiger sind als binäre Fragen:<br />

– n-wertige Fragen im Beispiel 1,<br />

– k-wertige Fragen im Beispiel 2.<br />

• Jede k-wertige Frage entspricht log 2 k binären Fragen. Wird dieser<br />

Umtauschfaktor berücksichtigt, so sind die Zeitkomplexitäten der<br />

obigen Algorithmen<br />

– !(n · log n) im Beispiel 1 und<br />

– !(n · log k + k) im Beispiel 2.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-67<br />

5-68


Radix Sort<br />

• Beispiel 3: Radix Sort<br />

Annahme: Die zu sortierenden Elemente sind ganze Zahlen bestehend<br />

aus höchstens d Ziffern, wobei die Ziffernpositionen von 1 bis d durchnumeriert<br />

sind. Die am weitesten rechts stehende Ziffer hat Position 1,<br />

die am weitesten links stehende Ziffer Position d. Die Basis des<br />

Zahlensystems sei b, d.h. 0 ! z < b für jede Ziffer z.<br />

Implementierung:<br />

void radixSort(int[] a, int d) {<br />

for (int i = 1; i


… Sortiernetzwerke …<br />

• Komparatoren sind Prozessoren, die Werte auf zwei Eingabekanälen<br />

vergleichen und sie an zwei Ausgabekanäle weitergeben, so daß der<br />

kleinere Wert auf dem oberen Ausgabekanal, der größere Wert auf<br />

dem unteren Ausgabekanal erscheint.<br />

x<br />

y<br />

Komparator<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

min(x, y)<br />

max(x, y)<br />

• Anordnung von Komparatoren innerhalb eines Netzwerks, daß aus n<br />

waagerecht verlaufenden Kanälen besteht. Vertikale Verbindungen<br />

zwischen den Kanälen repräsentieren Komparatoren. Zahlen<br />

repräsentieren Eingabewerte, die durch das Netzwerk wandern und<br />

miteinander verglichen werden:<br />

4<br />

1<br />

3<br />

2<br />

… Sortiernetzwerke …<br />

1<br />

4<br />

3<br />

4 2<br />

1<br />

3<br />

2<br />

4 4<br />

• Ein Netzwerk von Komparatoren ist ein Sortiernetzwerk, wenn es jede<br />

Eingabekonfiguration sortiert.<br />

• Eingabekonfiguration besteht aus verschiedenen Elementen $<br />

oBdA: Eingabekonfigurationen sind Permutationen der Zahlen<br />

1, 2, … , n<br />

• Netzwerk, daß duplikatfreie Eingabekonfigurationen sortiert, sortiert<br />

auch Konfigurationen mit Duplikaten.<br />

• (4, 1, 3, 2) wird nicht korrekt sortiert:<br />

$ kein Sortiernetzwerk!<br />

4<br />

1<br />

3<br />

2<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

1<br />

4<br />

3<br />

4 2<br />

1<br />

3<br />

2<br />

4 4<br />

5-71<br />

5-72


… Sortiernetzwerke …<br />

• Wieviele Komparatoren werden benötigt, wie sollten sie plaziert werden?<br />

• Sortiernetzwerk für 4 Elemente:<br />

k 1 k 2 k 3 k 4 k 5<br />

• Wie kann man feststellen, ob ein gegebenes Netzwerk korrekt sortiert?<br />

– Ausschöpfendes Testen ist allenfalls für kleine Netzwerke möglich.<br />

– Netzwerke mit regelmäßiger Struktur erlauben gewöhnlich einen<br />

einfacheren Korrektheitsbeweis.<br />

• Beispiel:<br />

k 1 , k 2 und k 3 plazieren das kleinste Element auf dem obersten Kanal.<br />

k 1 , k 2 und k 4 plazieren das größte Element auf dem untersten Kanal.<br />

Die mittleren beiden Elemente verbleiben auf den beiden mittleren<br />

Kanälen und werden durch k 5 in die richtige Reihenfolge gebracht.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Sortiernetzwerke …<br />

• Entwurfsprinzipien für die Entwicklung großer Sortiernetzwerke?<br />

• Abbildung eines Sortieralgorithmus, der für sequentielle Maschine<br />

entwickelt wurde, in ein Sortiernetzwerk im allgemeinen nicht direkt<br />

möglich, da Netzwerke ein eingeschränkteres Berechnungsmodell<br />

darstellen:<br />

Sequentieller Sortieralgorithmus führt Vergleiche in Abhängigkeit<br />

vom Ausgang vorheriger Vergleiche durch ,<br />

Sortiernetzwerk macht immer die gleichen Vergleiche für alle<br />

Eingabekonfigurationen.<br />

• Aber: gleiche grundlegende Designprinzipien, die beim Entwurf<br />

sequentieller Algorithmen eingesetzt werden, treffen auch auf<br />

parallele Algorithmen zu.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-73<br />

5-74


… Sortiernetzwerke …<br />

• Divide & Conquer:<br />

Plaziere zwei Sortiernetzwerke für jeweils n Kanäle übereinander und<br />

kombiniere sie in ein Sortiernetzwerk mit 2·n Kanälen durch<br />

Hinzufügen eines Merge-Netzwerks, daß ihre Ausgaben verschmilzt.<br />

Die rigide Struktur von Komparatornetzwerken kompliziert die<br />

Kombination von Netzwerken!<br />

• Inkrementeller Algorithmus:<br />

Plaziere einen n-ten Kanal unter ein Sortiernetzwerk mit n – 1 Kanälen<br />

und schalte diesem Netzwerk eine Kaskade von Komparatoren vor<br />

oder nach, die diesen Kanal in das existierende Netzwerk einbinden.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

… Sortiernetzwerke …<br />

• Insertion Sort:<br />

Elemente auf den oberen n – 1 Kanälen bereits sortiert $ Element<br />

auf dem untersten Kanal wandert nach oben in die richtige Position:<br />

Sortiernetzwerk<br />

für<br />

n–1 Elemente<br />

• Selection Sort:<br />

Das größte Element wandert zuerst nach unten, dann werden die<br />

restlichen Elemente sortiert:<br />

Sortiernetzwerk<br />

für<br />

n–1 Elemente<br />

Induktion<br />

Induktion<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-75<br />

5-76


… Sortiernetzwerke<br />

• Komparatoren können zur Minimierung der Anzahl Berechnungsstufen<br />

an ihren Kanälen entlang verschoben werden unter der Voraussetzung,<br />

daß die Topologie des Netzwerkes sich nicht ändert.<br />

Diese Kompression reduziert sowohl Insertion Sort als auch Selection<br />

Sort auf das folgende Netzwerk:<br />

!(n 2 ) Komparatoren, Zeitkomplexität 2 · n – 3 # !(n)<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

Zusammenfassung<br />

• <strong>Sequentielles</strong> <strong>Suchen</strong> in Array in !(n) Zeit<br />

• Binäres <strong>Suchen</strong> in geordnetem Array in !(log n) Zeit<br />

• Sortierproblem<br />

• Verschiedene Typen von Sortieralgorithmen<br />

• Einfache Algorithmen mit Zeitaufwand !(n 2 ):<br />

Insertion Sort, Selection Sort<br />

• Untere Schranke "(n · log n)<br />

• Quicksort mit durchschnittlichem Zeitaufwand !(n · log n),<br />

im schlimmsten Fall !(n 2 )<br />

• Interner Merge Sort mit Zeitaufwand !(n · log n) im schlimmsten Fall<br />

• Sortieren in linearer Zeit? Counting Sort, Radix Sort<br />

• Ausblick:<br />

Heapsort ist ein nicht stabiler, in place Sortieralgorithmus mit<br />

Zeitaufwand !(n · log n) im schlimmsten Fall.<br />

© Klaus Hinrichs Informatik II – <strong>Suchen</strong> und Sortieren<br />

5-77<br />

5-78

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!