Lektion 2.1

Such- und Sortierverfahren, Laufzeit

22 min Lesezeit

Ein Telefonbuch einer Großstadt hat Hunderttausende Einträge. Du blätterst nicht Seite für Seite, sondern schlägst die Mitte auf, siehst, ob dein Name davor oder dahinter steht, und halbierst den Rest. Bei einer Million Einträgen bist du nach höchstens 20 Schritten am Ziel. Das geht nur, weil das Buch sortiert ist.

Du lernst, wie lineare und binäre Suche arbeiten, fünf Sortierverfahren und wie man ihre Laufzeit unabhängig vom Rechner beschreibt: mit der O-Notation und den Komplexitätsklassen.

Definition

Lineare und binäre Suche

Die lineare Suche prüft die Elemente der Reihe nach und braucht im schlechtesten Fall nn Vergleiche. Sie funktioniert in jeder Liste. Die binäre Suche verlangt eine sortierte Liste. Sie vergleicht mit dem mittleren Element und sucht nur in der Hälfte weiter, in der das Gesuchte liegen kann.

ZeileEinrückungAnweisung
10def binaer(liste, x):\texttt{def binaer(liste, x):}
21links = 0\texttt{links = 0}
31rechts = len(liste) - 1\texttt{rechts = len(liste) - 1}
41while links <= rechts:\texttt{while links <= rechts:}
52mitte = (links + rechts) // 2\texttt{mitte = (links + rechts) // 2}
62if liste[mitte] == x:\texttt{if liste[mitte] == x:}
73return mitte\texttt{return mitte}
82if liste[mitte] < x:\texttt{if liste[mitte] < x:}
93links = mitte + 1\texttt{links = mitte + 1}
102else:\texttt{else:}
113rechts = mitte - 1\texttt{rechts = mitte - 1}
121return -1\texttt{return -1}

Jeder Durchlauf halbiert den Suchbereich: Aus nn Elementen werden höchstens n/2n/2, dann n/4n/4 und so weiter. Für n=1000n = 1000 genügen 10 Durchläufe, für n=1000000n = 1000000 sind es 20.

d(n)≤⌊log⁡2n⌋+1d(n) \leq \lfloor \log_2 n \rfloor + 1
Beispiel

Binäre Suche von Hand

Die Liste ist 3, 8, 12, 17, 21, 25, 30, 34 (Index 0 bis 7). Wie läuft binaer(liste, 25)\texttt{binaer(liste, 25)} und wie binaer(liste, 10)\texttt{binaer(liste, 10)}?

Suche nach 25: links = 0, rechts = 7, mitte = 3, liste[3] = 17 ist kleiner als 25, also links = 4. Dann mitte = (4 + 7) // 2 = 5, liste[5] = 25. Rückgabe 5 nach 2 Durchläufen.

Suche nach 10:

Durchlauflinksrechtsmitteliste[mitte]Folge
10731717 > 10, rechts = 2
202188 < 10, links = 2
32221212 > 10, rechts = 1

Nun ist links = 2 größer als rechts = 1, die Schleife endet, und die Rückgabe ist -1. Die 10 steht nicht in der Liste.

Definition

Sortierverfahren und Stabilität

Alle Verfahren sortieren aufsteigend.

  • Bubblesort vertauscht benachbarte Elemente, die falsch herum stehen, und schiebt so das größte nach hinten.
  • Selectionsort sucht im unsortierten Teil das kleinste Element und tauscht es an den Anfang dieses Teils.
  • Insertionsort fügt das nächste Element des unsortierten Teils an der richtigen Stelle in den sortierten Teil davor ein, wie Spielkarten auf der Hand.
  • Mergesort teilt die Liste in zwei Hälften, sortiert jede Hälfte nach demselben Verfahren und mischt die beiden sortierten Hälften zu einer sortierten Liste.
  • Quicksort wählt ein Pivot-Element, ordnet die kleineren Elemente links und die größeren rechts davon an und sortiert beide Seiten nach demselben Verfahren.

Bei Mergesort und Quicksort wendet sich das Verfahren auf kleinere Teillisten selbst an. Dieses Prinzip heißt Rekursion und kommt in der nächsten Lektion.

Ein Verfahren ist stabil, wenn Elemente mit gleichem Schlüssel ihre Reihenfolge behalten. Das zählt beim Sortieren nach mehreren Merkmalen: Wer erst nach Name und dann stabil nach Klasse sortiert, erhält die Namen innerhalb jeder Klasse alphabetisch. Selectionsort ist nicht stabil: Aus 2a, 2b, 1 wird 1, 2b, 2a.

Beispiel

Insertionsort von Hand

