Rekursion. Rekursive Funktionen, Korrektheit, Terminierung, Rekursion vs. Iteration, Sortieren

Ähnliche Dokumente
Mathematische Rekursion. Rekursion. Rekursion in C++ Mathematische Rekursion. Definition. 1, falls n 1. n! = n (n-1)!, falls n > 1

Algorithmen und Datenstrukturen

Übersicht. Datenstrukturen und Algorithmen Vorlesung 5: Rekursionsgleichungen (K4) Übersicht. Binäre Suche. Joost-Pieter Katoen. 20.

Abschnitt: Algorithmendesign und Laufzeitanalyse

Speicher und Adressraum

Algorithmen & Programmierung. Rekursive Funktionen (1)

JAVA - Suchen - Sortieren

Kapitel 6 Elementare Sortieralgorithmen

Einstieg in die Informatik mit Java

Kostenmaße. F3 03/04 p.188/395

11. Rekursion, Komplexität von Algorithmen

Programmiertechnik II

HEUTE. Datenstrukturen im Computer. Datenstrukturen. Rekursion. Feedback Evaluation. abstrakte Datenstrukturen

Datenstrukturen und Algorithmen

Komplexität von Algorithmen

Programmier-Befehle - Woche 10

Beispiel: Fibonacci-Zahlen

Dynamische Programmierung. Problemlösungsstrategie der Informatik

Entscheidungsbäume. Definition Entscheidungsbaum. Frage: Gibt es einen Sortieralgorithmus mit o(n log n) Vergleichen?

16. All Pairs Shortest Path (ASPS)

Technische Informatik 1 Übung 2 Assembler (Rechenübung) Georgia Giannopoulou (ggeorgia@tik.ee.ethz.ch) 22./23. Oktober 2015

Programmieren I. Kapitel 7. Sortieren und Suchen

Funktionale Programmierung. Funktionale Programmierung: Vorlesungsüberblick. Eigenschaften rein funktionaler Programmierung

Übungsblatt 1. f(n) = f(n) = O(g(n)) g(n) = O(f(n)) Zeigen oder widerlegen Sie: 3 n = Θ(2 n ) Aufgabe 1.2 Gegeben sei die folgende Funktion:

damit hätten wir nach Ende der Schleife: "a[0 n-1] enthält nur Elemente aus a[0 n-1], aber in sortierter Reihenfolge".

Programmieren in C. Rekursive Funktionen. Prof. Dr. Nikolaus Wulff

Ein Algorithmus heißt rekursiv, wenn er sich selbst aufruft. Meist werden nur einzelne Module eines Gesamtalgorithmus rekursiv verwendet.

Einführung in die Informatik 2

Kapitel 9. Komplexität von Algorithmen und Sortieralgorithmen

JAVA - Rekursion

6 Speicherorganisation

13. Binäre Suchbäume

Algorithmen und Datenstrukturen

Theoretische Informatik SS 03 Übung 3

Kapitel 9. Komplexität von Algorithmen und Sortieralgorithmen

4. Fortgeschrittene Algorithmen 4.1 Rekursion 4.2 Daten und Datenstrukturen 4.3 Bäume

Rekursive Funktionen

Übersicht. Berechnung der Potenz für zwei ganze Zahlen Klausuraufgabe SS 2010! Berechnung der Cosinus-Funktion Klausuraufgabe WS 2010/2011!

Babeș-Bolyai Universität Cluj Napoca Fakultät für Mathematik und Informatik Grundlagen der Programmierung MLG5005. Rekursion

Algorithmen. Sortieren durch Auswählen, Sortieren durch Mischen und Vergleich der Laufzeit. Abschätzung der Laufzeit eines Algorithmus, O-Notation.

II.3.1 Rekursive Algorithmen - 1 -

Student: Alexander Carls Matrikelnummer: Aufgabe: Beschreibung des euklidischen Algorithmus Datum:

Programmierung 2. Dynamische Programmierung. Sebastian Hack. Klaas Boesche. Sommersemester

Prüfung Informatik D-MATH/D-PHYS :00 11:00

Vorkurs Informatik WiSe 16/17

12. Rekursion Grundlagen der Programmierung 1 (Java)

Viel Spaÿ! Aufgabe 0.1. Laufzeit unter Verdoppelung (-)

Übersicht. Datenstrukturen und Algorithmen. Übersicht. Divide-and-Conquer. Vorlesung 9: Quicksort (K7)

Rekursion. Beispiel Fakultät (iterativ) Rekursive Java-Implementierung. Beispiel Fakultät (rekursiv) n! = n

Effizienz von Algorithmen

SOI Die Schweizer Informatikolympiade

Einführung in die Informatik I

Einführung in die Programmierung

Datenstrukturen und Algorithmen

Klausur C-Programmierung / / Klingebiel / 60 Minuten / 60 Punkte

Der linke Teilbaum von v enthält nur Schlüssel < key(v) und der rechte Teilbaum enthält nur Schlüssel > key(v)

Sortierverfahren. Sortierverfahren für eindimensionale Arrays

