Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Grundlagen der Algorithmen und Datenstrukturen Kapitel 5

Ähnliche Präsentationen


Präsentation zum Thema: "Grundlagen der Algorithmen und Datenstrukturen Kapitel 5"—  Präsentation transkript:

1 Grundlagen der Algorithmen und Datenstrukturen Kapitel 5
Prof. Dr. Christian Scheideler (Stv: Stefan Schmid) SS 2008 Willkommen zur Vorlesung. Prof. Scheideler ist diese Woche an einer Konferenz in Hongkong, weshalb ich ihn nun hier vertrete. In dieser Woche werden wir ein sehr fundamentales algorithmisches Problem anschauen, welches oft auch im Alltag anzutreffen ist: das Sortieren von Elementen (zum Beispiel Adresselisten nach Vornamen sortieren, oder schweizer Bankkontos nach Grösse, etc.) Kapitel 5

2 Ein paar Infos vorweg... Kapitel 0-4 sind relevant für Klausur (also inkl. Hashing)! Notenbonus der Uebungen gilt nicht, falls Prüfung nicht bestanden. Gesamtnote = 50% Mid + 50% End Probeprüfung auf Web (Lösung?) Mitnehmen: Handschriftliches DIN A4 Blatt Kapitel 5

3 Kapitel 5