Sortiere 5, 2, 9, 1, 7 mit Insertionsort. Ein Element wandert durch Vertauschen mit seinem linken Nachbarn nach vorn, solange dieser größer ist. Zähle Vergleiche und Vertauschungen.

Der sortierte Teil steht vorn, zuerst nur die 5.

  • 2 ist kleiner als 5: vertauschen. Ergebnis 2, 5, 9, 1, 7 (1 Vergleich, 1 Vertauschung).
  • 9 ist nicht kleiner als 5: bleibt (1 Vergleich).
  • 1 ist kleiner als 9, 5 und 2 und wandert an den Anfang. Ergebnis 1, 2, 5, 9, 7 (3 Vergleiche, 3 Vertauschungen).
  • 7 ist kleiner als 9: vertauschen, dann ist 7 nicht kleiner als 5. Ergebnis 1, 2, 5, 7, 9 (2 Vergleiche, 1 Vertauschung).

Insgesamt sind es 1 + 1 + 3 + 2 = 7 Vergleiche und 1 + 0 + 3 + 1 = 5 Vertauschungen. Jede Vertauschung beseitigt ein Paar, das falsch herum steht: (5, 2), (5, 1), (2, 1), (9, 1), (9, 7). Die Laufzeit hängt also von der Eingabe ab.

Insertionsort Schritt für Schritt

Hier steht dieselbe Liste. Klicke immer wieder auf "Weiter" und beobachte, wie ein Element nach vorn wandert, bis links davon ein kleineres steht. Am Ende zeigen die Zähler "Vergleiche" 7 und "Vertauschungen" 5. Gib dann im Feld "Zahlen" die sortierte Liste 1, 2, 5, 7, 9 ein und klicke auf "Übernehmen": Es sind nur 4 Vergleiche und keine Vertauschung. Mit der umgekehrten Liste 9, 7, 5, 2, 1 sind es 10 Vergleiche und 10 Vertauschungen.

Definition

Laufzeit und O-Notation

Die Laufzeit T(n)T(n) zählt die Grundschritte (etwa Vergleiche) in Abhängigkeit von der Größe nn der Eingabe, nicht Sekunden. Sie hängt auch von der Eingabe ab: Man unterscheidet besten, mittleren und schlechtesten Fall. Für große nn zählt nur die Wachstumsart. Die O-Notation gibt eine obere Schranke an: T∈O(f)T \in O(f) heißt, dass es eine Konstante c>0c > 0 und ein n0n_0 gibt, sodass T(n)T(n) für alle n≥n0n \geq n_0 höchstens c⋅f(n)c \cdot f(n) beträgt. Konstante Faktoren und kleinere Summanden fallen weg.

T∈O(f)  ⟺  ∃ c>0, n0: ∀n≥n0: T(n)≤c⋅f(n)T \in O(f) \iff \exists\, c > 0,\ n_0:\ \forall n \geq n_0:\ T(n) \leq c \cdot f(n)
Beispiel

Eine Laufzeit in O(n²) einordnen

Zeige für T(n)=3n2+5n+7T(n) = 3n^2 + 5n + 7, dass T∈O(n2)T \in O(n^2) gilt.

Wähle c=4c = 4 und n0=7n_0 = 7. Für n≥7n \geq 7 gilt n2≥7n=5n+2n≥5n+14≥5n+7n^2 \geq 7n = 5n + 2n \geq 5n + 14 \geq 5n + 7. Daraus folgt T(n)=3n2+5n+7≤3n2+n2=4n2T(n) = 3n^2 + 5n + 7 \leq 3n^2 + n^2 = 4n^2. Probe bei n=7n = 7: T(7)=147+35+7=189≤4⋅49=196T(7) = 147 + 35 + 7 = 189 \leq 4 \cdot 49 = 196. Mit n0=6n_0 = 6 ginge es nicht, denn T(6)=145>4⋅36=144T(6) = 145 > 4 \cdot 36 = 144.

Definition

Komplexitätsklassen

Verfahren teilt man nach der Wachstumsart ihrer Laufzeit in Komplexitätsklassen ein: O(1)O(1) konstant, O(log⁡n)O(\log n) logarithmisch, O(n)O(n) linear, O(nlog⁡n)O(n \log n), O(n2)O(n^2) quadratisch und O(2n)O(2^n) exponentiell. Der Logarithmus hat hier die Basis 2. Eine andere Basis ändert nur den konstanten Faktor.

nlog⁡2n\log_2 nnnnlog⁡2nn \log_2 nn2n^22n2^n
103,310331001024
1006,610066410000≈1,3⋅1030\approx 1{,}3 \cdot 10^{30}
100010100099661000000>10300> 10^{300}

