Programmieren, Algorithmen und Datenstrukturen II 5. Effizientes Sortieren und Suchen

Ähnliche Dokumente
Algorithmen und Datenstrukturen Heapsort

Datenstrukturen & Algorithmen

8. Sortieren II. 8.1 Heapsort. Heapsort. [Max-]Heap 6. Heapsort, Quicksort, Mergesort. Binärer Baum mit folgenden Eigenschaften

Heapsort, Quicksort, Mergesort. 8. Sortieren II

8. A & D - Heapsort. Werden sehen, wie wir durch geschicktes Organsieren von Daten effiziente Algorithmen entwerfen können.

Gliederung. 5. Compiler. 6. Sortieren und Suchen. 7. Graphen

Algorithmen und Datenstrukturen

Algorithmen und Datenstrukturen (für ET/IT)

Teil VII. Hashverfahren

13. Hashing. AVL-Bäume: Frage: Suche, Minimum, Maximum, Nachfolger in O(log n) Einfügen, Löschen in O(log n)

Informatik II, SS 2014

12. Hashing. Hashing einfache Methode um Wörtebücher zu implementieren, d.h. Hashing unterstützt die Operationen Search, Insert, Delete.

Abschnitt: Algorithmendesign und Laufzeitanalyse

Heapsort / 1 A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8]

Humboldt-Universität zu Berlin Berlin, den Institut für Informatik

Bäume, Suchbäume und Hash-Tabellen

Kapitel 6 Elementare Sortieralgorithmen

Suchen und Sortieren

Informatik II, SS 2018

Übung Algorithmen und Datenstrukturen

Informatik II Prüfungsvorbereitungskurs

Kapitel 8 Fortgeschrittene Sortieralgorithmen

Algorithmen und Datenstrukturen

Übung Algorithmen und Datenstrukturen

Abschnitt 19: Sortierverfahren

Inhaltsverzeichnis. Teil 1 Grundlagen 21. Teil 2 Datenstrukturen 85

8.1.3 Operation Build-Max-Heap Operation zur Konstruktion eines Heaps Eingabe: Feld A[1..n], n = länge(a) BUILD-MAX-HEAP (A)

7. Sortieren Lernziele. 7. Sortieren

Technische Universität München

Inhaltsverzeichnis. Teil 1 Grundlagen 23

Datenstrukturen und Algorithmen

Definition Ein Heap (priority queue) ist eine abstrakte Datenstruktur mit folgenden Kennzeichen:

A7.1 Untere Schranke. Algorithmen und Datenstrukturen. A7.1 Untere Schranke. Algorithmen und Datenstrukturen. A7.2 Quicksort. A7.

Algorithmen und Datenstrukturen

1 Raumwechsel: Gr. 15 (Do 10-12, F-235) ab sofort in G Studie zum Arbeitsverhalten von Studierenden unter Leitung

Klausur Algorithmen und Datenstrukturen

Suchen und Sortieren Sortieren. Heaps

Informatik II Prüfungsvorbereitungskurs

Algorithmen und Datenstrukturen

Grundlagen der Informatik

Klausur Algorithmen und Datenstrukturen SS August Arbeitszeit 90 min

Das Suchproblem. Gegeben Menge von Datensätzen. Beispiele Telefonverzeichnis, Wörterbuch, Symboltabelle

Sortieren durch Mischen (Mergesort; John von Neumann 1945)

Stud.-Nummer: Datenstrukturen & Algorithmen Seite 1

(Digital) Sorting. October 25, Algorithms & Datastructures 2 Exercises WS 2016

Das Suchproblem. Gegeben Menge von Datensätzen. Beispiele Telefonverzeichnis, Wörterbuch, Symboltabelle

f 1 (n) = log(n) + n 2 n 5 f 2 (n) = n 3 + n 2 f 3 (n) = log(n 2 ) f 4 (n) = n n f 5 (n) = (log(n)) 2

Das Suchproblem 4. Suchen Das Auswahlproblem Suche in Array

Datenstrukturen und Algorithmen. 7. Suchen in linearen Feldern

Sortieralgorithmen. Selection Sort

Informatik II, SS 2016

Kapitel 10. Komplexität von Algorithmen und Sortieralgorithmen

Grundlagen der Programmierung

Algorithmen und Datenstrukturen

Robert Sedgewick. Algorithmen in Java. »il 1-4 Grundlagen Datenstrykturen Sortleren Suchen. java-beratung durch Michael Schidlowsky

Grundlagen der Informatik. Prof. Dr. Stefan Enderle NTA Isny

Algorithmen und Datenstrukturen 1

Sortieren & Co. KIT Institut für Theoretische Informatik

Informatik II, SS 2016

Erinnerung VL vom

Robert Sedgewick. Algorithmen in Java. Teil 1-4 Grundlagen Datenstrukturen Sortieren Suchen. Java-Beratung durch Michael Schidlowsky

Übungsklausur Algorithmen I

NAME, VORNAME: Studiennummer: Matrikel:

JAVA - Suchen - Sortieren

Algorithmen und Datenstrukturen (für ET/IT) Programm heute. Sommersemester Dr. Tobias Lasser

Vorlesung Datenstrukturen

QuickSort ist ein Sortieralgorithmus, der auf der Idee des Teile & Beherrsche beruht, und das gegebene Array an Ort und Stelle (in place) sortiert

Sortieren II / HeapSort Heaps

Informatik II, SS 2014

Algorithmen I. Tutorium 1-3. Sitzung. Dennis Felsing

Vorlesung Datenstrukturen

Algorithmen & Komplexität

(08 - Einfache Sortierverfahren)

Kapitel 9 Suchalgorithmen