2. Felder (Arrays) 2.1 Suchen in Feldern. lineares Suchen: siehe Kapitel 1. Binäres Suchen. Vor.: Elemente (z.b. aufsteigend) sortiert

Allgemeine Hinweise: TECHNISCHE UNIVERSITÄT MÜNCHEN. Name Vorname Studiengang Matrikelnummer. Hörsaal Reihe Sitzplatz Unterschrift

1. Grundlagen Sortieren Vertauschen Selektion Einfügen Quicksort Suchen...

Einführung in die Informatik I

Technische Universität Wien Institut für Computergraphik und Algorithmen Arbeitsbereich für Algorithmen und Datenstrukturen

C.3 Funktionen und Prozeduren

Algorithms & Datastructures Midterm Test 1

Informatik II Greedy-Algorithmen

Algorithmen II Vorlesung am

Beispiellösungen zu den Übungen Datenstrukturen und Algorithmen SS 2008 Blatt 6

Primitive Rekursion. Basisfunktionen: Konstante Funktion: const 3 3 (1,1, pr 1,3(g,h) (1,1)) Projektion: proj 3 (1,1, pr. Komposition: comp 3,2

Grundlagen der Programmierung

Diskrete Strukturen Kapitel 2: Grundlagen (Beweise)

1.2 LOOP-, WHILE- und GOTO-Berechenbarkeit

11.1 Grundlagen - Denitionen

Rekursion. Annabelle Klarl. Einführung in die Informatik Programmierung und Softwareentwicklung

Grundlagen der Programmierung in C Funktionen

1. Probeklausur zu Programmierung 1 (WS 07/08)

Einführung in die Informatik 1

Typdeklarationen. Es gibt in Haskell bereits primitive Typen:

Prof. Dr. Margarita Esponda

Übungen zu Programmierung I - Blatt 8

Sortierverfahren für Felder (Listen)

Algorithmen und Datenstrukturen Tafelübung 4. Jens Wetzl 15. November 2011

Grundlagen der Künstlichen Intelligenz

Rekursion. Selbstbezug, rekursive Funktionen, rekursive Prozeduren, Terminierung, Effizienz, Korrektheit, Rekursion und Induktion

Informatik II - Übung 11

Algorithmen und Datenstrukturen Laufzeitabschätzung

Übung 9 - Lösungsvorschlag

Vorlesung Datenstrukturen

2 Sortieren. Beispiel: Es seien n = 8 und a = i : a i : ϕ(i) : a ϕ(i) :

Praktische Informatik I Der Imperative Kern Rekursive Funktionen

Programmierkurs Python I

Termine für Übungstests. Kap. 3 Sortieren HeapSort ff Priority Queues. Motivation. Überblick. Analyse SiftDown

Suchen und Sortieren Sortieren. Heaps

Einstieg in die Informatik mit Java

Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung)

Kapitel 5: Abstrakte Algorithmen und Sprachkonzepte. Elementare Schritte

Großübung zu Einführung in die Programmierung

Advanced Programming in C

Sortieralgorithmen. Inhalt: InsertionSort BubbleSort QuickSort. Marco Block

Modellierung und Programmierung

UE Algorithmen und Datenstrukturen 1 UE Praktische Informatik 1. Übung 5. Asymptotische Laufzeitkomplexität Definition Regeln Beispiele

Transkript:

Rekursion Rekursive Funktionen, Korrektheit, Terminierung, Rekursion vs. Iteration, Sortieren

Mathematische Rekursion o Viele mathematische Funktionen sind sehr natürlich rekursiv definierbar, d.h. o die Funktion erscheint in ihrer eigenen Definition.

Mathematische Rekursion o Viele mathematische Funktionen sind sehr natürlich rekursiv definierbar, d.h. o die Funktion erscheint in ihrer eigenen Definition. n! = 1, falls n 1 n (n-1)!, falls n > 1

Rekursion in C++ o modelliert oft direkt die mathematische Rekursionsformel

Rekursion in C++ o modelliert oft direkt die mathematische Rekursionsformel. // POST: return value is n! unsigned int fac (const unsigned int n) { } if (n <= 1) return 1; return n * fac(n-1); // n > 1

Unendliche Rekursion o ist so leicht zu erzeugen wie eine unendliche Schleife, o sollte aber genauso vermieden werden.