Angenommen, ein Rechner schafft eine Milliarde Schritte pro Sekunde. Bei einer Million Elementen braucht ein Verfahren mit nlog⁡2nn \log_2 n Schritten etwa 0,02 Sekunden, eines mit n2n^2 Schritten 1000 Sekunden, fast 17 Minuten. Bei 2n2^n Schritten und n=100n = 100 dauert es länger, als das Weltall alt ist.

Definition

Korrektheit, Effizienz und Speicherbedarf

Ein Sortierverfahren ist korrekt, wenn es für jede Eingabe terminiert und die Ausgabe aufsteigend geordnet und eine Umordnung der Eingabe ist. Man begründet das mit einer Schleifeninvariante, einer Aussage, die vor und nach jedem Durchlauf gilt. Beim Insertionsort lautet sie: Nach dem Durchlauf für Index ii sind die vorderen i+1i + 1 Elemente sortiert. Am Ende der Schleife folgt daraus die Korrektheit.

Effizienz meint Laufzeit und Speicherbedarf. Ein Verfahren mit nur konstantem Zusatzspeicher arbeitet am Platz. Jedes Verfahren, das nur durch Vergleiche sortiert, braucht im schlechtesten Fall mindestens etwa nlog⁡2nn \log_2 n Vergleiche.

Verfahrenbester Fallschlechtester FallstabilZusatzspeicher
Bubblesort mit vorzeitigem Endennn2n^2jakonstant
Selectionsortn2n^2n2n^2neinkonstant
Insertionsortnnn2n^2jakonstant
Mergesortnlog⁡nn \log nnlog⁡nn \log njaetwa nn
Quicksortnlog⁡nn \log nn2n^2neingering (Aufrufstapel)

Mergesort ist auch im schlechtesten Fall schnell, braucht dafür aber Zusatzspeicher. Beim Quicksort tritt der schlechteste Fall ein, wenn das Pivot immer das kleinste Element ist.

Häufige Fehler

Binäre Suche in jeder Liste

„Die binäre Suche ist schneller, also nehme ich sie für jede Liste.“

Richtig ist

Sie funktioniert nur in sortierten Listen. In 9, 2, 7, 4, 1 sucht sie die 1: Die Mitte ist 7, also geht es links weiter, dann ist die Mitte 9, und die Suche endet mit -1, obwohl die 1 vorhanden ist.

Häufige Fehler

O(n²) ist immer langsamer als O(n log n)

„Insertionsort ist quadratisch, Mergesort nicht, also ist Mergesort immer schneller.“

Richtig ist

Die O-Notation sagt nur etwas über große nn und verschweigt konstante Faktoren. Bei kleinen oder fast sortierten Listen kann Insertionsort schneller sein, bei einer sortierten braucht er nur n−1n - 1 Vergleiche.

Zusammenfassung

  • Die lineare Suche braucht bis zu nn Vergleiche. Die binäre Suche braucht höchstens ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 Durchläufe, verlangt aber eine sortierte Liste.

  • Bubblesort, Selectionsort und Insertionsort sind im schlechtesten Fall quadratisch, Mergesort ist immer O(nlog⁡n)O(n \log n), Quicksort im Mittel.

  • Stabil heißt: Gleiche Schlüssel behalten ihre Reihenfolge. Selectionsort und Quicksort sind nicht stabil.

  • Die Laufzeit zählt Schritte in Abhängigkeit von nn, getrennt nach bestem, mittlerem und schlechtestem Fall.

  • Die O-Notation ist eine obere Schranke ohne konstante Faktoren und kleinere Summanden. Klassen sind O(n)O(n), O(nlog⁡n)O(n \log n), O(n2)O(n^2), O(2n)O(2^n) und andere.

  • Korrektheit zeigt man mit einer Schleifeninvariante. Zeit und Speicher muss man gegeneinander abwägen.

Übungen

Aufgabe 1Numerisch

Die binäre Suche aus der Lektion durchsucht eine sortierte Liste mit 1000 Einträgen. Wie viele Schleifendurchläufe braucht sie im schlechtesten Fall?

Aufgabe 2Multiple Choice

Welches Verfahren ist in der Form aus der Lektion nicht stabil?

Aufgabe 3Numerisch

Ein Rechner schafft angenommen eine Milliarde Schritte pro Sekunde. Ein Verfahren braucht bei n=100000n = 100000 Elementen n2n^2 Schritte. Wie viele Sekunden dauert das?

Aufgabe 4Multiple Choice

Die Laufzeit ist T(n)=4n2+20n+100T(n) = 4n^2 + 20n + 100. Welche ist die kleinste der folgenden Klassen, in der T liegt?

Aufgabe 5Freitext

Du musst in einer Liste mit 100000 Einträgen 5000 Mal suchen. Vergleiche die Möglichkeiten, jedes Mal linear zu suchen oder die Liste einmal zu sortieren und dann binär zu suchen.