Algorithms & Data Structures 2

Teil 1: Suchen. Ausgeglichene Bäume B-Bäume Digitale Suchbäume. M.O.Franz, Oktober 2007 Algorithmen und Datenstrukturen - Binärbäume 1-1

Grundlagen der Algorithmen und Datenstrukturen Kapitel 4

Algorithmen und Datenstrukturen 12

6/23/06. Universelles Hashing. Nutzen des Universellen Hashing. Problem: h fest gewählt es gibt ein S U mit vielen Kollisionen

(a, b)-bäume / 1. Datenmenge ist so groß, dass sie auf der Festplatte abgespeichert werden muss.

Kapitel 9. Komplexität von Algorithmen und Sortieralgorithmen

Sortieren und Suchen. Jens Wächtler Hallo Welt! -Seminar LS 2

Übung Algorithmen und Datenstrukturen

Vorlesung Informatik 2 Algorithmen und Datenstrukturen

Vorlesung Informatik 2 Algorithmen und Datenstrukturen

Kapitel 2. Weitere Beispiele Effizienter Algorithmen

Übung Algorithmen I

Informatik II, SS 2014

Abstrakter Datentyp (ADT): Besteht aus einer Menge von Objekten, sowie Operationen, die auf diesen Objekten wirken.

Interne Sortierverfahren

Datenstrukturen & Algorithmen

2. Effizienz von Algorithmen

3. Suchen. Das Suchproblem. Suche in Array. Lineare Suche. 1 n. i = n Gegeben Menge von Datensätzen.

Datenstrukturen und Algorithmen. Vorlesung 10

Kapitel 9. Komplexität von Algorithmen und Sortieralgorithmen

Algorithmen und Datenstrukturen

Vorlesung Informatik 2 Algorithmen und Datenstrukturen

Informatik II Vorlesung am D-BAUG der ETH Zürich

Algorithmen und Datenstrukturen

Kapitel 3: Sortierverfahren Gliederung

Transkript:

Programmieren, Algorithmen und Datenstrukturen II 5. Effizientes Sortieren und Suchen 1

Übersicht 1. Ziele des Kapitels 2. Asymptotische Schranken 3. Wiederholung bekannter Sortieralgorithmen 4. Heap Sort 5. Quicksort 6. Analyse rekursiver Algorithmen 7. Einführung Suchen 8. Hashing 9. Assoziative STL-Container 2

1. Ziele des Kapitels Kapitel C++ und SW Engineering Algorithmen und Datenstrukturen 1 2 Ein- / Ausgabe mit Dateien, Ausnahmebehandlung Vererbung 4 3 Abstrakte Datentypen + Klassen-Templates Überladen von Operatoren 5 Sortieren und Suchen 6 Bäume 7 Graphen 8 Allgemeine Lösungsverfahren 3

1. Ziele des Kapitels Bisher haben wir fast ausschließlich Sortieralgorithmen mit quadratischer Laufzeit kennen gelernt. In diesem Abschnitt betrachten wir bessere Algorithmen, welche i.d.r. rekursiv arbeiten und nach dem Divide-and-conquer-Paradigma (Teile und Herrsche) arbeiten Darüber hinaus betrachten wir die beim Programmieren immer wieder kehrende Problematik, Daten zu speichern, um sie später noch einmal wieder benutzen zu können. Eine Technik, dies zu tun ist Hashing. Zunächst werden ein paar wichtige Grundlagen aus dem 1. Semester wiederholt. 4

2. Asymptotische Schranken [Wiederholung aus dem 1. Semester] In der Algorithmenanalyse interessiert man sich meistens für den Worst-Case (WC): also die schlechteste Laufzeit bei Eingabegrößen (obere Schranke für die Laufzeit). Der mittlere Fall (AvgCase) ist häufig nur sehr schwer zu bestimmen und ist zudem oftmals auch nicht besser als der Worst-Case. Asymptotische Schranken sind ein abstraktes Modell, das es erlaubt die Effizienz von verschiedenen Algorithmen zu vergleichen. Die Effizienz wird dabei ausgedrückt durch eine Laufzeit-Funktion, welche in Abhängigkeit von der Eingabe-Größe n, die Anzahl der benötigten Einzelschritte (Statements) vorhersagt, z.b. f(n) n 2 + 20 n 7 Zusätzlich beschränkt sich eine asymptotische Schranke auf den Teil der Funktion, der den größten Betrag liefert (hier n 2 ). Man macht also eine Abschätzung in der O-Notation. 5

2. Asymptotische Schranken 2.1 Formale Definition der O-Notation: Es ergeben sich folgende formalen Rechenregeln zur Vereinfachung: f(n) O(f(n)) O(c f(n)) O(f(n)) O(f(n)) + O(g(n)) O(f(n) + g(n)) O(max(f(n),g(n))) O(f(n)) O(g(n)) O(f(n) g(n)) f(n) O(g(n)) O(f(n) g(n)) 6

2. Asymptotische Schranken 2.2 Komplexitätsklassen Nicht-effiziente Algorithmen: exponentielle Komplexität O(2 n ), O(n!) Effiziente Algorithmen konstante Komplexität O(1) logarithmische Komplexität O(log n) lineare Komplexität O(n) überlinear O(n log n) polynomiale Komplexität O(n 2 ), O(n 3 ), etc. Grundlage: 1 Operation 1 ns ( 10-9 sec) 7