Unendliche Rekursion o ist so leicht zu erzeugen wie eine unendliche Schleife, o sollte aber genauso vermieden werden. void f() { f(); // calls f(), calls f(), calls f(),... }

Terminierung von rekursiven Funktionsaufrufen Wie bei Schleifen brauchen wir o Fortschritt Richtung Terminierung fac(n) : terminiert sofort für n 1, andernfalls wird die Funktion rekursiv mit Argument < n aufgerufen.

Terminierung von rekursiven Funktionsaufrufen Wie bei Schleifen brauchen wir o Fortschritt Richtung Terminierung fac(n) : terminiert sofort für n 1, andernfalls wird die Funktion rekursiv mit Argument < n aufgerufen. n wird mit jedem Aufruf kleiner.

Auswertung rekursiver Funktionsaufrufe (z.b. fac(3)) // POST: return value is n! unsigned int fac (const unsigned int n) { if (n <= 1) return 1; return n * fac(n-1); // n > 1 }

Auswertung rekursiver Funktionsaufrufe (z.b. fac(3)) // POST: return value is n! unsigned int fac (const unsigned int n) { // n = 3 if (n <= 1) return 1; return n * fac(n-1); // n > 1 } Initialisierung des formalen Arguments mit dem Wert des Aufrufarguments

Auswertung rekursiver Funktionsaufrufe (z.b. fac(3)) // POST: return value is n! unsigned int fac (const unsigned int n) { // n = 3 if (n <= 1) return 1; return n * fac(n-1); // n > 1 } Ausführen des Funktionsrumpfs: Auswertung des Rückgabeausdrucks

Auswertung rekursiver Funktionsaufrufe (z.b. fac(3)) // POST: return value is n! unsigned int fac (const unsigned int n) { // n = 3 if (n <= 1) return 1; return n * fac(n-1); // n > 1 } Ausführen des Funktionsrumpfs: Rekursiver Aufruf von fac mit Aufrufargument n-1 == 2

Auswertung rekursiver Funktionsaufrufe (z.b. fac(3)) // POST: return value is n! unsigned int fac (const unsigned int n) { // n = 2 if (n <= 1) return 1; return n * fac(n-1); // n > 1 } Initialisierung des formalen Arguments mit dem Wert des Aufrufarguments

Auswertung rekursiver Funktionsaufrufe (z.b. fac(3)) // POST: return value is n! unsigned int fac (const unsigned int n) { // n = 2 if (n <= 1) return 1; return n * fac(n-1); // n > 1 } Es gibt jetzt zwei n! Das von fac (3), und das von fac (2) Initialisierung des formalen Arguments mit dem Wert des Aufrufarguments

Auswertung rekursiver Funktionsaufrufe (z.b. fac(3)) // POST: return value is n! unsigned int fac (const unsigned int n) { // n = 2 if (n <= 1) return 1; return n * fac(n-1); // n > 1 } Wir nehmen das Argument des aktuellen Aufrufs, fac (2) Initialisierung des formalen Arguments mit dem Wert des Aufrufarguments

Der Aufrufstapel Bei Auswertung jedes Funktionsaufrufs: o Wert des Aufrufarguments kommt auf einen Stapel (erst n = 3, dann n = 2,...) o es wird stets mit dem obersten Wert gearbeitet o nach Ende des Funktionsaufrufs wird der oberste Wert vom Stapel gelöscht

Der Aufrufstapel Bei Auswertung jedes Funktionsaufrufs: o Wert des Aufrufarguments kommt auf einen Stapel (erst n = 3, dann n = 2,...) o es wird stets mit dem obersten Wert gearbeitet o nach Ende des Funktionsaufrufs wird der oberste Wert vom Stapel gelöscht Bei mehreren Argumenten analog (alle kommen auf den Stapel, wir arbeiten jeweils mit der obersten Version jedes Arguments)

Grösster gemeinsamer Teiler Euklidischer Algorithmus o findet den grössten gemeinsamen Teiler gcd (a, b) zweier natürlicher Zahlen a und b

Grösster gemeinsamer Teiler Euklidischer Algorithmus o findet den grössten gemeinsamen Teiler gcd (a, b) zweier natürlicher Zahlen a und b o basiert auf folgendem Lemma (Beweis im Skript): gcd (a, b) = gcd (b, a mod b) für b > 0.

Grösster gemeinsamer Teiler // POST: return value is the greatest common // divisor of a and b unsigned int gcd { } (const unsigned int a, const unsigned int b) if (b == 0) return a; return gcd(b, a % b); // b!= 0

Grösster gemeinsamer Teiler // POST: return value is the greatest common // divisor of a and b unsigned int gcd { } (const unsigned int a, const unsigned int b) if (b == 0) return a; return gcd(b, a % b); // b!= 0 Korrektheit: Lemma! gcd (a, 0) = a gcd (a, b) = gcd (b, a mod b), b > 0

Grösster gemeinsamer Teiler // POST: return value is the greatest common // divisor of a and b unsigned int gcd { } (const unsigned int a, const unsigned int b) if (b == 0) return a; return gcd(b, a % b); // b!= 0 Terminierung: a mod b < b, also wird b in jedem rekursiven Aufruf kleiner.

Grösster gemeinsamer Teiler // POST: return value is the greatest common // divisor of a and b unsigned int gcd { } (const unsigned int a, const unsigned int b) if (b == 0) return a; return gcd(b, a % b); // b!= 0 Korrektheit und Terminierung müssen bei rekursiven Funktionen stets separat bewiesen werden!

Fibonacci-Zahlen o F 0 := 0 o F 1 := 1 o F n := F n-1 + F n-2, n > 1

Fibonacci-Zahlen o F 0 := 0 o F 1 := 1 o F n := F n-1 + F n-2, n > 1 0, 1, 1, 2, 3, 5, 8, 13, 21,...

Fibonacci-Zahlen o F 0 := 0 o F 1 := 1 o F n := F n-1 + F n-2, n > 1 0, 1, 1, 2, 3, 5, 8, 13, 21,...

Fibonacci-Zahlen // POST: return value is the n-th // Fibonacci number F_n unsigned int fib (const unsigned int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n-1) + fib(n-2); // n > 1 }

Fibonacci-Zahlen // POST: return value is the n-th // Fibonacci number F_n unsigned int fib (const unsigned int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n-1) + fib(n-2); // n > 1 } Korrektheit und Terminierung sind klar.

Fibonacci-Zahlen // POST: return value is the n-th // Fibonacci number F_n unsigned int fib (const unsigned int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n-1) + fib(n-2); // n > 1 } Laufzeit: fib (50) dauert ewig, denn es berechnet F 48 2-mal, F 47 3-mal, F 46 5-mal, F 45 8-mal, F 44 13-mal,...

Rekursion und Iteration Rekursion kann im Prinzip ersetzt werden durch o o Iteration (Schleifen) und expliziten Aufrufstapel (z.b. Feld). Oft sind direkte rekursive Formulierungen ein- facher, aber manchmal auch weniger effizient.

Endrekursion Eine Funktion ist endrekursiv, wenn sie genau einen rekursiven Aufruf ganz am Ende enthält.

Endrekursion Eine Funktion ist endrekursiv, wenn sie genau einen rekursiven Aufruf ganz am Ende enthält. // POST: return value is the greatest common // divisor of a and b unsigned int gcd (const unsigned int a, const unsigned int b) { if (b == 0) return a; return gcd(b, a % b); // b!= 0 }

Endrekursion o ist leicht iterativ zu schreiben. // POST: return value is the greatest common divisor of a and b unsigned int gcd2 (unsigned int a, unsigned int b) { while (b!= 0) { } unsigned int a_prev = a; a = b; b = a_prev % b; } return a;

Endrekursion o ist leicht iterativ zu schreiben. // POST: return value is the greatest common divisor of a and b unsigned int gcd2 (unsigned int a, unsigned int b) { while (b!= 0) { } unsigned int a_prev = a; a = b; b = a_prev % b; } return a; (a,b) (b, a mod b)

Endrekursion o ist leicht iterativ zu schreiben. // POST: return value is the greatest common divisor of a and b unsigned int gcd2 (unsigned int a, unsigned int b) { while (b!= 0) { } unsigned int a_prev = a; a = b; b = a_prev % b; } return a; (a,b) (b, a mod b)

Endrekursion o ist leicht iterativ zu schreiben. // POST: return value is the greatest common divisor of a and b unsigned int gcd2 (unsigned int a, unsigned int b) { while (b!= 0) { } unsigned int a_prev = a; a = b; b = a_prev % b; } return a; (a,b) (b, a mod b)

Endrekursion o ist leicht iterativ zu schreiben. // POST: return value is the greatest common divisor of a and b unsigned int gcd2 (unsigned int a, unsigned int b) { while (b!= 0) { } unsigned int a_prev = a; a = b; b = a_prev % b; } return a; (a,b) (b, a mod b)...aber die rekursive Version is lesbarer und (fast) genauso effizient.

Fibonacci-Zahlen iterativ Idee: o berechne jede Zahl genau einmal, in der Reihenfolge F 0, F 1, F 2, F 3,... o speichere die jeweils letzten beiden berechneten Zahlen (Variablen a, b), dann kann die nächste Zahl durch eine Addition erhalten werden.

Fibonacci-Zahlen iterativ // POST: return value is the n-th Fibonacci number F_n unsigned int fib2 (const unsigned int n) { if (n == 0) return 0; if (n <= 2) return 1; unsigned int a = 1; // F_1 unsigned int b = 1; // F_2 for (unsigned int i = 3; i <= n; ++i) { unsigned int a_prev = a; // F_{i-2} a = b; // F_{i-1} } b += a_prev; } return b; // F_{i-1} += F_{i-2} -> F_i

Fibonacci-Zahlen iterativ // POST: return value is the n-th Fibonacci number F_n unsigned int fib2 (const unsigned int n) { if (n == 0) return 0; if (n <= 2) return 1; unsigned int a = 1; // F_1 unsigned int b = 1; // F_2 for (unsigned int i = 3; i <= n; ++i) { unsigned int a_prev = a; // F_{i-2} a = b; // F_{i-1} b += a_prev; // F_{i-1} += F_{i-2} -> F_i } return b; } ( F i-2, F i-1 ) ( F i-1, F i ) a b

Fibonacci-Zahlen iterativ // POST: return value is the n-th Fibonacci number F_n unsigned int fib2 (const unsigned int n) { if (n == 0) return 0; if (n <= 2) return 1; unsigned int a = 1; // F_1 unsigned int b = 1; // F_2 for (unsigned int i = 3; i <= n; ++i) { unsigned int a_prev = a; // F_{i-2} a = b; // F_{i-1} b += a_prev; // F_{i-1} += F_{i-2} -> F_i } return b; } ( F i-2, F i-1 ) ( F i-1, F i ) a b

Fibonacci-Zahlen iterativ // POST: return value is the n-th Fibonacci number F_n unsigned int fib2 (const unsigned int n) { if (n == 0) return 0; if (n <= 2) return 1; unsigned int a = 1; // F_1 unsigned int b = 1; // F_2 for (unsigned int i = 3; i <= n; ++i) { unsigned int a_prev = a; // F_{i-2} a = b; // F_{i-1} b += a_prev; // F_{i-1} += F_{i-2} -> F_i } return b; } ( F i-2, F i-1 ) ( F i-1, F i ) a b

Fibonacci-Zahlen iterativ // POST: return value is the n-th Fibonacci number F_n unsigned int fib2 (const unsigned int n) { if (n == 0) return 0; sehr schnell auch bei fib2(50) if (n <= 2) return 1; unsigned int a = 1; // F_1 unsigned int b = 1; // F_2 for (unsigned int i = 3; i <= n; ++i) { unsigned int a_prev = a; // F_{i-2} a = b; // F_{i-1} b += a_prev; // F_{i-1} += F_{i-2} -> F_i } return b; } ( F i-2, F i-1 ) ( F i-1, F i ) a b

Die Ackermann-Funktion n + 1, falls m = 0 A (m, n) = A (m-1, 1), falls m > 0, n = 0 A (m-1, A (m, n-1)), falls m > 0, n > 0

Die Ackermann-Funktion n + 1, falls m = 0 A (m, n) = A (m-1, 1), falls m > 0, n = 0 A (m-1, A (m, n-1)), falls m > 0, n > 0 o o ist berechenbar, aber nicht primitiv rekursiv (man dachte Anfang des 20. Jahrhunderts, dass es diese Kombination gar nicht gibt) wächst extrem schnell

Die Ackermann-Funktion // POST: return value is the Ackermann function // value A(m,n) unsigned int A } (const unsigned int m, const unsigned int n) { if (m == 0) return n+1; if (n == 0) return A(m-1,1); return A(m-1, A(m, n-1));

Die Ackermann-Funktion 0 1 2 3... n 0 1 2 3 4... n+1 m 1 2 3 4 n

Die Ackermann-Funktion 0 1 2 3... n 0 1 2 3 4... n+1 m 1 2 3 4 5... n+2 2 3 4 n

Die Ackermann-Funktion 0 1 2 3... n 0 1 2 3 4... n+1 m 1 2 3 4 5... n+2 2 3 5 7 9... 2n+3 3 4 n

Die Ackermann-Funktion 0 1 2 3... n 0 1 2 3 4... n+1 m 1 2 3 4 5... n+2 2 3 5 7 9... 2n+3 3 5 13 29 61... 2 n+3-3 4 n

Die Ackermann-Funktion 0 1 2 3... n 0 1 2 3 4... n+1 m 1 2 3 4 5... n+2 2 3 5 7 9... 2n+3 3 5 13 29 61... 2 n+3-3 2 65536 4 13 65533 2 65536-3 2-3... 2 2 2... -3 n Turm von n+3 Zweierpotenzen

Die Ackermann-Funktion 0 1 2 3... n 0 1 2 3 4... n+1 m 1 2 3 4 5... n+2 2 3 5 7 9... 2n+3 3 5 13 29 61... 2 n+3-3 2 65536 4 13 65533 2 65536-3 2-3... 2 2 2... -3 n nicht mehr praktisch berechenbar

Die Ackermann-Funktion - Moral o Rekursion ist sehr mächtig... o... aber auch gefährlich:

Die Ackermann-Funktion - Moral o Rekursion ist sehr mächtig... o... aber auch gefährlich: Es ist leicht, harmlos aussehende rekursive Funktionen hinzuschreiben, die theoretisch korrekt sind und terminieren, praktisch aber jeden Berechenbarkeitsrahmen sprengen.

Sortieren n Wie schnell kann man n Zahlen (oder Wörter,...) aufsteigend sortieren?

Sortieren n Wie schnell kann man n Zahlen (oder Wörter,...) aufsteigend sortieren? n Wir haben in den Übungen bereits sortiert, uns aber keine Gedanken über die Effizienz gemacht

Sortieren n Wie schnell kann man n Zahlen (oder Wörter,...) aufsteigend sortieren? n Wir haben in den Übungen bereits sortiert, uns aber keine Gedanken über die Effizienz gemacht n Das holen wir jetzt nach, denn Sortieren ist eine der grundlegenden Operationen in der Informatik

Minimum-sort n ist ein sehr einfaches Sortierverfahren n haben einige wahrscheinlich in den Übungen implementiert

Minimum-sort n ist ein sehr einfaches Sortierverfahren n haben einige wahrscheinlich in den Übungen implementiert 8 6 2 5 4 1 7 3 Finde die kleinste Zahl...

Minimum-sort n ist ein sehr einfaches Sortierverfahren n haben einige wahrscheinlich in den Übungen implementiert 8 6 2 5 4 1 7 3 1 6 2 5 4 8 7 3...und vertausche sie mit der ersten Zahl.

Minimum-sort n ist ein sehr einfaches Sortierverfahren n haben einige wahrscheinlich in den Übungen implementiert 1 6 2 5 4 8 7 3 Finde die kleinste Zahl...

Minimum-sort n ist ein sehr einfaches Sortierverfahren n haben einige wahrscheinlich in den Übungen implementiert 1 6 2 5 4 8 7 3 1 2 6 5 4 8 7 3...und vertausche sie mit der ersten Zahl.

Minimum-sort n ist ein sehr einfaches Sortierverfahren n haben einige wahrscheinlich in den Übungen implementiert 1 2 6 5 4 8 7 3 Finde die kleinste Zahl...

Minimum-sort n ist ein sehr einfaches Sortierverfahren n haben einige wahrscheinlich in den Übungen implementiert 1 2 6 5 4 8 7 3 Finde die kleinste Zahl... usw...

Minimum-sort // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); p last } } 1 2 6 5 4 8 7 3 Finde die kleinste Zahl...

Minimum-sort // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); p p_min } } 1 2 6 5 4 8 7 3..und vertausche sie mit der ersten Zahl

Minimum-sort // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); p p_min } } 1 2 3 5 4 8 7 6..und vertausche sie mit der ersten Zahl

Minimum-sort // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); p p_min } } 1 2 3 5 4 8 7 6 #include<algorithm>..und vertausche sie mit der ersten Zahl

Minimum-sort: Laufzeit n hängt von der Plattform ab.

Minimum-sort: Laufzeit n hängt von der Plattform ab n Trotzdem gibt es ein plattformunabhängiges Laufzeitmass: Anzahl der Vergleiche *q < *p_min

Minimum-sort: Laufzeit n hängt von der Plattform ab n Trotzdem gibt es ein plattformunabhängiges Laufzeitmass: Anzahl der Vergleiche *q < *p_min n Anzahl anderer Operationen ist wesentlich kleiner oder proportional dazu

Minimum-sort: Laufzeit // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { n Elemente for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); } } n - 1 + n - 2 +... + 1 Vergleiche = n (n -1) / 2

Minimum-sort: Laufzeit // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { n Elemente for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); } } jeweils n Operationen (wesentlich kleiner)

Minimum-sort: Laufzeit // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { n Elemente for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); } } höchstens n (n -1) / 2 Operationen (proportional)

Minimum-sort: Laufzeit // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void minimum_sort (int* first, int* last) { n Elemente for (int* p = first; p!= last; ++p) { // find minimum in nonempty range described by [p, last) int* p_min = p; // pointer to current minimum int* q = p; // pointer to current element while (++q!= last) if (*q < *p_min) p_min = q; // interchange *p with *p_min std::iter_swap (p, p_min); } } n (n -1) / 2 + n Operationen (proportional)

Minimum-sort: Laufzeit n Auf jeder Plattform: Gesamtlaufzeit ist proportional zur Zeit, die mit den Vergleichen *q < *p_min verbracht wird

Minimum-sort: Laufzeit n Auf jeder Plattform: Gesamtlaufzeit ist proportional zur Zeit, die mit den Vergleichen *q < *p_min verbracht wird n Diese wiederum ist proportional zur Anzahl n (n 1) / 2 dieser Vergleiche

Minimum-sort: Laufzeit n Auf jeder Plattform: Gesamtlaufzeit ist proportional zur Zeit, die mit den Vergleichen *q < *p_min verbracht wird n Diese wiederum ist proportional zur Anzahl n (n 1) / 2 dieser Vergleiche n Anzahl der Vergleiche ist deshalb ein gutes Mass für die Gesamtlaufzeit!

Minimum-sort: Laufzeit (Mac) auf zufälliger Eingabe n 100,000 200,000 400,000 800,00 0 1,600,000 Gcomp (Milliarden Vergleiche) Laufzeit in min : s Laufzeit / GComp (s) 5 20 80 320 1280 0:15 1:05 4:26 15:39 64:22 3.0 3.25 3.325 2.93 3.01

Minimum-sort: Laufzeit (Mac) auf zufälliger Eingabe n 100,000 200,000 400,000 800,00 0 1,600,000 Gcomp (Milliarden Vergleiche) Laufzeit in min : s Laufzeit / GComp (s) 5 20 80 320 1280 0:15 1:05 4:26 15:39 64:22 3.0 3.25 3.325 2.93 3.01 Ungefähr konstant! Anzahl Vergleiche ist ein gutes Laufzeitmass!

Minimum-sort: Laufzeit (Mac) auf zufälliger Eingabe n 100,000 200,000 400,000 800,00 0 1,600,000 Gcomp (Milliarden Vergleiche) Laufzeit in min : s Laufzeit / GComp (s) 5 20 80 320 1280 0:15 1:05 4:26 15:39 64:22 3.0 3.25 3.325 2.93 3.01 D.h.: für 10,000,000 Zahlen bräuchte Minimum-sort ca. 2 Tage!

Merge-sort n ein schnelleres (und rekursives) Sortierverfahren n folgt dem Paradigma Teile und Herrsche (Divide & Conquer) Teile das Problem in kleinere Teilprobleme des gleichen Typs auf, löse diese rekursiv, und berechne die Gesamtlösung aus den Lösungen der Teilprobleme

Merge-sort n Analogie: Sortieren eines Kartenstapels n Karten sind mit Zahlen beschriftet

Merge-sort: Teile n Analogie: Sortieren eines Kartenstapels n Karten sind mit Zahlen beschriftet Schritt 1: Teile den Stapel in zwei gleich grosse Stapel

Merge-sort: Teile n Analogie: Sortieren eines Kartenstapels n Karten sind mit Zahlen beschriftet Schritt 2: Sortiere beide Stapel getrennt

Merge-sort: Herrsche n Analogie: Sortieren eines Kartenstapels n Karten sind mit Zahlen beschriftet Schritt 3: Mische die beiden sortierten Stapel zu einem sortierten Stapel: lege dazu jeweils die kleinere der beiden oberen Karten umgedreht auf einen neuen Stapel

Merge-sort in C++ // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void merge_sort (int* first, int* last) { int n = last - first; if (n <= 1) return; // nothing to do int* middle = first + n/2; merge_sort (first, middle); // sort first half merge_sort (middle, last); // sort second half merge (first, middle, last); // merge both halfs }

Merge-sort in C++: Teile // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void merge_sort (int* first, int* last) { int n = last - first; if (n <= 1) return; // nothing to do int* middle = first + n/2; merge_sort (first, middle); // sort first half merge_sort (middle, last); // sort second half merge (first, middle, last); // merge both halfs } n / 2 Elemente (n / 2 abgerundet) n / 2 Elemente (n / 2 aufgerundet)

Merge-sort in C++: Herrsche // PRE: [first, last) is a valid range // POST: the elements *p, p in [first, last) are in // ascending order void merge_sort (int* first, int* last) { int n = last - first; if (n <= 1) return; // nothing to do int* middle = first + n/2; merge_sort (first, middle); // sort first half merge_sort (middle, last); // sort second half merge (first, middle, last); // merge both halfs } Diese Funktion übernimmt das Mischen zu einem Stapel

Die Merge-Funktion // PRE: [first, middle), [middle, last) are valid ranges; in // both of them, the elements are in ascending order void merge (int* first, int* middle, int* last) { int n = last - first; // total number of cards int* deck = new int[n]; // new deck to be built int* left = first; // top card of left deck int* right = middle; // top card of right deck for (int* d = deck; d!= deck + n; ++d) // put next card onto new deck if (left == middle) *d = *right++; // left deck is empty else if (right == last) *d = *left++; // right deck is empty else if (*left < *right) *d = *left++; // smaller top card left else *d = *right++; // smaller top card right // copy new deck back into [first, last) int *d = deck; while (first!= middle) *first++ = *d++; while (middle!= last) *middle++ = *d++; } delete[] deck;

Merge-sort: Laufzeit n ist wieder proportional zur Anzahl der Vergleiche *left < *right (das muss man aber nicht sofort sehen) n Alle Vergleiche werden von der Funktion merge durchgeführt

Merge-sort: Laufzeit n ist wieder proportional zur Anzahl der Vergleiche *left < *right (das muss man aber nicht sofort sehen) n Alle Vergleiche werden von der Funktion merge durchgeführt Beim Mischen zweier Stapel zu einem Stapel der Grösse n braucht merge höchstens n -1 Vergleiche (maximal einer für jede Karte des neuen Stapels, ausser der letzten)

Merge-sort: Laufzeit Satz: Die Funktion merge_sort sortiert eine Folge von n 1 Zahlen mit höchstens (n 1) log 2 n Vergleichen zwischen zwei Zahlen.

Merge-sort: Laufzeit Beweis: T (n ) sei die maximal mögliche Anzahl von Vergleichen zwischen Zahlen, die beim Sortieren von n Zahlen mit merge_sort auftreten können.

Merge-sort: Laufzeit Beweis: T (n ) sei die maximal mögliche Anzahl von Vergleichen zwischen Zahlen, die beim Sortieren von n Zahlen mit merge_sort auftreten können. o T (0) = T (1) = 0

Merge-sort: Laufzeit Beweis: T (n ) sei die maximal mögliche Anzahl von Vergleichen zwischen Zahlen, die beim Sortieren von n Zahlen mit merge_sort auftreten können. o T (0) = T (1) = 0 o T (2) = 1

Merge-sort: Laufzeit Beweis: T (n ) sei die maximal mögliche Anzahl von Vergleichen zwischen Zahlen, die beim Sortieren von n Zahlen mit merge_sort auftreten können. o T (0) = T (1) = 0 o T (2) = 1 o T (3) = 2

Merge-sort: Laufzeit Beweis: T (n ) sei die maximal mögliche Anzahl von Vergleichen zwischen Zahlen, die beim Sortieren von n Zahlen mit merge_sort auftreten können. o T (n ) T ( n /2 ) + T ( n /2 ) + n -1 für n 2

Merge-sort: Laufzeit Beweis: T (n ) sei die maximal mögliche Anzahl von Vergleichen zwischen Zahlen, die beim Sortieren von n Zahlen mit merge_sort auftreten können. o T (n ) T ( n /2 ) + T ( n /2 ) + n -1 maximale Anzahl im linken Stapel maximale Anzahl im rechten Stapel maximale Anzahl beim Mischen für n 2

Merge-sort: Laufzeit o Mit vollständiger Induktion beweisen wir nun (für n 1) die Aussage T (n ) (n 1) log 2 n

Merge-sort: Laufzeit o Mit vollständiger Induktion beweisen wir nun (für n 1) die Aussage T (n ) (n 1) log 2 n o n=1: T (1) = 0 = (1-0) log 2 1

Merge-sort: Laufzeit o Sei nun n 2 und gelte die Aussage für alle Werte in {1,...,n -1} (Induktionsannahme)

Merge-sort: Laufzeit o Sei nun n 2 und gelte die Aussage für alle Werte in {1,...,n -1} (Induktionsannahme) o Zu zeigen ist, dass die Aussage dann auch für n gilt (Induktionsschritt)

Merge-sort: Laufzeit o Es gilt T (n ) T ( n /2 ) + T ( n /2 ) + n -1 1 und < n

Merge-sort: Laufzeit o Es gilt T (n ) T ( n /2 ) + T ( n /2 ) + n -1 ( n /2-1) log 2 n /2 + ( n /2-1) log 2 n /2 + n -1 Induktionsannahme

Merge-sort: Laufzeit o Es gilt T (n ) T ( n /2 ) + T ( n /2 ) + n -1 ( n /2-1) log 2 n /2 + ( n /2-1) log 2 n /2 + n -1 ( n /2-1) ( log 2 n - 1) + ( n /2-1) ( log 2 n - 1 )+ n -1 Aufgabe 122

Merge-sort: Laufzeit o Es gilt T (n ) T ( n /2 ) + T ( n /2 ) + n -1 ( n /2-1) log 2 n /2 + ( n /2-1) log 2 n /2 + n -1 ( n /2-1) ( log 2 n - 1) + ( n /2-1) ( log 2 n - 1 )+ n -1 = (n 2) ( log 2 n - 1) + n -1

Merge-sort: Laufzeit o Es gilt T (n ) T ( n /2 ) + T ( n /2 ) + n -1 ( n /2-1) log 2 n /2 + ( n /2-1) log 2 n /2 + n -1 ( n /2-1) ( log 2 n - 1) + ( n /2-1) ( log 2 n - 1 )+ n -1 (n 1) ( log 2 n - 1) + n -1

Merge-sort: Laufzeit o Es gilt T (n ) T ( n /2 ) + T ( n /2 ) + n -1 ( n /2-1) log 2 n /2 + ( n /2-1) log 2 n /2 + n -1 ( n /2-1) ( log 2 n - 1) + ( n /2-1) ( log 2 n - 1 )+ n -1 (n 1) ( log 2 n - 1) + n -1 = (n 1) ( log 2 n )

Merge-sort: Laufzeit (PC) auf zufälliger Eingabe n 100,000 200,000 400,000 800,00 0 1,600,000 Mcomp (Millionen Vergleiche) Laufzeit in ms Laufzeit / GComp (s) 1.7 3.6 7.6 16 33.6 46 93 190 390 834 27 25.8 25 24.4 25.1 merge_sort braucht 8-mal mehr Zeit pro Vergleich als minimum_sort...

Merge-sort: Laufzeit (PC) auf zufälliger Eingabe n 100,000 200,000 400,000 800,00 0 1,600,000 Mcomp (Millionen Vergleiche) Laufzeit in ms Laufzeit / GComp (s) 1.7 3.6 7.6 16 33.6 46 93 190 390 834 > 1h bei minimum_sort 27 25.8 25 24.4 25.1...ist aber aufgrund sehr viel weniger Vergleichen trotzdem sehr viel schneller!

Mit wievielen Vergleichen kann man n Zahlen sortieren? n minimum_sort: n(n-1)/2 n merge_sort: (n 1) ( log 2 n ) n optimal_sort:???

Mit wievielen Vergleichen kann man n Zahlen sortieren? n minimum_sort: n(n-1)/2 n merge_sort: (n 1) ( log 2 n ) n optimal_sort:??? Satz: jedes vergleichsbasierte Sortierverfahren braucht mindestens log 2 (n!) n log 2 n 1.44 n Vergleiche; also ist merge_sort fast optimal.

Mit wievielen Vergleichen kann man n Zahlen sortieren? n minimum_sort: n(n-1)/2 n merge_sort: (n 1) ( log 2 n ) n optimal_sort:??? Satz: jedes vergleichsbasierte Sortierverfahren braucht mindestens log 2 (n!) n log 2 n 1.44 n Vergleiche; also ist merge_sort fast optimal. Aber: Anzahl der Vergleiche ist nur bedingt geeignet zur Vorhersage praktischer Effizienz. quick_sort : braucht etwas mehr Vergleiche, ist aber schneller