4 Wörterbuch S: Menge von Elementen
Jedes Element e identifiziert über key(e). Operationen: S.insert(e: Element): S:=S [ {e} S.remove(k: Key): S:=Sn{e}, wobei e das Element ist mit key(e)=k S.find(k: Key): Falls es ein e2 S gibt mit key(e)=k, dann gib e aus, sonst gib ? aus Wir haben bereits das Konzept von Wörterbüchern kennengelernt. Ein Wörterbuch basiert auf einer Menge S von Elementen welche sich über einen eindeutigen Schlüssel identifizieren lassen. Die Datenstruktur bietet folgende Operationen an: Man kann Elemente einfügen, d.h. zur Menge beifügen. Man kann ein Element mit einem gewissen Schlüssel entfernen. Man kann Elemente nach einem gewissen Schlüssel suchen. Kapitel 5

5 Statisches Wörterbuch
Lösungsmöglichkeiten: Perfektes Hashing Vorteil: Suche in konstanter Zeit Nachteil: keine Ordnung auf Elementen, d.h. Bereichsanfragen (alle Namen, die mit A anfangen) teuer Speicherung der Daten in sortiertem Feld Vorteil: Bereichsanfragen möglich Nachteil: Suche teurer (logarithmische Zeit) 1 3 5 14 19 10 Wir haben bereits zwei Möglichkeiten kennengelernt, ein solches /statisches/ Wörterbuch zu implementieren. Wer weiss welche? Hashing – möglichst perfektes wenn‘s dann geht: Was ist Vor- und Nachteil? Sehr effizientes Einfügen und Löschen und Finden von Elementen. Nachteil: Aehnliche Daten werden völlig zufällig verteilt, kann keine Bereichsabfragen machen effizient: Nicht alle Leute finden mit Namen zwischen Meier und Müller z.B. Dafür müsste man jedes Element einzeln nachschauen. Sortiertes Feld: Kann nun alle Elemente mit Schlüssel (e.g., Nachnamen die mit A beginnen) finden effizient: suche erstes und laufe linear durch bis keines mehr. Um ein konkretes Element zu finden, brauchen wir nun aber logarithmische Zeit, also nicht mehr konstant! Im folgenden werden wir uns mit der Frage beschäftigen, wie man ein Feld denn überhaupt sortieren kann! 1 3 10 14 19 5 Wie geht das? Kapitel 5

6 Wieso Sortieren? Telefonbuch ohne Sortierung nach Namen?
Beispiel aus dem Skript: Statistik bei Volkszählung Bei der US Volkzählung 1880 benötigten 1500 Leute 7 Jahre um die Daten zu zählen und ordnen Herman Hollerith baute darauf hin Sortiermaschinen, welche die Bearbeitungszeit der nächsten Zählung trotz grösserer Population auf 1 Jahr reduzierte. Ursprung von IBM Sortieren (Pre-processing) hilft vielerlei Fragen effizient zu beantworten: e.g., finde alle Personen zw. 20 und 30 die auf einem Bauernhof leben Kapitel 5

7 Sortierproblem Eingabe: Sequenz s = <e1,…,en> mit Ordnung <= auf den Schlüsseln key(ei) (Beispiel: ) Ausgabe: Sequenz s´ = <e´1,…,e´n>, so dass key(ei)<=key(ei+1) für alle 1<=i<n und s´ eine Permutation von s ist (Beispiel: ) 5 10 1 14 3 19 In der heutigen Vorlesung werden wir das problem studieren, wie man eine Menge von Elementen sortieren kann. Wir nehmen an, dass wir zu Beginn eine beliebige Sequenz von Elementen haben. Das Ziel eines Sortieralgorithmus ist es, die Sequenz zu sortieren dass gilt, dass die neue Sequenz eine Permutation der alten ist, und jedes Element höchstens so gross ist wie das nachfolgende („aufsteigend sortiert“). 1 3 10 14 19 5 Kapitel 5

8 Ordnungen Ordnung auf Zahlen: klar
Ordnung auf Vektoren: z.B. Länge des Vektors Ordnung auf Namen: lexikographische Ordnung (erst alle Namen, die mit A beginnen, dann B, usw.) Damit wir die Sequenz sortieren können, muss als eine Ordnung auf den Elementen existieren. Wie kann man eine Ordnung auf Zahlen definieren? Einfach ob die Zahl grösser oder kleiner ist, klar, natürliche Ordnung! Bei Vektoren gibt es mehrere Möglichkeiten, man könnte z.B. die Länge eines Vektors als Kriterium für die Ordnung nehmen. Bei Namen kann man z.B. eine lexikographische Ordnung betrachten. Kapitel 5

9 Einfache Sortierverfahren
Selection Sort: nimm wiederholt das kleinste Element aus der Eingabesequenz, lösche es, und hänge es an das Ende der Ausgabesequenz an. Beispiel: <4,7,1,1> | <> ! <4,7,1> | <1> ! <4,7> | <1,1> ! <7> | <1,1,4> ! <> | <1,1,4,7> Also eine der ersten Ideen, wie man eine Sequenz sortieren kann ist selection sort: Man sucht immer das kleinste verbleibende Element, hängt es hinten an den Ausgabevektor und löscht es aus dem Eingabevektor. Z.B. hier: zuerst die „1“, ist das kleinste, dann nochmals „1“, etc. Kapitel 5

10 Einfache Sortierverfahren
Selection Sort: nimm wiederholt das kleinste Element aus der Eingabesequenz, lösche es, und hänge es an das Ende der Ausgabesequenz an. Zeitaufwand (worst case): Minimumsuche in Feld der Größe i: (i) Gesamtzeit: i=1..n (i) = (n2) Wie gross ist der Zeitaufwand für dieses Sortierverfahren? Wie lange dauert es das Minimum in einem Feld der Grösse i zu bestimmen? Linear durchlaufen im worst-case (ist ja nicht sortiert) => Laufzeit I Das n mal machen gibt n(n+1) / 2 = O(n^2) Das hinten anhängen an die Ausgabesequenz braucht hingegen nur konstante Zeit immer (pushback), können wir vernachlässigen. Kapitel 5

11 Selection Sort Procedure SelectionSort(a: Array [1..n] of Element) for i:=1 to n-1 do // bewege min{a[i],…,a[n]} nach a[i] for j:=i+1 to n do if a[i]>a[j] then a[i] $ a[j] Vorteil: sehr einfach zu implementieren (1 Array reicht!), also gut für kurze Sequenzen Nachteil: kann für lange Sequenzen lange dauern Wie kann man dieses selection sort nun programmieren in Java ähnlicher Notation? Idee: Das ganze funktioniert auch mit nur einem Array! Man kann das Array ganz durchlaufen, und an jeder Stelle jeweils immer swappen wenn ein noch kleineres gefunden auf der rechten Seite! Das ganze ist also ein 3 Zeiler im wörtlichen Sinne und deshalb auch praktisch. Der Nachteil ist die quadratische Laufzeit: Um ein Telefonbuch von München (1.3 Mio Einwohner?) zu sortieren, brüchte man also ca. 10^12 (= 1000 Milliarden) Rechenoperationen! Um ein Telefonbuch von China zu sortieren => ... Sortieren ist allgemein eine teure Opertionen, und sollte nicht allzu oft als Subroutine aufgerufen werden!! Kapitel 5

12 Selection Sort: Beispiel
j 5 10 19 1 3 14 j Element grösser => weiter Prinzip: Suche Minimum im Feld rechts von i und füge es bei i ein. (Ausgabesequenz = vorne am Array) Laufe ganzes Feld nach rechts durch, swappe mit i-Position wann immer ein neuer Rekord! Kapitel 5

13 Selection Sort: Beispiel
j 5 10 19 1 3 14 j Element grösser => weiter Kapitel 5

14 Selection Sort: Beispiel
j 5 10 19 1 3 14 j Element kleiner => swap! Kapitel 5

15 Selection Sort: Beispiel
j 1 10 19 5 3 14 j Element kleiner => swap! Kapitel 5

16 Selection Sort: Beispiel
j 1 10 19 5 3 14 j Element grösser => weiter! Kapitel 5

17 Selection Sort: Beispiel
j 1 10 19 5 3 14 j Element grösser => i++; j=i+1! Kapitel 5

18 Selection Sort: Beispiel
j 1 10 19 5 3 14 j Element grösser => weiter Kapitel 5

19 Selection Sort: Beispiel
j 1 10 19 5 3 14 j Element kleiner => swap! Kapitel 5

20 Selection Sort: Beispiel
j 1 5 19 10 3 14 j Element kleiner => swap! Kapitel 5

21 Selection Sort: Beispiel
j 1 5 19 10 3 14 j Element kleiner => swap! Kapitel 5

22 Selection Sort: Beispiel
j 1 3 19 10 5 14 j Element kleiner => swap! Kapitel 5

23 Selection Sort: Beispiel
j 1 3 19 10 5 14 j Element grösser => i++; j=i+1! Kapitel 5

24 Selection Sort: Beispiel
j 1 3 19 10 5 14 j Element kleiner => swap! Kapitel 5

25 Selection Sort: Beispiel
j 1 3 10 19 5 14 j Element kleiner => swap! Kapitel 5

26 Selection Sort: Beispiel
j 1 3 10 19 5 14 j Element kleiner => swap! Kapitel 5

27 Selection Sort: Beispiel
j 1 3 5 19 10 14 j Element kleiner => swap! Kapitel 5

28 Selection Sort: Beispiel
j 1 3 5 19 10 14 j Element grösser => i++; j=i+1! Kapitel 5

29 Selection Sort: Beispiel
j 1 3 5 19 10 14 j Element kleiner => swap! Kapitel 5

30 Selection Sort: Beispiel
j 1 3 5 10 19 14 j Element grösser => i++; j:=i+1! Kapitel 5

31 Selection Sort: Beispiel
j 1 3 5 10 19 14 j Element kleiner => swap! Kapitel 5

32 Selection Sort: Beispiel
j 1 3 5 10 14 19 Fertig!  Kapitel 5

33 Einfache Sortierverfahren
Insertion Sort: nimm Element für Element aus der Eingabesequenz und füge es in der richtigen Position der Ausgabe-sequenz ein Beispiel: <4,7,1,1> | <> ! <7,1,1> | <4> ! <1,1> | <4,7> ! <1> | <1,4,7> ! <> | <1,1,4,7> Wir lernen jetzt noch einen anderen Sortieralgorithmus kennen: Insertion Sort! Bei diesem Algo nehmen wir ein Element nach dem anderen in der Eingabesequenz (also nicht das Minimum oder so, sondern beliebiges das grad dort steht), und fügen es AN DIE RICHTIGE STELLE in der Ausgabesequenz. Hier z.B: zuerst das erste, das ist eine „4“, dann das zweite, die „7“, etc. Kapitel 5

34 Einfache Sortierverfahren
Insertion Sort: nimm Element für Element aus der Eingabesequenz und füge es in der richtigen Position der Ausgabe-sequenz ein Zeitaufwand (worst case): Einfügung an richtiger Stelle beim i-ten Element: O(i) (wegen Verschiebungen) Gesamtaufwand: i=1..n O(i) = O(n2) Wie gross ist die Laufzeit dieses Algorithmus? Das i-te Element RICHTIG einfügen bei der Ausgabesequenz bedeutet, dass wir zuerst richtige Position finden müssen (log i Zeit), und danach ev. i Element nach hinten schieben müssen => Laufzeit O(i). Wir summieren noch über alle Elemente der Eingabesequenz und kommen wieder auf n^2. BEACHTE: WIESO HIER NUN O(.) WÄHREND BEI SELECTION SORT THETA WAR? Wenn Element gross und man weit rechts einfügt, gibt‘s wenig Verschiebungen. Jede Operation geht also höchstens so lange. In Realität ist‘s aber wirklich tight über alles gesehen. Wieso O()? Kapitel 5

35 Insertion Sort Procedure InsertionSort(a: Array [1..n] of Element) for i:=2 to n do // bewege a[i] zum richtigen Platz for j:=i-1 downto 1 do if a[j]>a[j+1] then a[j] $ a[j+1] Vorteil: sehr einfach zu implementieren (1 Array genügt auch hier), also gut für kurze Sequenzen Nachteil: kann für lange Sequenzen lange dauern „runterblubbern“ Wie sieht das Code mässig aus? Wieder ist der Algo sehr einfach und eigentlich ein 3-Zeiler: wieder reicht auch nur 1 Array! Die Idee ist dass wir ein Element einfach runterplubbern lassen bis es an der richtigen Stelle ist, und dabei benachbarte Elemente vertauschen. BEACHTE: Nur O-Notation, nicht Theta: Weil vorne sortiert, muss nicht unbedingt ganzes durchgehen! Wenn ich wo nicht weiterkomme dann muss ich weiterlinks gar nicht schauen => blubbere nur soweit wie nötig! Nur im Worst Case! Beim Minimum Suchen beim Selection sort hingegen immer ganz durchgehen! Kapitel 5

36 Beispiel (Wikipedia...) 18.09.2018 Kapitel 5
Hier noch ein Beispiel das ich von Wkipedia runtergeladen habe: wir haben immer den ersten Teil (immer grösser) sortiert des Arrays, und fügen das pinke Element richtig ein unten durch „runterblubbern“. Kapitel 5

37 Insertion Sort: Beispiel
j i 5 10 19 1 3 14 j Element kleiner als j+1 Element => weiter Prinzip: Für immer grössere i, lasse i-tes Element „runterblubbern“, soweit bis Nachfolger kleiner => alles links von i ist sortiert! (Ausgabesequenz = vorne am Array) Laufe Feld nach links durch soweit wie nötig, swappe mit Nachbarposition wann immer ein neuer Rekord! Kapitel 5

38 Insertion Sort: Beispiel
j i 5 10 19 1 3 14 j Element kleiner als j+1 Element => weiter Kapitel 5

39 Insertion Sort: Beispiel
j i 5 10 19 1 3 14 j Element kleiner als j+1 Element => weiter Kapitel 5

40 Insertion Sort: Beispiel
j i 5 10 19 1 3 14 j Element grösser => swap Kapitel 5

41 Insertion Sort: Beispiel
j i 5 10 1 19 3 14 j Element grösser => swap Kapitel 5

42 Insertion Sort: Beispiel
j i 5 1 10 19 3 14 j Element grösser als (j+1) Element => swap Kapitel 5

43 Insertion Sort: Beispiel
j i 5 1 10 19 3 14 j Element grösser als (j+1) Element => swap Kapitel 5

44 Insertion Sort: Beispiel
j i 1 5 10 19 3 14 j Element grösser als (j+1) Element => swap Die 1 ist ganz „runtergeblubbert“! Kapitel 5

45 Insertion Sort: Beispiel
j i 1 5 10 19 3 14 j Element grösser als (j+1) Element => swap Kapitel 5

46 Insertion Sort: Beispiel
j i 1 5 10 3 19 14 j Element grösser als (j+1) Element => swap Kapitel 5

47 Insertion Sort: Beispiel
j i 1 5 3 10 19 14 j Element grösser als (j+1) Element => swap Kapitel 5

48 Insertion Sort: Beispiel
j i 1 3 5 10 19 14 j Element kleiner als (j+1) Element => weiter Die 3 „blubberte“ nur an zweitunterste Stelle. Kapitel 5

49 Insertion Sort: Beispiel
j 1 3 5 10 19 14 j Element grösser als (j+1) Element => swap Die 14 „blubbert“ nur eine Stelle weit. Kapitel 5

50 Insertion Sort: Beispiel
j i 1 3 5 10 14 19 Kapitel 5

51 Insertion Sort: Beispiel
j i 1 3 5 10 14 19 Rest ok, alles schon sortiert gegen links => fertig!  Kapitel 5

52 Einfache Sortierverfahren
Selection Sort: Mit besserer Minimumstrategie worst-case Laufzeit O(n log n) erreichbar (mehr dazu in Kapitel 6) Insertion Sort: Mit besserer Einfügestrategie (lass Lücken) worst-case Laufzeit O(n log2 n) erreichbar Wir haben also nun zwei einfach Sortierverfahren kennengelernt: Selection Sort und Insertion Sort. Beide hatten eine Laufzeit quadratisch in den Anzahl Elementen. Beide Algorithmen kann man im Prinzip noch verbessern (bessere Minimumstrategie bei Selection Sort oder Lücken lassen bei Insertion Sort), das reduziert die Laufzeiten noch wie folgt: <zeigen> Kapitel 5

53 Mergesort Idee: zerlege Sortierproblem rekursiv in Teil-probleme, die separat sortiert werden und dann verschmolzen werden 10 5 19 1 14 3 rekursiv Die Frage ist nun, ob‘s noch besser geht, d.h., ob man noch schneller sortieren kann irgendwie? Wir werden nun einen super schnellen Sortieralgorithmus kennenlernen, der auf dem „divide-and-conquer“ Prinzip beruht: das heisst, wir zerlegen das Sortierprobleme rekursiv in Teilprobleme, lösen die, und MERGEN dann die sortierten Teilsequenzen. Zum Beispiel hier: wir teilen das Array in der Mitte, sortieren die linke und die rechte Seite rekursiv, und mergen dann die beiden Teile zu einem ganz sortierten Array! 5 10 19 1 3 14 merge 1 3 5 10 14 19 Kapitel 5

54 Beispiel (Wikipedia) 18.09.2018 Kapitel 5
Hier noch ein Beispiel das ich auch auf Wikipedia gefunden habe: wir teilen das Array solange weiter in kleinere Probleme, bis nur noch einzelne Elemente da sind. Jene sind per Definition sortiert. Danach mergen wir die zusammen zu immer grösseren sortierten Arrays. Kapitel 5

55 Mergesort Procedure Mergesort(l,r: Integer) // a[l..r]: zu sortierendes Feld if l=r then return // fertig m:= b(r+l)/2c // Mitte Mergesort(l,m) Mergesort(m+1,r) j:=l; k:=m+1 for i:=1 to r-l+1 do // verwende Hilfsfeld b zum mergen if j>m then b[i]:=a[k]; k:=k else if k>r then b[i]:=a[j]; j:=j else if a[j]<a[k] then b[i]:=a[j]; j:=j else b[i]:=a[k]; k:=k+1 for i:=1 to r-l+1 do a[l-1+i]:=b[i] Erstes Feld schon leer! Zweites Feld schon leer! Wie sieht das Code mässig aus? Das ganze ist nun schon einwenig komplizierter! Wir haben eine Prozedur Mergesort, welche zwei Indizes „links“ und „rechts“ bekommt. Wenn das Array nur aus 1 Element besteht, machen wir nichts. Ansonsten bestimmen wir die Mitte des Arrays und rufen rekursiv die Prozedur für die beiden Teile auf. Danach verwenden wir ein Hilfsfeld b, in das wir jeweils falls eines der beiden Teile bereits leer, fügen wir restliche Elemente ein einfach der anderen Hälfte. Ansonsten fügen wir immer das kleinere der beiden Element in b ein. Am Schluss kopieren wir das Hilfsfeld zurück. Hässlich an Mergesort ist, dass man so ein Hilfsarray braucht. Das Kopieren macht es in der Praxis auch langsamer! Kapitel 5

56 Zwei sortierte Felder mergen
j k m 5 10 19 1 3 14 merge Kapitel 5

57 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 Kapitel 5

58 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 Kapitel 5

59 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 Kapitel 5

60 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 Kapitel 5

61 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 Kapitel 5

62 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 Kapitel 5

63 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 10 Kapitel 5

64 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 10 Kapitel 5

65 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 10 14 Kapitel 5

66 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 10 14 Kapitel 5

67 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 10 14 19 Kapitel 5

68 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge 1 3 5 10 14 19 Kapitel 5

69 Mergesort Theorem 5.2: Mergesort benötigt O(n log n) Zeit, um eine Folge der Länge n zu sortieren. Beweis: T(n): Laufzeit bei Folgenlänge n T(1) = (1) und T(n) = T(bn/2c) + T(dn/2e) + (n) aus Übungsaufgabe: T(n)=O(n log n) Wie gross ist die Laufzeit von Mergesort? Man kann beweisen dass das in n log n Zeit geht! Und zwar haben wir folgende rekursive Form für die Laufzeit: (Rekursion über Feldgrösse) Ein Feld mit nur einem Element ist in konstanter Zeit sortiert Ein Feld mit n Elementen: wir rufen Mergesort zweimal mit halber Grösse auf (DIVIDE), und dann mergen wir die beiden Sachen, das geht in linearer Zeit (an jeder Stelle machen wir einen Vergleich und dann wird ein Index weitergerückt). Daraus folgt einfach: die Laufzeit ist n log n Kapitel 5

70 Untere Schranke Ziel einer unteren Laufzeitschranke t(n): Zeige, dass kein Algorithmus (aus einer gewissen Klasse) eine bessere Laufzeit als O(t(n)) im worst case haben kann. Methodik: nutze strukturelle Eigenschaften des Problems Beispiel: vergleichsbasiertes Sortieren Das Ziel beim Algorithmenentwurf ist immer, einen möglichst schnellen Algo zu finden. Ein wichtiges Konzept, damit man da nicht allzu lange vergeben sucht sind Lower Bounds: Eine Lower Bound sagt, dass es keinen Algorithmus geben kann der schneller ist als diese lower bound. Eine gute Lower Bound zu finden ist nicht immer einfach! Tatsächlich gibt es wichtige Probleme heute noch (z.B. die Multiplikation), bei denen man noch keinen Algorithmus kennt der so gut ist wie die untere Schranke. Um eine untere Schranke zu beweisen im Worst-Case, muss man gewisse Annahmen treffen was ein Algorithmus darf. Allgemein nennen wir die Laufzeit eines Algorithmus eine „obere Schranke“ für ein Problem: wir wissen zum Beispiel, dass sortieren höchsten O(n^2) Zeit braucht, weil unser Insertion Algo ist so gut. Aber wie lange braucht ein Algorithmus mindestens zum sortieren im Worst-Case? Wir werden nun eine Lower Bound herleiten für vergleichsbasiertes Sortieren. BEMERKUNG: Ein vergleichsbasierter Algo darf Elemente nur vergleichen! Aber zum Beispiel nicht die Bitrepräsentation z.B. ausnutzen oder so! (=> Radixsort geht dann z.B. unter Umständen besser.) Kapitel 5

71 Untere Schranke Vergleichsbasiertes Sortieren: Wie oft muss jeder vergleichsbasierte Algo im worst case einen Vergleich e<e´ machen? Frage: Gegeben sei beliebige Menge S von n Elementen. Wieviele Möglichkeiten gibt es, S als Folge darzustellen? Antwort: n! = n¢(n-1)¢(n-2)…2¢1 viele Um eine untere Schranke für vergleichsbasiertes Sortieren herzuleiten, betrachten wir Algos die auf Vergleichen beruhen! D.h., wieviele Vergleiche muss jeder Algo mindestens machen um ein Feld zu sortieren in jedem Fall? Um eine untere Schranke herzuleiten, können wir uns überlegen, wieviele Möglichkeiten eine Folge darzustellen es gibt: n Elemente können wir auf n! viele Arten anordnen. Kapitel 5

72 Untere Schranke Permutation  der Eingabefolge: Menge S: Eingabefolge:
1 3 5 10 14 19 Ein Sortieralgorithmus muss fähig sein, aus jeder beliebigen Anfangssequenz ein sortiertes Feld zu erstellen, d.h. die entsprechende Permutation vorzunehmen! 10 5 19 1 14 3 Kapitel 5

73 Untere Schranke Wenn der Algorithmus sortieren kann, kann er auch die Permutation  ausgeben (u.u.). 1 3 5 10 14 19 D.h. jeder Sortieralgorithmus kann jede Permutation der Eingabesequenz erzeugen. 10 5 19 1 14 3 Kapitel 5

74 Untere Schranke Beliebiger vergleichsbasierter Algo als Entscheidungsbaum: = Menge aller Permutationen e1<e2?  = 1 [ 2 Zeit ja nein 1 2 Die Schwierigkeit beim Beweisen von unteren Schranken ist es, dass man über alle möglichen Algorithmen argumentieren muss die Vergleiche anwenden! Die Idee für die untere Schranke ist nun, dass wir jeden beliebigen Algorithmus der ein Array der Grösse n sortieren kann als Entscheidungsbaum darstellen können. Bei diesem Baum läuft die Zeit von oben nach unten, und jeder Knote im Baum entspricht einem Vergleich von zwei Elementen. Schlussendlich muss jeder Sortieralgorithmus auch die Permutation ausgeben können, die vom unsortierten zum sortierten Feld führt. Zu Beginn, an der Wurzel des Baumes, sind noch alle Permutationen möglich. Bei jedem inneren Knoten wird die Anzahl möglicher Permutationen in zwei kleinere mögliche Permutationsmengen aufgeteilt, welche sich unterscheiden ob e1 nun grösser als e2 ist oder nicht. FRAGE: Sine diese Mengen disjunkt? Ja! Kann eine der beiden leer sein? Ja, dann ist der Vergleich aber redundant (dann wurden vorher schon die Permutationsmengen durch einen solchen Vergleich getrennt)! Das heisst, mit jedem Vergleich kommen nur noch die Permutationen in Frage, die die beiden Elemente gemäss diesem Vergleichresultat anordnen. Beispiel: Algo für sortieren von 3 Zahlen 1,2,3: Mengen {(1,2,3)(1,3,2)(2,1,3)(2,3,1)(3,1,2)(3,2,1)}. Algo der Position 1 mit Position 2 vergleicht nachher Sets: {(1,2,3)(1,3,2)(2,3,1)} und {(2,1,3)(3,1,2)(3,2,1)}. e3<e4? e5<e6? i : Permutationsmenge, die Bedingungen bis dahin erfüllt Kapitel 5

75 Untere Schranke Beliebiger vergleichsbasierter Algo als Entscheidungsbaum: 1 e1<e2? 1 = 2 [ 3 Zeit ja nein 2 3 Die Menge der Permutationen die das noch erfüllen wird immer kleiner. e3<e4? e5<e6? i : Permutationsmenge, die Bedingungen bis dahin erfüllt Kapitel 5

76 Untere Schranke Beliebiger vergleichsbasierter Algo als Entscheidungsbaum:  = {p1, p2} e1<e2? Zeit ja nein p1 p2 Der Algorithmus ist fertig, wenn die Permutation eindeutig ist: Dann ist man beim Blatt angelangt im Entscheidungsbaum. Das heisst: Jeder deterministische Sortieralgorithmus wird in einer anderen Reihenfolge die Vergleiche machen während der Ausführung, man kann die Abarbeitung der möglichen Permutationen immer so darstellen. 1 2 pi : eindeutige Permutation der Eingabefolge Kapitel 5

77 Untere Schranke Beliebiger vergleichsbasierter Algo als Entscheidungsbaum:  = {p1, p2} e1<e2? Zeit ja nein p1 p2 Wieviele Blätter muss der Entscheidungsbaum haben? 1 2 Wieviele Blätter muss Entscheidungsbaum haben? Kapitel 5

78 Untere Schranke Beliebiger vergleichsbasierter Algo als Entscheidungsbaum:  = {p1, p2} e1<e2? Zeit ja nein p1 p2 Soviele wie es Permutationen gibt! Jedes Blatt entspricht einer Permutation! Es gibt n! Permutationen, also n! Blätter! Wie folgt nun daraus die Laufzeit? Jede Instanz / Inputsequenz führt zu einem anderen Pfad in diesem Baum! (D.h., der deterministische Algorithmus gibt die Struktur des Entscheidungsbaums vor, welcher Pfad gewählt wird hängt von der Inputsequenz ab!) 1 2 Mindestens n! viele Blätter! Kapitel 5

79 Untere Schranke Beliebiger vergleichsbasierter Algo als Entscheidungsbaum: Baum der Tiefe T: Höchstens 2T Blätter 2T >= n! , T >= log(n!) = (n log n) Zeit Wie folgt nun daraus die Laufzeit? Jede Instanz / Inputsequenz führt zu einem anderen Pfad in diesem Baum! (D.h., der deterministische Algorithmus gibt die Struktur des Entscheidungsbaums vor, welcher Pfad gewählt wird hängt von der Inputsequenz ab!) Ein Baum der Tiefe T kann aber höchstens 2^T Blätter haben. Daraus folgt dass die Tiefe des Baumes – und somit die Laufzeit im Worst-Case: falls man nämlich bei dieser Frequenz soweit kommt – n log n Vergleiche braucht! => Das heisst, jeder vergleichsbasierte Algorithmus has mindestens n log n Vergleiche nötig im worst-case! e.g., O() folgt aus n! ¸ (n/e)n Jeder vergleichsbasierte Algo hat (n log n) Laufzeit Kapitel 5

80 Beispiel Ein Algo für Arrays der Grösse 3:
{(123),(132),(213),(231),(312),(321)} Position 1 < Position 2 Position 2 < Position 1 {(123),(132),(231)} {(213), (312),(321)} p2 < p3? { (132),(231)} ... {(123)} p1 < p3? Um (132) zu sortieren braucht dieser Algo 3 Vergleiche! [log(3!) = ] { (132)} {(231)} Kapitel 5

81 Quicksort Idee: ähnlich wie Mergesort, aber Aufspal-tung in Teilfolgen nicht in Mitte sondern nach speziellem Pivotelement unsortiert! 10 5 19 1 14 3 ordne nach <10 und >10 5 1 3 10 19 14 Nachdem wir nun wissen, was wir höchstens erwarten können von einem Sortieralgo, schauen wir noch einer der berühmtesten Algorithmen an, welcher auch in der Praxis oft verwendet wird! => Quicksort! Eigentlich ist er ähnlich wie Mergesort. Die Idee ist dass wir ein Pivotelement (ein spezielles Element) wählen, und dann alle Elemente die kleiner sind (unsortiert) in die Arrayfelder links tun und alle die grösser sind in die Arrayfelder rechts! Danach machen wir die beiden Hälften rekursiv genauso! Anders als bei Mergesort wo wir beim rekursiv runtergehen erst mal gar nichts machen, machen wir hier diese Aufteilung auf die beiden Seiten. Man ist also hier „ganz unten in der Rekursion“ fertig! Quicksort hat schlechtere Laufzeit im Worst-Case als Mergesort wie wir sehen werden, aber in der Praxis ist es schneller. Insbesondere vermeiden wir hier unnötige Kopieroperationen, da wir kein zweites Array brauchen! rekursiv 1 3 5 10 14 19 Kapitel 5

82 Quicksort Procedure Quicksort(l,r: Integer) // a[l..r]: zu sortierendes Feld if r>l then v:=a[r]; i:=l-1; j:=r repeat // ordne Elemente nach Pivot v repeat i:=i+1 until a[i]>=v repeat j:=j-1 until a[j]<=v if i<j then a[i] $ a[j] until j<=i a[i] $ a[r] Quicksort(l,i-1) // sortiere linke Teilfolge Quicksort(i+1,r) // sortiere rechte Teilfolge Der Code sieht so aus: wir wählen als Pivotelement (gemäss dem wir die Hälften machen) ganz rechts. Dann gehen wir von links nach rechts bis wir einen Wert grösser als v (Pivot) gefunden haben. Danach gehen wir von rechts nach links bis wir einen Wert kleiner gleich v gefunden haben, dann swappen wir die beiden. i bedeutet Invariante dass links davon nie ein Element grösser als v, j ist Invariante dass rechts nie davon kleiner. Das machen wir solange bis j <= i. Dann sortieren wir rekursiv. Kapitel 5

83 Quicksort: Quicksort i: gehe solange nach links bis grösser als Pivot
j: gehe solange nach rechts bis kleiner als Pivot => dann mache swap! Hier ein Beispiel für Quicksort: wir beginnen mit Index i links und Index j rechts, und gehen solange weiter bis i bei einem grösser als Pivos und j bei einem kleiner als Pivot, dann machen wir Swap, etc. Bis i>j => dann rekrusiv. Kapitel 5 Jetzt rekursiv beide Hälften mit QS!

84 Quicksort: Beispiel j i Pivot! 5 10 19 1 3 14
i++, dann i: gehe solange nach rechts bis >= Pivot j--, dann j: gehe solange nach links bis <= Pivot Kapitel 5

85 Quicksort: Beispiel j i Pivot! 5 10 19 1 3 14
i<j => ok => swap! Kapitel 5

86 Quicksort: Beispiel j i Pivot! 5 10 3 1 19 14
i++ solange bis >= Pivot j-- solange bis <= Pivot Kapitel 5

87 Quicksort: Beispiel j i Pivot! 5 10 3 1 19 14
i<j : falsch => kein swap mehr! Nun noch Pivos an Position i swappen! Kapitel 5

88 Quicksort: Beispiel Pivot ist an richtiger sortierter Position!
Wird nicht mehr ändern! 5 10 3 1 14 19 Sortiere rekursiv! Sortiere rekursiv! Kapitel 5

89 Quicksort Problem: im worst case kann Quicksort (n2) Laufzeit haben (wenn schon sortiert) Lösungen: wähle zufälliges Pivotelement (Laufzeit O(n log n) mit hoher W.keit) berechne Median (Element in Mitte) ! dafür Selektionsalgorithmus (Kap 5.5) Was ist die Laufzeit von Quicksort? Sieht jemand eine schlechte Sequenz? Wenn das Array bereits sortiert ist, ist die Laufzeit sehr schlecht! Die Laufzeit ist quadratisch wenn wir in jedem Step eine grosse „Hälfte“ für QS Rekursion haben, d.h., wenn Pivotelement die Elemente nicht schön in zwei gleich grosse Teile teilt! Wenn Pivot immer das grösste Element (ganz rechts gewählt: wenn sortiert immer max), wird eine Hälfte immer n-1 gross sein, also haben wir ungefähr n Rekursionstiefen und jede Rekursion kostet n viel Zeit (muss mit i ganz nach rechts laufen). Man kann zeigen, dass Quicksort aber recht gut ist, wenn man ein zufälliges Element als Pivot nimmt (resp. im Average Case): mehr als n log n kann man nicht erwarten, und das ist ist ja bekanntlich optimal. Auch wenn man sonst das Pivot intelligent wählt, kann man effizienter sein, z.B. wenn man das Pivot „in der Mitte wählt“, das heisst als Median der links und recht gleichviele Elemente hat. Kapitel 5

90 Quicksort: Worst-Case
Pivot 1 1 3 5 10 14 19 Sortiere rekursiv! Kapitel 5

91 Quicksort: Worst-Case
Pivot 2 1 3 5 10 14 19 Sortiere rekursiv! Kapitel 5

92 Quicksort: Worst-Case
Pivot 3 1 3 5 10 14 19 Sortiere rekursiv! Kapitel 5

93 Quicksort: Worst-Case
Pivot 3 1 3 5 10 14 19 Insgesamt n Rekursionen, jede Rekursion erfordert I (für i=1..n) Vergleiche mit Pivotelement => O(n2) Kapitel 5

94 Quicksort Laufzeit bei zufälligem Pivot-Element:
Zähle Anzahl Vergleiche (Rest macht nur konstanten Faktor aus) C(n): erwartete Anzahl Vergleiche bei n Elementen Theorem 5.6: C(n) <= 2n ln n <= 1.4 n log n Wir wollen nun die Laufzeit von Quicksort bei zufälligem Pivot formal analysieren. Die Hauptoperation / das Hauptkriterium sollen die Anzahl Vergleiche sein. Konkret wollen wir die ERWARTETE Anzahl Vergleiche berechnen (also der Erwartungswert). Wir können das folgende Theorem zu beweisen. NEBENBEMERKUNG: QUICKSORT IST AUCH GUT ZUM PARALLELISIEREN! Kapitel 5

95 Beweis von Theorem 5.6 s=<e1,…,en>: sortierte Sequenz
Quicksort: nur Vergleiche mit Pivotelement, Pivotelement nicht im rekursivem Aufruf ei und ej werden <=1-mal verglichen und nur, wenn ei oder ej Pivotelement ist 10 5 19 1 14 3 ordne nach <10 und >10 5 1 3 10 19 14 Bei Quicksort können wir jedes Element mit Pivotelement vergleichen und dann auf restlichen zwei Teilen OHNE Pivotelement rekursiv QS aufrufen. => Zwei Elemente werden höchstens einmal verglichen, nämlich wenn eines der beiden Pivotelement ist. Kapitel 5

96 Beweis von Theorem 5.6 Zufallsvariable Xi,j 2 {0,1}
Xi,j = 1 , ei und ej werden verglichen C(n) = E[i<j Xi,j] = i<j E[Xi,j] = i<j Pr[Xi,j = 1] Lemma 5.7: Pr[Xi,j] = 2/(j-i+1) Wir können die folgende Indikatorzufallsvariable definieren. X_ij bedeutet dass Element i und j miteinander verglichen werden, wobei e_i und e_j die Indizes der Elemente in der sortierten Reihenfolge sind. Die erwartete Laufzeit von Quicksort ist die Summe über alle Indikatorvariablen (i<j damit wir‘s nicht doppelt zählen). Wegen der Unabh.keit des Erwartungswertes können wir das so umschreiben, und wegen der Definition des Erwartungswertes und weil die ZV eine Indikatorvariable ist, ergibt sich die Summe über die Wahrscheinlichkeiten. Wir zeigen nun, dass die Wahrscheinlichkeit dass zwei Elemente miteinander verglichen werden gleich 2 / j-i+1 ist. Also je weiter die Elemente in der sortierten Reihenfolge auseinander desto unwahrscheinlicher. Kapitel 5

97 Beweis von Theorem 5.6 Lemma 5.7: Pr[Xi,j] = 2/(j-i+1) Beweis:
M={ei,…,ej} Irgendwann wird Element aus M als Pivot ausgewählt (bis dahin bleibt M zusammen) ei und ej werden nur verglichen, wenn ei oder ej als Pivot ausgewählt werden Pr[ei oder ej in M ausgewählt]=2/|M| Betrachten wir die Menge der Elemente von e_i bis e_j. Solange es kein Pivot aus dieser Menge gibt, bleiben alle Elemente dieser Menge immer in der gleichen Hälfte des Arrays! D.h., solange es kein Pivotelement gibt bleiben die Elemente aus M zusammen. Klar? Pivot teilt ja die Mengen in die zwei Hälften, also sind sie immer auf gleicher Seite des Pivots! Wir gehen ja von der _sortierten_ Elementmenge aus. Wenn e_i oder e_j Pivot ist, werden sie miteinander verglichen. Dass bei dieser Menge genau eines dieser beiden Elemente Pivot wird, hat WSK 2 / M. Wenn ein anderes Element Pivot ist, werden sie nicht verglichen. Also ist die WSK 2 / M. Kapitel 5

98 Beweis von Theorem 5.6 C(n) = i<j Pr[Xi,j=1] = i<j 2/(j-i+1)
= i=1n k=2n-i+1 2/k <= i=1n k=2n 2/k = 2n k=2n 1/k <= 2n ln n Erwartungsgemäß ist Quicksort also sehr effizient (bestätigt Praxis). Der Erwartungswert kann nun so geschrieben werden. Diese Summe lässt sich so umformulieren. (wir führen einfach eine Variable k ein für die Distanz zwischen zwei Elmenten und summieren zuerst noch über alle i unten). Das ist dann kleinergleich dem (Subtraktion weglassen). Die Summe 1/k ist dann gerade die Eulersche Zahl / die harmonische Zahl, ist kleiner als der natürliche Logarithmus von n. Daraus folgt also, dass die erwartete Laufzeit höchstens n log n ist mit kleiner Konstante davor. Kapitel 5

99 Harmonic Number Es gilt: Hn · ln n + 1 18.09.2018 Kapitel 5
Hier noch eine Bemerkung zur harmonischen Zahl. Asymptotisch ist sie etwa log n. Kapitel 5

100 Geht das auch schneller??
Selektion Problem: finde k-kleinstes Element in einer Folge von n Elementen Lösung: sortiere Elemente, gib k-tes Ele-ment aus ! Zeit O(n log n) Geht das auch schneller?? Wir machen nun einen kleinen Exkurs zu einem anderen wichtigen algorithmischen Problem: der Selektion. Ziel der Selektion ist es, das k-kleinste Element in einer Folge von n Elementen zu finden. Wie könnte man das effizient machen? Eine Möglichkeit wäre, das Feld zuerst zu sortieren, und dann das k kleinste Element einfach zu nehmen => Laufzeit sortieren wäre n log n, das k kleinste kann man dann einfach durch durchlaufen noch finden, also Laufzeit O(n log n). Geht das besser? Kapitel 5

101 Selektion Ansatz: verfahre ähnlich zu Quicksort
j: Position des Pivotelements k<j: mach mit linker Teilfolge weiter k>j: mach mit rechter Teilfolge weiter 10 5 19 1 14 3 5 1 3 10 19 14 Das Selektionsproblem kann man auch mit einer Quicksort-ähnlichen Idee lösen! Die Idee ist, wieder ein Pivot zu wählen und alles kleinere links davon und alles grössere rechts davon anzuordnen. Nun wissen wir: Das Pivotelement ist jetzt an der richtigen Position bereits im sortierten Array! Wird seine Position nicht mehr ändern!! Wir können nun gucken, wie gross die beiden Hälften sind! Wenn wir das k-t kleinste Element suchen und die Menge links vom Pivot grösser ist als k, machen wir mit der Hälfte weiter, sonst mit der anderen! Beachte: Die andere Hälfte ist uns also egal! Wir müssen die nicht mehr sortieren, es reicht zu wissen wieiviele Elemente drin sind! Nur in der einen Hälfte müssen wir dann noch genauer schauen und das rekursiv anwenden. Kapitel 5

102 Selektion Function Quickselect(l,r,k: Integer): Element // a[l..r]: Restfeld, k: k-kleinstes Element if r=l then return a[l] z:=zufällige Position in {l,..,r}; a[z] $ a[r] v:=a[r]; i:=l-1; j:=r repeat // ordne Elemente nach Pivot v repeat i:=i+1 until a[i]>=v repeat j:=j-1 until a[j]<=v if i<j then a[i] $ a[j] until j<=i a[i] $ a[r] if k<i then e:=Quickselect(l,i-1,k) if k>i then e:=Quickselect(i+1,r,k) if k=i then e:=a[k] return e Wie sieht das Code mässig nun aus? Falls das Array nur noch aus 1 Element besteht (r=l), sind wir fertig: das muss das k-kleinste sein! Sonst wir wählen aus dem Array eine zufällige Position als Pivot und tun das Pivot ganz nach rechts (damit können wir das mit den nach links und rechts wandernden Pointern i und j machen). Wir tun wieder alles links und rechts hin des Pivots. TODO: Wieso swap hier nach until? Ah, weil wir erst dann das i-te an richtige Position tun! Vorher wars immer ganz rechts zum Vergleichen (wird zuerst r– gerechnet bei repeat: also nicht geändert). Danach machen wir Falunterscheidung mit welcher Hälfte des Feldes wir rekursiv weitermachen müssen. Falls k<i: wir können nur mit der linken Hälfte mitmachen, da hats noch genug Platz. Falls k>i: nur mit der rechten, das k-kleinste muss irgendwo dort sein. Falls wir per Zufall gerade k=i haben, sind wir fertig: diese Position wird sich nicht mehr verändern! Kapitel 5

103 Quickselect C(n): erwartete Anzahl Vergleiche Theorem 5.8: C(n)=O(n)
Beweis: Pivot ist gut: keine der Teilfolgen länger als 2n/3 Sei p=Pr[Pivot ist gut] p=1/3 Was haben wir nun für eine Laufzeit für Quickselect, für diese Selektion wo wir immer mit der einen Array Hälfte weitermachen? Wir können zeigen dass die Laufzeit linear ist! Das heisst, Selektion geht echt schneller als sortieren! Brauche weniger Vergleiche!! Im folgenden nennen wir ein Pivot GUT, falls keine der beiden Hälften nach dem Aufteilen länger als 2n/3 ist. (=> d.h., andere Hälfte mind. n/3) Das heisst, Pivot ist genau dann gut wenn es im mittleren Drittel gewählt wird des Arrays. Wie gross ist diese WSK? 1/3, klar. Nennen wir das p. gut 1/3 2/3 Kapitel 5

104 Quickselect Pivot gut: Restaufwand <= C(2n/3)
Pivot schlecht: Restaufwand <= C(n) C(n) <= n + p C(2n/3) + (1-p) C(n) , C(n) <= n/p + C(2n/3) <= 3n + C(2n/3) <= 3(n+2n/3+4n/9+…) <= 3n i>=0 (2/3)i <= 3n / (1-2/3) = 9n Wenn das Pivot gut ist, ist die „Hälfte“ die wir rekursiv genauer anschauen müssen höchstens noch 2n/3 so lange. Wenn das Pivot schlecht ist, kann es aber noch linear lange gehen (nochmals gleichlange, fast nichts gewonnen ausser das eine Pivot! Also maximal noch n-1 gross.) Für die Laufzeit haben wir also: Wir müssen zuerst n Vergleiche machen: alle Elemente entscheiden ob sie links oder rechts vom Pivot hingehören. Dann geht‘s rekursiv weiter mit einem „kleinen Teil“ falls das Pivot gut, sonst mit einem grossen Teil. Wenn wir hier das C(n) auf die linke seite nehmen, ergibt sich das. (zweite Gleichung) Wir wissen dass p=1/3, und durch Teleskopieren erhalten wir das (d.h., wir setzen nun das rekursiv ein). Wir kriegen folgende geometrische Summe, welche sich noch vereinfachen lässt. Kapitel 5

105 Soweit gekommen am Dienstag. Start hier am Donnerstag!
Kapitel 5

106 Ein paar Infos vorweg... Morgen Freitag: 15:00-16:30 Fragestunde von Prof. Scheideler im FMI HS 2 (Fragen zur Vorlesung bis jetzt) Heute: - kurze Repetition von Quicksort - Noch schnellere Sortieralgorithmen! - Externes sortieren (was wenn nicht genug Speicher vorhanden für ganzes Feld?) - Ev. noch was zur Analyse von While Schleifen Kapitel 5

107 Repetition (1) Wie kann man etwas effizient sortieren?
Gesehen: „Minimum-Selektion “-Algo O(n2), „Einfüge“-Algo O(n2), Algo durch „rekursives Mergen“ O(n log n), Quicksort Algo O(n2) Wieso ist Mergesort immer O(n log n) während Quicksort zum Teil O(n2) ist? Bei Quicksort können die beiden rekursiv zu bearbeitenden Teile unterschiedlich gross sein... Kapitel 5

108 Repetition (2) Weshalb ist es schlecht, wenn die Teile unterschiedlich gross sind? (Weshalb wird Quicksort dennoch verwendet in der Praxis?) Quicksort = kein extra Array nötig fürs Mergen! Im Worst-Case schlechter, am im Average-Case effizienter. Kapitel 5

109 Repetition (3) Rekursion: Wie wird das z.B. ausgeführt?
Procedure Quicksort(l,r: Integer) // a[l..r]: zu sortierendes Feld if r>l then v:=a[r]; i:=l-1; j:=r repeat // ordne Elemente nach Pivot v repeat i:=i+1 until a[i]>=v repeat j:=j-1 until a[j]<=v if i<j then a[i] $ a[j] until j<=i a[i] $ a[r] Quicksort(l,i-1) // sortiere linke Teilfolge Quicksort(i+1,r) // sortiere rechte Teilfolge Kapitel 5

110 Repetition (4) j i Pivot! 5 10 19 1 3 14 i++, dann i: gehe solange nach rechts bis >= Pivot j--, dann j: gehe solange nach links bis <= Pivot Kapitel 5

111 Repetition (4) j i Pivot! 5 10 19 1 3 14 i<j => ok => swap!
Kapitel 5

112 Repetition (4) j i Pivot! 5 10 3 1 19 14 i++ solange bis >= Pivot
j-- solange bis <= Pivot Kapitel 5

113 Repetition (4) j i Pivot! 5 10 3 1 19 14 i<j : falsch => kein swap mehr! Nun noch Pivos an Position i swappen! Kapitel 5

114 Repetition (4) Pivot ist an richtiger sortierter Position!
Wird nicht mehr ändern! 5 10 3 1 14 19 Sortiere rekursiv! Sortiere rekursiv! Kapitel 5

115 Veranschaulichung (1) Falls Pivot immer in der Mitte:
O(log (n)) viele Stufen ... Wieviele Vergleiche sind nötig pro Stufe? Kapitel 5

116 Veranschaulichung (2) Tiefe i: 2^i Subarrays der Grösse n/2^i.
Falls Pivot immer in der Mitte: n-1 Vergleiche mit Pivot ~ 2 * n/2 Vergleiche mit Pivots ~ 4 * n/4 Vergleiche mit Pivots ... Tiefe i: 2^i Subarrays der Grösse n/2^i. Bereits fixierte Pivots: ^i. Kapitel 5

117 Veranschaulichung (2) Falls Pivot immer am Rand landet:
n-1 Vergleiche mit Pivot n-2 Vergleiche mit Pivot n-3 Vergleiche mit Pivot ... Auch auf jeder Stufe ca. n Vergleiche! Was ist der Unterschied?? Kapitel 5

118 Veranschaulichung (3) Tiefe i: 1 Subarray der Grösse n-i.
Falls Pivot immer am Rand landet: O(n) Stufen -- nur ein neues fixes Element pro Stufe! ... Tiefe i: 1 Subarray der Grösse n-i. Bereits fixierte Pivots: i. Kapitel 5

119 Repetition (5) Wieviele Vergleiche braucht ein Algorithmus im Jahr 2050 mindestens um ein Feld der Grösse n zu sortieren? Konzept der „unteren Schranke“ oder „lower bound“ Lösung: mind. log(n!) Kapitel 5

120 Repetition (6) Kann man nicht schneller sortieren?
Doch – falls Algorithmus nicht vergleichsbasiert ist oder das Problem / der Input eine spezielle Form hat! Z.B.: Algorithmus der nicht nur Keys als ganzes miteinander vergleicht, sondern die Repräsentation als einzelne Ziffern berücksichtigt! Oder: Felder die nur aus ganz wenigen sich wiederholenden Keys bestehen. Kapitel 5

121 Sortieren schneller als O(n log n)
Annahme: n Elemente im Bereich {0,…,K-1} Strategie: verwende Feld von K Listenzeigern 3 1 3 2 4 3 4 2 Beispiel mit n = 9, K=5 1 2 3 4 Wir haben bewiesen, dass sortieren mindestens n log n Vergleiche braucht, und Algorithmen vorgestellt die das auch erreichen. Wir werden nun Algorithmen vorstellen, die asymptotisch schneller sind als n log n! Wie kann das sein? Das kann dann der Fall sein, wenn wir nicht allgemeine Inputsequenzen haben, sondern „einfachere“ / speziellere! Wenn wir zum Beispiel eine Inputsequenz haben, bei der sehr viele Elemente mehrmals vorkommen, geht sortieren schneller. Nehmen wir z.B. ein Array der Länge n welches nur aus K Elementen zw. 0 und K-1 besteht. Wir können dann so vorgehen, dass wir ein Feld der Grösse k machen und dort jeweils die Elemente einhängen des entsprechenden Wertes (mit Pointern). Kapitel 5

122 Sortieren schneller als O(n log n)
Procedure KSort(s: Sequence of Element) b: Array [0..K-1] of Sequence of Element foreach e2s do b[key(e)].pushBack(e) s:=concatenation of b[0],…,b[K-1] Laufzeit: O(n+K) Problem: nur gut für K=o(n log n) const time (e.g., linked list), hinten anhängen (Stabilität!) Buckets Also einwenig konkreter: Wir gehen durch die ganze Inputsequenz s durch, und fügen jedes Element e von s an der entsprechenden Stelle ein. (Wichtig: wir machen pushback, dadurch bleibt Reihenfolge von Elementen mit gleicher Zahl gleich wie im Input! => Stabilität) Die Ausgabe ist dann das Sequenz dieser Vektoren. Was ist die Laufzeit? Wir gehen duch alle Elemente von s durch => kostet n (pushback geht jeweils in konstanter Zeit) Danach fügen wir noch die Elemente zusammen, d.h. wir „konkatenieren“ (verketten) alle non-empty Buckets => kostet k (falls k >> n müssen wir viele leere Buckets Durchlaufen am Schluss um die Schlussordnung zu erstellen, deshalb hier noch +K) Das ist also schneller als sortieren wenn der Wertebereich K der Elemente asymptotisch kleiner ist als n log n. Kapitel 5

123 K-Sort Example Erste Ziffer = Schlüssel Schlüsselraum {0,1,2,3} s = (<3,a>, <1,b>, <2,c>, <3,d>, <0,e>, <0,f>, <3,g>, <2,h>, <1,i>) wird zu: b = ([<0,e>,<0,f>], [<1,b>,<1,i>], [<2,c>,<2,h>], [<3,a>,<3,d>,<3,g>]) also (<0,e>,<0,f>, <1,b>,<1,i>, <2,c>,<2,h>,<3,a>,<3,d>,<3,g>) Beachte: K-Sort ist stabil, d.h., Elemente mit gleichem Key bleiben in gleicher Reihenfolge wie in der Eingabe (wegen pushBack)! ... Hier noch ein Beispiel aus dem Buch. Wir machen die Buckets Beachte, dass k-Sort stabil ist, d.h. Elemente mit gleichem Key bleiben in gleicher Reihenfolge wie im Input. Kapitel 5

124 Radixsort Ideen: Benutze Repräsentation der Schlüssel
K-adische Darstellung der Schlüssel Sortiere Ziffer für Ziffer gemäß KSort Behalte Ordnung der Teillisten bei Als letztes betrachten wir noch ein spezielles Sortierverfahren, das Radixsort genannt wird. Radixsort benutzt ein k Sort für jede Stelle. Konkret z.B. beim Zehnersystem (k=10) sortieren wir die Zahlen Ziffern weise, und behalten dabei die Ordnung der bereits erzeugten Teillisten bei. Wir benutzen also die Stabilitätseigenschaft von k-Sort (dass Elemente mit gleichem Key in gleicher Reihenfolge bleiben wie beim Input). Kapitel 5

125 Radixsort Procedure Radixsort(s: Sequence of Element) for i:=0 to d-1 do KSort(s,i) // sortiere gemäß keyi(x) // mit keyi(x) = (key(x) div Ki) mod K, // d.h. kleinste Ziffer zuerst! Laufzeit: O(d(n+K)) Falls maximale Zahlengröße O(log n), dann alle Zahlen <= nd für konstantes d. In diesem Fall Laufzeit für n Zahlen sortieren O(n). Bei d-ären Zahlen geht es also so: wir sortieren nach immer grösseren Ziffern mit KSort und behalten Teilordnungen bei. Wieso ist das korrekt? Induktion über sortierte Kolonne! Was ist die Laufzeit von diesem Algorithmus? Wir haben d Aufrufe von KSort, also d mal (n+K). Falls d konstant ist (konstante Anzahl Stellen) und K in O(n) ist (Alphabet höchstens so gross wie Sequenzlänge), ist Laufzeit in O(n). Oder anders gesagt: Falls die Zahlen in der Sequenz höchsten O(log n) viele Bits haben => dann sind die Zahlen also höchsten n^d gross für eine Konstante d. Das heisst, wir haben maximal d viele Stellen von Zahlen die maximal n gross sind => d = const, K = n => O(n) Also doch noch relativ grosser Sequenzraum möglich zu sortieren in linearer Zeit, nicht nur n, sondern polynomiell! Kapitel 5

126 Radixsort Beispiel (Ziffern des Zehnersystems, K=10):
Ordnung nach Einerstelle: 12 203 3 74 24 17 112 1 2 3 4 5 6 7 8 9 Schauen wir ein Beispiel an fürs Zehnersystem: gegeben diese Inputsequenz, sortieren wir nach der letzten Stelle (Einerstelle). Kapitel 5

127 Radixsort Ergebnis nach Einerstelle: Ordnung nach Zehnerstelle: 12 112
203 3 74 24 17 1 2 3 4 5 6 7 8 9 Nun sortieren wir noch nach der Zehnerstelle. Kapitel 5

128 Radixsort Ergebnis nach Zehnerstelle: Ordnung nach Hunderterstelle:
203 3 12 112 17 24 74 1 2 3 4 5 6 7 8 9 Als nächstes sortieren wir nach der Zehnerstelle. Dabei behalten wir die Reihenfolge der Zahlen mit gleicher Zehnerstelle bei! Reihenfolge von Zahlen mit gleicher Zehnerstelle behalten wir bei! (Stabilität von k-Sort!) Kapitel 5

129 Radixsort Ergebnis nach Hunderterstelle: Sortiert! Zauberei??? 3 12 17
24 74 112 203 Zauberei??? Nun machen wir das gleiche noch für die Hunderterstelle, und schon ist alles ganz sortiert! Kapitel 5

130 Radixsort Korrektheit:
3135 < 3146 Korrektheit: Für jedes Paar x,y mit key(x)<key(y) gilt: es existiert i mit keyi(x)<keyi(y) und keyj(x)=keyj(y) für alle j>i (j wächst gegen links: kleine Stellen zuerst) Schleifendurchlauf für i: poss(x)<poss(y) (poss(z): Position von z in Folge s) Schleifendurchlauf für j>i: Ordnung wird beibehalten wegen pushBack in KSort Wieso funktioniert das? Wir können folgende Beobachtungen machen: Für jedes Schlüsselpaar bei welchem Schlüssel x kleiner ist als Schlüssel y gilt dass es eine Stelle gibt, bei dem der erste Schlüssel kleiner ist, und bei allen höheren Stellen sind sie gleich. Beim Schleifendurchgang i werden diese beiden Zahlen dann richtig sortiert, bei den höheren Stellen bleibt dann ihre Reihenfolge dank der Stabilität bestehen. Das ganze folgt also sozusagen über Induktion über die Stellen. Kapitel 5

131 Radixsort: Beispiel Wikipedia
Input Phase 1 Phase 2 Hier zum Schluss noch ein Beispiel von Wikipedia. Wir machen k-Sort nach einer Stelle nach der anderen und lassen bestehende Sequenz solange es möglich ist. Phase 3 Output Kapitel 5

132 Externes Sortieren Heutige Computer: Prozessor Interner Speicher
Größe M Zum Schluss betrachten wir nun noch einen weiteren Aspekt vom Sortieren. Bis jetzt haben wir immer angenommen, dass das ganze zu sortierende Feld im Computerspeicher Platz hat und wir sofort ein beliebiges Element mit jedem anderen vergleichen können. Was ist nun aber, wenn wir einen riesigen Datensatz (Petabytes von Daten) sortieren müssen, aber unser Memory recht klein ist? Können wir dann überhaupt noch sortieren? Und falls ja, wie geht‘s effizient? Wir betrachten das folgende vereinfachte Computermodell: Wir haben einen Prozessor der auf einem internen Speicher der Grösse maximal M operiert (e.g., Hauptspeicher oder Cache). Daneben gibt‘s einen (beliebig grossen) „externen Speicher“ (langsameres Cache, oder Festplatte, oder Tape) Der interne Speicher kommuniziert mit dem externen über den Austausch von Blocks der Grösse B Blockgröße B Externer Speicher Kapitel 5

133 Externes Sortieren Problem: Minimiere Blocktransfers zwischen internem und externem Speicher (# Vergleiche nun sekundär / vernachlässigbar) Lösung: verwende Mergesort für 2 sortierte Teilsequenzen Um Elemente zu vergleichen, müssen wir sie ins interne Memory laden. Wir nehmen nun im folgenden an, dass diese Blocktransfers eine sehr teure Operation sind (Zeugs ins RAM laden), und dass die Anzahl Vergleiche die wir brauchen zum Sortieren dagegen vernachlässigt werden kann: das einzige was zählt, ist die Anzahl Blocktransfers. Wie kann man nun ein gegebenes Feld im externen Memory sortieren, indem man nur einzelne Blocks in den internen Speicher lädt? Nehmen wir an, n (= Anzahl Elemente im externen Feld) sei ein ganzzahliges Vielfaches von B. Wir können nun mit Mergesort das Array wie folgt sortieren. O(n/B) Blocktransfers Kapitel 5

134 Zwei sortierte Felder mergen
load block load block m 5 10 19 1 3 14 merge Kapitel 5

135 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge write block 1 Kapitel 5

136 Zwei sortierte Felder mergen
load block 5 10 19 1 3 14 merge 1 Kapitel 5

137 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge write block 1 3 Kapitel 5

138 Zwei sortierte Felder mergen
load block 5 10 19 1 3 14 merge 1 3 Kapitel 5

139 Zwei sortierte Felder mergen
5 10 19 1 3 14 merge write block 1 3 5 Etc. Kapitel 5

140 Externes Sortieren Externes Mergesort (einfach):
sortiere Folge in Abschnitten der Größe M (komplett intern möglich, e.g., Quicksort) Merge von jeweils zwei Teilfolgen bis zur Gesamtfolge (<=1+log(n/M) Durchläufe) Anzahl Blocktransfers: O(2n/B (1+log(n/M))) M M M M M Wir laden Subarrays der Grösse M ins Memory (haben ganz Platz). Dieses Subarray sortieren wir lokal (e.g., mit Quicksort). Wir haben nun also so M grosse sortierte Blöcke. Danach sortieren wir die einzelnen Teilfolgen mit Mergesort jeweils. D.h. wir laden zwei Blocks rein, machen Mergesort, und schreiben ihn zurück. Insgesamt müssen wir also 1 + log(n/M) oft das ganze Array in den internen Speicher laden => Laufzeit O(2n/B (1+log(n/M)). Kapitel 5

141 Externes Sortieren Externes Mergesort (einfach):
Mergen zweier Teilfolgen mittels 3 Blöcken im internen Speicher (rote Kästen): B Merge zwei Sequenzen blockweise! Min Wir mergen wir am besten zwei solche Teile? Die einfachste Variante ist, dass wir einfach jeweils 2 Blocks reinladen, und einen Block als Output halten fürs Mergen. Das heisst, wir schreiben immer den kleineren der beiden Blöcke gleich wieder rauf => in beiden verbleibenden Teilstücken können nur noch grössere kommen. B B M Kapitel 5

142 Externes Sortieren Externes Mergesort (einfach):
Mergen zweier Teilfolgen mittels 3 Blöcken im internen Speicher (rote Kästen): B Wir mergen wir am besten zwei solche Teile? Die einfachste Variante ist, dass wir einfach jeweils 2 Blocks reinladen, und einen Block als Output halten fürs Mergen. Das heisst, wir schreiben immer den kleineren der beiden Blöcke gleich wieder rauf => in beiden verbleibenden Teilstücken können nur noch grössere kommen. B B M Kapitel 5

143 Beispiel Hier ein Beispiel. Wir sortieren zuerst alle Zeilstücke der Grösse M vor. Danach machen wir immer Paare von Teilstücken, und sortieren die: dazu laden wir einfach zwei Blocks rein, und schreiben den kleineren raus. Kapitel 5

144 Externes Sortieren Externes Mergesort (verbessert):
sortiere Folge in Abschnitten der Größe M (komplett intern möglich) Merge von jeweils M/B-1 Teilfolgen bis zur Gesamtfolge (<=1+logM/B(n/M) Durchläufe) Anzahl Blocktransf.: O(2n/B (1+logM/B(n/M))) M M M M M Wie kann man das nun noch verbessern? Idee: k-weg Merge! Eigentlich ist es Verschwendung, dass wir nur zwei Teilsequenzen vergleichen! Wir können viel mehr Blocks aus versch. Sequenzen nehmen und dann gleichzeitig das Minimum aller Sequenzen rausschreiben. (interne Vergleiche sind ja gratis wie wir annehmen, dafür haben wir jetzt insgesamt weniger Phasen) Wir haben ja viel mehr interner Speicher zur Verfügung als wir bisher benutzen wollten! D.h. wir mergen bis zu k Sequenzen in jeder Phase, wir können ja auch mehr als nur 2 Blocks ins main memory laden: Wir können M/B Blöcke ins Memory laden!! Dadurch erreichen wir einen höheren Grad und somit weniger tiefen Baum => Laufzeit ändert in Basis des Logarithmus. Kapitel 5

145 Externes Sortieren Externes Mergesort (verbessert):
Mergen von M/B-1 Teilfolgen mittels M/B Blöcken im internen Speicher (rote Kästen): Min Die Idee vom k-weg Merge ist es, gerade M/B-1 (einen Block brauchen wir für Output) viele Teilfolgen aufs mal zu mergen. Wie gesagt, können wir intern dann das Minimum aller M/B-1 Minima der Sequenzen effizient bestimmen. (uns geht‘s ja nur um die Anzahl Blocktransfers!) Sobald der Block von der einen Sequenz als Minimum bestimmt wurde wird der nächste Block gewählt! Kapitel 5

146 Externes Sortieren Externes Mergesort (verbessert):
Mergen von M/B-1 Teilfolgen mittels M/B Blöcken im internen Speicher (rote Kästen): Wenn z.B. hier das Minimum ist... Min! Kapitel 5

147 Externes Sortieren Externes Mergesort (verbessert):
Mergen von M/B-1 Teilfolgen mittels M/B Blöcken im internen Speicher (rote Kästen): Min Die Idee vom k-weg Merge ist es, gerade M/B-1 (einen Block brauchen wir für Output) viele Teilfolgen aufs mal zu mergen. Wie gesagt, können wir intern dann das Minimum aller M/B-1 Minima der Sequenzen effizient bestimmen. (uns geht‘s ja nur um die Anzahl Blocktransfers!) Sobald der Block von der einen Sequenz als Minimum bestimmt wurde wird der nächste Block gewählt! Kapitel 5

148 Beispiel M/B-1=3 merge load block load block load block 5 10 1 19 3 14
Kapitel 5

149 Beispiel M/B-1=3 merge 5 10 1 19 3 14 write block 1 18.09.2018
Kapitel 5

150 Beispiel M/B-1=3 merge read block 5 10 1 19 3 14 1 18.09.2018
Kapitel 5

151 Beispiel M/B-1=3 merge 5 10 1 19 3 14 write block 1 3 18.09.2018
Kapitel 5

152 Beispiel M/B-1=3 merge read block 5 10 1 19 3 14 1 3 Etc. 18.09.2018
Kapitel 5

153 Externes Sortieren Externes Mergesort (verbessert):
Mergen von M/B-1 Teilfolgen mittels M/B Blöcken im internen Speicher (rote Kästen): Können wir eins Weiterrücken. BEMERKUNG: wobin schreiben wir das? Entweder extern zweiten Outputbereich, oder dann grad gefundenes Minimum ganz nach rechts swappen, „in place“. Kapitel 5

154 Veranschaulichung 18.09.2018 Kapitel 5
Hier nochmals eine Veranschaulichung vom m-weg Mergen: Wir mergen immer m Sequenzen miteinander, wir brauchen also weniger Schritte bis alles gemerged. Kapitel 5

155 Nächstes Kapitel Priority Queues
(Damit ist Selection Sort viel besser!) Vielen Dank für die Aufmerksamkeit. Im nächsten Kapitel werden wir dann Priority Queues kennenlernen. Diese Datenstruktur kann z.B. helfen, Selection Sort effizienter zu machen! Kapitel 5

156 Nachtrag zu While Schleifen (1)
Wie kann man die Laufzeit von for-Schleifen analysieren? Einfach: for-Schleifen haben Zähler - for (i=0, i<X, i++) {} wird X mal ausgeführt while-Schleifen haben keinen Zähler - Idee: Führe künstlich einen „Zähler“ ein, resp. eine Grösse die in jeder (oder jeder zweiten etc.) Iteration strikt zu oder abnimmt - Nennen wir Potenzialfunktion! - Hat nichts mit amortisierter Analyse mit Potenzialfunktionen zu tun! Kapitel 5

157 Nachtrag zu While Schleifen (2)
Beispiel: i := N; while (i>1) i:=i/2; Mögliche Potenzialfunktion:  = i; Es gilt 0 = N und j+1 = j / 2 Sobald j · 1 terminiert‘s Wieviel Iterationen hat die While-Schleife? Was wäre, wenn man in jedem Schritt i:=i-1 rechnen würde? Was wäre, wenn man in jedem Schritt i:=sqrt(i) rechnen würde? Was wäre, wenn man in jedem Schritt i:=log(i) rechnen würde? Was wäre, wenn in der Schleife noch mehr Code wäre? Kapitel 5


Herunterladen ppt "Grundlagen der Algorithmen und Datenstrukturen Kapitel 5"

Ähnliche Präsentationen


Google-Anzeigen