3. Wiederholung bekannter Sortieralgorithmen [Wiederholung aus dem 1. Semester] Im Bereich Informatik ist das Sortieren sehr früh ausführlich behandelt worden, denn es ist in fast allen Anwendungsbereichen von Bedeutung (Datenbanken, Compiler, Betriebssysteme, Computer-Grafik, ). Bemerkung: Wir beschränken uns dabei auf die aufsteigende Sortierung von ganzen Zahlen (Schlüssel). Wir betrachten zunächst zwei Sortieralgorithmen aus dem letzten Semester. 8

3. Wiederholung bekannter Sortieralgorithmen 3.1 Sortieren durch Auswählen Selection Sort wählt das Element mit dem niedrigsten Wert aus und vertauscht es mit dem ersten Element. Von den verbleibenden n-1 Elementen wird dann das kleinste genommen und mit dem zweiten Element vertauscht. Dies wird bis zu den letzten zwei Elementen wiederholt. // Selection Sort mit linearer Suche for (int i0; i <n-1; i++) { int ki; int min A[i]; // Index kleinstes Element // kleinstes Element for (int ji+1; j<n; j++) { // suche kleinstes Element if (A[j] < min) { k j; min A[j]; } } } A[k] A[i]; // vertausche aktuelles Element mit kleinstem Element A[i] min; 9

3. Wiederholung bekannter Sortieralgorithmen 3.2 Sortieren durch Mischen (Merge Sort) Mischen (engl. merge) bedeutet hierbei das Zusammenfließen von zwei (oder mehreren) geordneten Sequenzen zu einer einzigen sortierten Sequenz. Beispiel: aus (10, 23, 36, 42) und (4, 6, 54, 64) wird (4, 6, 10, 23, 36, 42, 54, 64) Man erhält folgenden Algorithmus: Mergesort(A, Mergesort(A, p, p, r) r) if if p p < < r r then then // // Größe Größe des des Teilarrays Teilarrays ist ist größer größer 1 1 q q (p+r)/2 (p+r)/2 // // Teile Teile in in der der Mitte Mitte Mergesort(A, Mergesort(A, p, p, q) q) // // Sortiere Sortiere linken linken Teil Teil Mergesort(A, Mergesort(A, q+1, q+1, r) r) // // Sortiere Sortiere rechten rechten Teil Teil Merge(A, Merge(A, p, p, q, q, r) r) // // Kombiniere Kombiniere die die beiden beiden Teile Teile Initialer Aufruf ist: Mergesort(A, 0, n-1); 10

4. Heap Sort Heap-Sort ist eine Verbesserung des bereits vorgestellten Selection Sort. Das zeitraubende am Selection Sort ist die Lineare Suche nach dem kleinsten Element im verbleibenden Arrray, was im schlechtesten Fall O(n) Schritte benötigt. // Selection Sort mit linearer Suche for (int i0; i <n-1; i++) { int ki; // Index kleinstes Element int min A[i]; // kleinstes Element for (int ji+1; j<n; j++) { // suche kleinstes Element if (A[j] < min) { k j; min A[j]; } } A[k] A[i]; // vertausche aktuelles Element mit kleinstem Element A[i] min; } Die Idee bei Heap-Sort ist es, den noch unsortierten Teil durch eine bessere Datenstruktur (Heap) zu verwalten, so dass es besser (d.h. effizienter) möglich ist, das jeweilige Minimum zu finden. 11

4. Heap Sort 4.1 Definition Heap Der ADT Baum besteht aus Mengen von Knoten (Elementen) und gerichteten Kanten, die die Knoten verbinden. Dabei gilt: Gibt es eine Kante von A nach B, wird A der Vorgänger (Vater) von B und B der Nachfolger von A genannt. Mehrere Nachfolger eines Knotens werden auch Kinder genannt. Es gibt immer nur genau einen Knoten (die sog. Wurzel engl. root), von dem aus alle anderen Knoten zu erreichen sind. Es gibt immer nur genau einen Weg von der Wurzel zu einem Knoten. Bäume und andere Graphen werden wir später noch genauer kennen lernen. 12

4. Heap Sort Ein Heap ist ein Baum mit folgenden Eigenschaften: In den Knoten stehen die zu sortierenden Elemente. Dabei gilt für jeden Knoten außer der Wurzel: Der Wert jedes Knotens ist größer oder gleich dem seiner Nachfolger (Heap-Eigenschaft). Ein Heap ist ein binärer Baum, d.h. jeder Knoten hat maximal 2 Kinder. Ein Heap ist ein linksvollständiger Baum: d.h. jeder linke Nachfolgerknoten, der einen Bruder mit Kindern hat, muss auch Kinder haben. Der Baum ist also mindestens bis zur vorletzten Ebene vollständig und in der letzten Ebene dürfen nur rechts Knoten fehlen. 13

4. Heap Sort Ein Heap lässt sich leicht auf ein Array A[1...n] abbilden, indem man die Knoten von links oben nach rechts unten durch nummeriert: A[1] root parent(i) i/2 left(i) 2i right(i) 2i+1 Heap-Eigenschaft: A[parent(i)] A[i], i A[1]_ A[1]_ / \ A[2] A[2] A[3] A[3] / \ / \ A[4] A[4] A[5] A[5] A[6] A[6] A[7] A[7] / \ A[8] A[8] A[9] A[9] 14

4. Heap Sort Damit wird ein Heap zum Sortieren wie folgt benutzt: Zuerst wird aus einem ungeordneten Array ein Heap gebaut. (1) Somit steht der größte Wert immer in der Wurzel (A[1]), welche nun in den sortierten Teil am Ende des Arrays übertragen wird (4) Man beginnt daher ausgehend vom größten Element, d.h. der sortierte Teil steht rechts im Array Baue aus den verbleibenden Elementen erneut einen Heap (6) Wiederhole diese Vorgehen bis alles sortiert worden ist (3) Heapsort(A) Heapsort(A) // // Sortiere Sortiere Array Array A A (1) (1) Build-Heap(A) Build-Heap(A) (2) (2) HeapSize(A) HeapSize(A) n n (3) (3) for for in in downto downto 2 2 do do (4) (4) swap(a[1], swap(a[1], A[i]) A[i]) (5) (5) HeapSize(A) HeapSize(A) HeapSize(A)-1 HeapSize(A)-1 (6) (6) Heapify(A,1) Heapify(A,1) 15

4. Heap Sort 4.2 Erzeugen der Heap-Ordnung: Idee: Beginnend beim letzten Knoten, der kein Blatt ist, bis zur Wurzel wird Heapify(i) aufgerufen, welches die Heap-Ordnung herstellt, wenn left(i), right(i) jeweils Heaps sind, die Heap-Ordnung selbst in Knoten i aber verletzt ist. Einelementige Teilbäume (z.b. die Blätter) erfüllen bereits die Heap- Eigenschaft. Algorithmus: BuildHeap(A) BuildHeap(A) for for i i n/2 n/2 downto downto 1 1 do do Heapify(A,i) Heapify(A,i) 16

4. Heap Sort 4.3 Heapify: Erhalten der Heap-Ordnung Ausgangspunkt: Knoten i, bei dem für die Teilbäume mit den Wurzeln left(i) und right(i) die Heap-Eigenschaft erfüllt ist Im Knoten i selbst ist die Heap-Eigenschaft verletzt d.h: A[i] < A[left(i)] oder A[i] < A[right(i)] Vorgehen: Heapify(A,i) stellt die Heap-Ordnung wieder her Der größte der 3 Knoten i, left(i) und right(i) wird bestimmt und zur Wurzel des Teilbaums gemacht (durch Austausch der Knoten) In dem betroffenen Teilbaum ist anschließend ggf. die Heap-Eigenschaft verletzt, was durch einen rekursiven Aufruf von Heapify behoben werden kann. 17

4. Heap Sort Algorithmus: Heapify Heapify (A,i) (A,i) (1) (1) l l left(i) left(i) (2) (2) r r right(i) right(i) (3) (3) if if l l HeapSize(A) HeapSize(A) and and A[l] A[l] > > A[i] A[i] (4) (4) then then max max l l (5) (5) else else max max i i (6) (6) if if r r HeapSize(A) HeapSize(A) and and A[r] A[r] > > A[max] A[max] (7) (7) then then max max r r (8) (8) if if max max i i (9) (9) then then swap(a[i],a[max]) swap(a[i],a[max]) (10) (10) Heapify(A,max) Heapify(A,max) 18

4. Heap Sort Beispiel: 19

4. Heap Sort Beispiel: Heapsort von Gms - Eigenes Werk. Lizenziert unter CC BY-SA 3.0 über Wikimedia Commons - http://commons.wikimedia. org/wiki/file:heapsort.svg# /media/file:heapsort.svg 20

4. Heap Sort Beispiel-Aufgabe: Sortieren der Folge (6, 17, 9, 8, 2, 5, 11, 3) 21

4. Heap Sort 4.5 Analyse von Heap-Sort Heapify(): Zeilen 1-9: konstant O(1) Entscheidend für die Laufzeit ist die Rekursion. Die maximale Rekursionstiefe ist dabei gleich der Höhe des Heaps. Da ein Heap ein Binärbaum ist, ist diese log 2 n. Somit ergibt sich ein Aufwand von BuildHeap(): Heapify() wird O(n)-mal aufgerufen. Somit ergibt sich ein Aufwand von Heapsort(): BuildHeap benötigt O(n log n) Zeit Die for-schleife (2-6) wird O(n)-mal durchlaufen Also wird Heapify() O(n)-mal aufgerufen Somit ergibt sich ein Aufwand von Aufgabe: Füllen Sie die leeren Felder aus! 22

5. Quicksort Quicksort basiert auf der Vertauschmethode (wie Bubblesort) und repräsentiert ein Divide & Conquer-Verfahren (wie Merge Sort), das ein Teilfeld A[p..r] wie folgt sortiert: Divide: Bestimme ein Pivot-Element A[q] (nach einem noch fest zulegenden Verfahren) Das Feld A[p..r] wird zerlegt und umgeordnet in zwei Teilfelder A[p..(q-1)] und A[(q+1)..r], so dass gilt: A[i] A[q] A[j] p i q-1 und q+1 j r links vom Pivot-Element sind also alle kleiner (gleich) und rechts alle größer (gleich) sortiert sind die beiden Teile zwar noch nicht, aber das Pivot-Element steht bereits an der richtigen Stelle. Conquer: Wende Zerteilungs- und Umordnungsprinzip rekursiv auf beide Teilfelder an. Ein Teil-Array der Größe 1 ist sortiert und braucht/kann nicht mehr zerteilt werden. Combine: Ein expliziter Kombinationsschritt ist nicht notwendig, da der Conquer-Schritt bereits sortierte Teilfolgen hinterlässt. 23

5. Quicksort 5.1 Algorithmus Initialer Aufruf: Quicksort(A, 1, n) Quicksort(A, Quicksort(A, p, p, r) r) (1) (1) if if p p < < r r then then (2) (2) q q Partition(A, Partition(A, p, p, r) r) (3) (3) Quicksort(A, Quicksort(A, p, p, q-1) q-1) (4) (4) Quicksort(A, Quicksort(A, q+1, q+1, r) r) Partition(A, Partition(A, p, p, r) r) //Lumuto-Partition //Lumuto-Partition (1) (1) pivot pivot A[p] A[p] (2) (2) l l p p (3) (3) for for ip+1 ip+1 to to r r do do (4) (4) if if A[i] A[i] < < pivot pivot then then (5) (5) l l l l + + 1 1 (6) (6) swap(a[i], swap(a[i], A[l]) A[l]) (7) (7) swap(a[p], swap(a[p], A[l]) A[l]) (8) (8) return return l l 24

5. Quicksort Beispiel-Aufgabe: Sortieren der Folge (6, 17, 9, 8, 2, 5, 11, 3) 25

5. Quicksort 5.2 Eigenschaften von Quick-Sort Es gibt verschiedene Methoden, das Pivot-Element zu bestimmen. Die hier vorgestellte ist recht praktikabel. Eine ausgefeiltere Methode wäre allerdings effizienter. Der Worst-Case tritt ein, wenn die Folge bereits sortiert ist: O(n2 ) Es lässt sich formal beweisen, dass im durchschnittlichen Fall ein Aufwand von O(n log n) entsteht. Trotz des schlechteren Worst-Case-Verhaltens ist Quck-Sort im Allgemeinen um einen Faktor 2-3 schneller als Heap-Sort (aufgrund seiner einfacheren Datenstruktur). Da bei großen Datenmengen ein Stack-Überlauf eintreten kann, wird in der Praxis häufig eine iterative Variante bevorzugt. 26

6. Analyse rekursiver Algorithmen Rekursive Algorithmen sind meist nur schwer im Aufwand abzuschätzen. Mit herkömmlichen Methoden kann dies auf zwei Arten geschehen: 1. Der Rekursionsbaum wird bildlich dargestellt und dessen Höhe und Breite ermittelt. Dabei ist es i.a. schwierig, alle Aufwände korrekt zu erfassen. 2. Eine Rekurrenzgleichung der unten stehenden Form wird durch einen formalen Beweis gelöst, was sehr mühsam ist (Iterationsmethode). Bei der im Folgenden vorgestellten Methode dem Master Theorem wird dagegen auf die Anwendung von drei Grundregeln vertraut, die (mit etwas Übung) in wenigen Schritten eine Abschätzung ermöglichen. Voraussetzung dabei ist, dass sich der Aufwand eines rekursiven Algorithmus oft (aber nicht immer) durch folgende Gleichung darstellen lässt (D&C): T n at n /b f n Zerlegung in a Teilprobleme der Größe n/b, jeweils mit Aufwand T(n/b) Kosten für Zerteilung und Kombination (je Knoten im Rekursionsbaum) 27

6. Analyse rekursiver Algorithmen 6.1 Iterationsmethode [Wiederholung aus dem 1. Semester] Für Mergesort erhält man folgende (vereinfachte) Rekurrenz-Gleichung: T(n) 2 T(n/2) + O(n) 2 T(n/2) + n Mit der Iterationsmethode erhält man folgende Abschätzung: T n 2 T n/ 2 n 2 T 2 T n /4 n /2 n 4T n /4 2n 4 T 2 T n /8 n/ 4 2n 8 T n/8 3n... n T 1 n log 2 n n log 2 n n O n log n 28

6. Analyse rekursiver Algorithmen Beispiele zur Herleitung der Rekurrenz-Gleichung: int seltsam (int n) { if (n < 2) return 2; else return seltsam(n/2)*2; } Es gilt: T(n) 1 T(n/2) + 1, d.h. a1, b2 und f(n)1 unsigned long fac (unsigned long n) { if (n 1) return 1; else return fact(n-1)*n; } Es gilt: T(n) 1 T(n-1) + 1, d.h. keine Werte für a, b und f(n) 29

6. Analyse rekursiver Algorithmen 6.2 Master-Theorem (Master Method) vereinfachte Form: T n at n /b O n c (1) Falls gilt log b a > c : dann ist: T(n) O(n log b a ) (2) Falls gilt log b a c : dann ist: T(n) O(n c log n) (3) Falls gilt log b a < c : dann ist: T(n) O(n c ) Gilt ab, ist log b a 1. Gilt keiner der drei Fälle oder lassen sich keine Werte für a, b oder f(n) finden, ist das Master-Theorem nicht anwendbar. 30

6. Analyse rekursiver Algorithmen 6.2 Master-Theorem (Master Method) vereinfachte Form: T n at n /b O n c (1) Falls gilt a / b c > 1 : dann ist: T(n) O(n log b a ) (2) Falls gilt a / b c 1 : dann ist: T(n) O(n c log n) (3) Falls gilt a / b c < 1 : dann ist: T(n) O(n c ) Gilt ab, ist log b a 1. Gilt keiner der drei Fälle oder lassen sich keine Werte für a, b oder f(n) finden, ist das Master-Theorem nicht anwendbar. 31

6. Analyse rekursiver Algorithmen Beispiele/Aufgaben zur Anwendung des Master-Theorems: Durch Master-Theorem lösbar: T(n) T(n/2) + 1 T(n) 2 T(n/2) + n T(n) 2 T(n/2) + n 2 Nicht durch Master-Theorem lösbar: T(n) T(n/2) + log n 32

6. Analyse rekursiver Algorithmen Aufgabe: Analysieren Sie die beiden folgenden Algorithmen! bool search(struct tree *p, int info) { if (pnull) return false; if (info p->info) return true; else if (info < p->info) return search(p->left, info); else return search(p->right, info); } // search info in tree p Mergesort(A, Mergesort(A, p, p, r) r) if if p p < < r r then then // // Größe Größe des des Teilarrays Teilarrays ist ist größer größer 1 1 q q (p+r)/2 (p+r)/2 // // Teile Teile in in der der Mitte Mitte Mergesort(A, Mergesort(A, p, p, q) q) // // Sortiere Sortiere linken linken Teil Teil Mergesort(A, Mergesort(A, q+1, q+1, r) r) // // Sortiere Sortiere rechten rechten Teil Teil Merge(A, Merge(A, p, p, q, q, r) r) // // Kombiniere Kombiniere die die beiden beiden Teile Teile 33

7. Einführung Suchen Suchen in der Informatik heißt, ein Element (mit einem bestimmten Schlüssel) in einer Menge von gespeicherten Werten zu finden. Dies ist ein genauso elementares Problem wie das Sortieren. Problemstellung: Wie verwaltet (Einfügen, Finden, Löschen) man effizient (Zeit & Platz) große und sich ständig ändernde Datenstrukturen? Beispiel: Der Compiler für einer Programmiersprache verwaltet eine Symboltabelle Neue Identifier (z.b. Variablen) werden darin gespeichert Abfrage auf doppelte Definitionen Auswertung von Ausdrücken Beispiel zum Beispiel: int x; char y; int x;... x z + y; 34

7. Einführung Suchen Genaue Problemstellung Zur Verarbeitung durch einen Algorithmus (bzw. Computerprogramm) wird eine Datenstruktur zur Verwaltung der Daten gebraucht Definition: A Dictionary is a dynamic set that supports the following operations: INSERT an element: D x T -> D DELETE an element: D x T -> D MEMBER/SEARCH (is it in the set?): D x T -> T or bool Dabei sind D: Ausprägung der Datenstruktur Dictionary T: Wertebereich des verwalteten Schlüsselelemente Im Folgenden nur Betrachtung von nicht-negativen ganzen Zahlen Ziel: Effiziente Implementierung der o.g. Operationen 35

7. Einführung Suchen 7.1 Dictionary als unsortiertes Array Legt man die Daten in unsortiert Weise in einem Array (oder Vector) ab, so können die Dictionary-Funktionen wie folgt implementiert werden: INSERT: einfach am Ende SEARCH: durch eine sequentielle Suche [Wiederholung aus dem 1. Semester] int key, pos; bool found false; cout << "Suche nach: "; cin >> key; for (int i0; i<n; i++) if (key A[i]) { found true; pos i; break; } if (!found) cout << "Element nicht vorhanden!!" << endl; else cout << "Element liegt an Stelle " << pos << endl; 36

7. Einführung Suchen Aufgabe: Wie sieht damit die DELETE-Operation aus? Aufgabe: Laufzeit-Analyse INSERT SEARCH DELETE Be s t W o rs t Av e ra g e 37

7. Einführung Suchen 7.2 Dictionary als sortiertes Array Sorgt man dafür, dass das Array immer sortiert ist, ist die Suche effizienter: SEARCH: durch eine binäre Suche: [Wiederholung aus dem 1. Semester] Zunächst wird das mittlere Element untersucht: ist es der Suchschlüssel ist man fertig. Ist der Suchschlüssel kleiner als das mittlere Element, wird der linken Hälfte des Arrays mittels des gleichen Verfahrens bearbeitet, ansonsten der rechte Teil. Beispiel: Suche 4 Ausgangssituation 1 2 3 4 5 15 INSERT: Suche rechts Suche links 1 2 3 4 5 15 1 2 3 4 5 15 Finden der Einfügestelle durch eine binäre Suche mit anschließendem Verschieben 38

7. Einführung Suchen Beispiel: Finden der Einfügestelle durch eine binäre Suche: INSERT(35) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 9 11 23 32 33 34 40 42 43 49 50 55 66 67 99 35 muss an Position 7 Anschließend: verschieben ab Position 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 9 11 23 32 33 34 35 40 42 43 49 50 55 66 67 99 39

7. Einführung Suchen Aufgabe: Laufzeit-Analyse INSERT SEARCH DELETE Be s t W o rs t Av e ra g e 40

7. Einführung Suchen Dictionary mit Direkter Adressierung Man reserviere für jedes potenzielle Element einen Eintrag: Universum der Schlüssel 9 7 Dictionary-Operationen Direct_Address_Search(T, k) return T[k] Direct_Address_Insert(T, x) T[key(x)] x Direct_Address_Delete(T, x) T[key(x)] NIL 1 5 3 8 2 6 4 1 2 3 4 5 6 7 8 9 NIL 2 3 4 NIL NIL NIL 8 NIL Laufzeit Jeweils O(1) T Platz O( Wertebereich ) 41

8. Hashing 8.1 Grundlegendes Hash-Verfahren Ambition: Vereinigung der Vorteile von Direkter Adressierung: Zugriff in Zeit O(1) (Un)Sortiertem Feld: Platzbedarf O(n) Hashverfahren berechnet die Adresse in einem Array T[0..(m-1)] (Hashtabelle), an der ein Schlüssel gespeichert werden soll (m > n) Die Berechnung erfolgt durch eine Hashfunktion h, die jedem Schlüssel seine Hashadresse zuordnet. Die Hashfunktion ist eine Abbildung h: U {0,...,m-1} von der Menge U (Universum) der möglichen Schlüsselwerte in die Indizes der Hashtabelle 42

8. Hashing Ziel: möglichst gleichmäßige Streuung der Schlüssel T k 10 k 6 k 1 k 9 k 7 k 8 k 5 k 3 k 2 k 4 0 1 m-1 X X X X X h(k 3 ) h(k 4 ) h(k 2 ) h(k 5 ) h(k 1 ) Aber: manche Elemente erhalten denselben Hashwert, d.h. es gibt k k mit h(k) h(k ) Kollision 43

8. Hashing Beispiel eines Hashing-Verfahrens Hashfunktion Divisionsmethode: h(k) k mod m m 13 (Größe der Hashtabelle) Nacheinander Einfügen der Schlüssel k 12, 1, 54, 40 12 mod 13 12 1 mod 13 1 54 mod 13 2 40 mod 13 1 Kollision 0 1 2 3 4 5 6 7 8 9 10 11 12 12 1 54 44

8. Hashing 8.2 Kollisionswahrscheinlichkeit Beispiel: Man benutze für Benutzerdaten eine Hashfunktion auf Basis des Geburtstags (Tag, Monat) Also gilt: m 365 Wahrscheinlichkeit für eine Kollision: n P Kollision 1 i1 P i 1 1 m i 1 m n i 1 m m 1 m 2 m n 1 1 m n n 0,9999 45 n 10 22 23 50 100 P Kollision 0,1169 0,4756 0,5072 0,9703 Birthday Paradoxon: Treffen 23 Personen in einem Raum zusammen, so ist die Wahrscheinlichkeit, dass zwei von ihnen am gleichen Tag Geburtstag haben bereits größer als 1/2. Kollisionen sind also unvermeidbar!

8. Hashing 8.3 Hashing mit Zeichenketten Wenn Zeichenketten als Schlüssel dienen sollen, so können sie einfach in eine ganze Zahl kodiert und in die Hashfunktion eingebracht werden. Algorithmus: hash hash (m, (m, k) k) // // m: m: Modulus, Modulus, k: k: Zeichenkette Zeichenkette h h 0 0 for for i1 i1 to to length[k] length[k] do do h h (128 (128 * * h h + + num(k[i])) num(k[i])) % % m m // // num(c) num(c) liefert liefert die die Ordnungszahl Ordnungszahl zu zu c c return return h h 46

8. Hashing 8.4 Kollisionsvermeidung Praktische Tipps (für Divisionsmethode: h(k) k mod m) Sei m eine gerade Zahl, dann gilt: Ist k gerade so ist auch h(k) gerade, und umgekehrt Sei m eine Zweierpotenz d.h. m 2 i, dann gilt: Es werden nur die niederwertigen i Bits verwendet Sei m 2 i -1, dann gilt: Bei Umwandlung aus Zeichenketten erhalten alle Permutationen eines Begriffs denselben Wert, z.b. h(abcd) h(dacb) Man wähle m 1,1n Folgerung: Man wähle m als Primzahl nicht in der Nähe einer Zweierpotenz Beispiel: n 500, m 551 47

8. Hashing 8.5 Kollisionsbehandlung Was tun im Falle einer Kollision? 1. Möglichkeit: Verkettung (Chaining): Jedes Tabellenfeld kann beliebig viele Elemente aufnehmen (d.h. Listenbildung) T 0 k 10 k 6 k 1 k 9 k 7 k 3 k 2 k 4 1 k 3 k 4 k 2 k 5 k 5 k 1 m-1 48

8. Hashing Pseudo-Code void insert (x) // insert x in Hash Table i h(x); // derive hash value if (H[i] NULL) // entry not yet used H[i] ListCreate(x); else H[i] ListAppend(H[i], x); bool ismember(x) // look for x in Ha sh Table list H[h(x)]; // determ ine linked list } if (ListFound(list, x)) // found x return true; else // not found return false; 49

8. Hashing Beispiel: const int M3;... struct element *H[M]; // ha sh ta ble int h(int x) { // ha sh fu nction return x%m; } Aufgabe: Zeichnen Sie (schrittweise) die Hash-Tabelle bei Eingabe von: 10, 3, 1, 4, 21, 7, 11 50

8. Hashing Kollisionsbehandlung Was tun im Falle einer Kollision? 2. Offene Adressierung: Jedes Tabellenfeld kann nur eine feste Anzahl b von Elementen aufnehmen (i.a. b1) D.h. es wird nach einer neuen Lücke gesucht Beispiel von eben 12 mod 13 12 1 mod 13 1 54 mod 13 2 40 mod 13 Analoger Ablauf für MEMBER & DELETE 1 0 1 2 3 4 5 6 7 8 9 10 11 12 12 1 54 40 51

8. Hashing Formell/praktisch erweitert man dabei die Hashfunktion zu einer Sondierungsfolge h: U x {0,...,m-1} {0,...,m-1} z.b.: Lineares Sondieren (Probing): h(x, i) : (h (x) + i) mod m Hash-Insert(T, Hash-Insert(T, k) k) i i 0 0 repeat repeat j j h(k,i) h(k,i) if if T[j] T[j] NIL NIL then then T[j] T[j] k k return return j j else else i i i+1 i+1 until until im im error error hash hash overflow overflow Hash-Search(T, Hash-Search(T, k) k) i i 0 0 repeat repeat j j h(k,i) h(k,i) if if T[j] T[j] k k then then return return j j i i i+1 i+1 until until T[j]NIL T[j]NIL or or im im return return NIL NIL 52

8. Hashing 8.6 Aufwand für Hashing Im Best-Case Keine Kollision O(1) Im Worst-Case Alle Elemente werden durchlaufen O(n) Im Average-Case Es lässt sich formal beweisen, dass gilt: O(1) Achtung: im Falle einer schlechten Hash-Funktion, tritt der Worst-Case häufig ein!! 53

8. Hashing Experimentelle Resultate 60 Laufzeitverhalten: Sortiertes Array vs. Hashing 50 40 Zeit in s 30 Sortiertes Array Hashing 20 10 0 0 100000 200000 300000 400000 500000 600000 700000 800000 900000 1000000 Größe n 54

8. Hashing 8.7 Erweiterungs- und Veränderungsmöglichkeiten Andersartige Hash-Funktionen Multiplikationsmethode: man multipliziere mit einer Konstanten und extrahiere Nachkommastellen Universelles Hashing: Kombination mehrerer Hash-Funktionen Verschiedenartige Offene Adressierung: ( im Vergleich zum Linearen Sondieren: h(x, i) : (h (x) + i) mod m ) Quadratisches Sondieren: h(x, i) : (h (x) + c 1 i + c 2 i 2 ) mod m Double Hashing: h(x, i) : (h 1 (x) + i*h 2 (x)) mod m 55

8. Hashing Andersartige Lösungen Dictionaries lassen sich auch durch sogenannte Balancierte Suchbäume implementieren Vorteil: Für den Worst-Case kann eine Laufzeit von O(log n) garantiert werden Nachteil: kompliziert und viele technischen Details, d.h. schwierig zu implementieren Daher wird in der Praxis Hashing oft bevorzugt Weiterführende Themen: Bäume und Graphen 56

9. Assoziative STL-Container Die Templates [multi]set und [unordered_][multi]map werden assoziative Container genannt, da sie einen Wert mit einem Schlüssel assoziieren d.h. verknüpfen. Sie werden im Allgemeinen benutzt, um die Suche nach Daten zu unterstützen. Achtung: Assoziative Container haben einen schwer zu kalkulierenden Aufwand Und können sehr speicherintensiv sein! 9.1 set Eine Menge (set) kann ein Element nur einmal enthalten. Sollen mehrere Elemente in einer Menge enthalten sein, die den selben Wert repräsentieren, so kann ein [multi]set verwendet werden. Das folgende Beispiel zeigt beide Möglichkeiten. 57

9. Assoziative STL-Container #include <iostream> #include <set> using namespace std; template<typename container> void test(container & T) { // einfügen T.insert(1); T.insert(42); T.insert(2); T.insert(1); // ausgeben for (typename container::iterator it T.begin(); it! T.end(); ++it) { cout << *it << endl; } cout << endl; return; } int main() { set <unsigned int> s; test(s); multiset <unsigned int> ms; test(ms); return 0; } 1 2 42 1 1 2 42 58

9. Assoziative STL-Container 9.2 map In einer map werden Schlüssel-Werte-Paare abgelegt: Ein solches Paar wird mit der Operation make_pair(key, element) gebildet Mit insert() wird das Paar in der map abgelegt. Mit find(key) kann danach in der map gesucht werden. Eine multimap erlaubt mehrere Einträge zu einem Schlüssel (d.h. ADT Dictionary). Das folgende Beispiel zeigt beide Möglichkeiten. 59

9. Assoziative STL-Container #include <stdlib.h> /* srand, rand */ #include <iostream> #include <map> using namespace std; template<typename Map> void test(map & M); int main() { map<unsigned int, string> m; test(m); multimap<unsigned int, string> mm; test(mm); } return 0; 60

9. Assoziative STL-Container template<typename Map> void test(map & M) { unsigned int tmp_knr rand(); // make_pair() verknüpft den Schlüssel mit den Daten (Kundennr mit Namen) M.insert(make_pair(tmp_knr, "Altenbernd, Peter")); tmp_knr rand(); / insert() fügt ein neu verknüpftes Paar ein M.insert(make_pair(tmp_knr, "Schütte, Alois")); / tmp_knr rand(); M.insert(make_pair(tmp_knr, "Lopez, Jennifer")); M.insert(make_pair(tmp_knr, "Lopez, Jennifer")); unsigned int knr3 tmp_knr; typename Map::iterator found_it M.find(knr3); // suche über Kundennummer if(found_it! M.end()) cout << (*found_it).second << " wieder gefunden!" << endl; // Ausgabe aller Elemente for(typename Map::iterator it M.begin(); it! M.end(); ++it) cout << (*it).first << ": " << (*it).second << endl; // first Schlüssel, second verknüpfte Daten cout << endl; } return; 61

9. Assoziative STL-Container Die Ausgabe erfolgt sortiert (über das Schlüssel-Element): Lopez, Jennifer wieder gefunden! 846930886: Schütte, Alois 1681692777: Lopez, Jennifer 1804289383: Altenbernd, Peter Lopez, Jennifer wieder gefunden! 424238335: Lopez, Jennifer 424238335: Lopez, Jennifer 1714636915: Altenbernd, Peter 1957747793: Schütte, Alois 62

9. Assoziative STL-Container 9.3 unordered_map Eine wirkliche Hash-Tabelle wird am ehesten durch eine unordered_map repräsentiert. Im Gegensatz zu einer normalen map ist die Ablage also unsortiert und der Zugriff erfolgt über eine (interne) Hash-Funktion. Bemerkung: Eine unordered_map gibt es erst seit dem C++-11-Standard. Eine unordered_multimap erlaubt mehrere Einträge zu einem Schlüssel. Beide Möglichkeiten ergänzen das Beispiel von eben. 63

9. Assoziative STL-Container #include <unordered_map> int main() { unordered_map<unsigned int, string> um; // nur C++-11 test(um); // ggf. mit eigener Hash-Funktion - nur C++-11 unordered_multimap<unsigned int, string> umm; test(umm); } return 0; Lopez, Jennifer wieder gefunden! 719885386: Altenbernd, Peter 596516649: Lopez, Jennifer 1649760492: Schütte, Alois Lopez, Jennifer wieder gefunden! 1189641421: Altenbernd, Peter 1350490027: Lopez, Jennifer 1350490027: Lopez, Jennifer 1025202362: Schütte, Alois 64