Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Kapitel 11 Rekursio Rekursio 1
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Ziele Das Prizip der rekursive Berechugsvorschrift verstehe. was? Rekursive Methode i Java implemetiere köe. wie? Verschiedee Forme der Rekursio kee lere. welche Arte? Quicksort als rekursive Methode zur Sortierug eies Arrays formuliere köe ud verstehe. reales Beispiel? Rekursio 2
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Rekursive Algorithme ud Methode Ei Algorithmus ist rekursiv, we i seier (edliche) Beschreibug derselbe Algorithmus wieder aufgerufe wird. Der Algorithmus ist da selbstbezüglich defiiert. Rekursive Algorithme köe i Java durch rekursive Methode implemetiert werde. Eie Methode ist rekursiv, we i ihrem Rumpf (Aweisugsteil) die Methode selbst wieder aufgerufe wird. Rekursio
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Erläuteruge zu Folie Beispiel: Treppe hochgehe We keie Stufe mehr, da fertig. Asoste (d.h. es gibt och Stufe): steige eie Stufe hoch ud steige de Rest der Treppe hoch (d.h. wede de gleiche Algorithmus auf die kürzere Treppe a) Allgemeies Prizip: für eie eifache Fall weiß ma das Ergebis sofort -> Basisfall Asoste: Idee 1: Mache ei bissche Arbeit, um das Problem zu verkleier, ud wede de Algorithmus auf das kleiere Problem a -> Rekursio (d.h. eie rekursive Vorschrift verkleiert das Problem so lage, bis der Basisfall erreicht wird ud die Rekursio beedet wird bzw. termiiert) Idee 2: Nimm a, dass das Ergebis für ei kleieres Problem scho bekat ist, ud bereche daraus das Gesamtergebis Rekursio 4
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Rekursive Defiitio der Fakultät: 0! = 1 Die Fakultätsfuktio! = * (-1)! für alle atürliche Zahle 1 Rekursive Methode: public static it fact(it ) { } if ( == 0) retur 1; else retur * fact(-1); z.b.! = *2*1 =>! = * (-1) * * 1 z.b.! = *2! = *(2*1!) = *(2*(1*0!)) = *(2*(1*1)) = = 6 rekursiver Aufruf! (-1)! z.b. fact() = *fact(2) = *(2*fact(1)) = *(2*(1*fact(0)) = = *(2*(1*1)) = *(2*1) = *2 = 6 schrittweise (pro rek. Aufruf) ausmultipliziere Rekursio 5
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Auswertug rekursiver Methodeaufrufe Bei der Auswertug wird ei Stack für die Zwischeergebisse der geschachtelte Methodeaufrufe aufgebaut, der am Ede gemäß des Rekursiosschemas rückwärts abgearbeitet wird. Beispiel: it k = fact(); Speicherplatz für de Aufruf fact() k Parameter Speicherplatz für lokale Variable Parameter if (==0) retur 1; else retur *fact(-1); Speicherplatz für rekursive Aufruf fact(2) fact() k 2 *fact(2) s 0 s 1 Rekursio 6
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Aufbau des Stacks zur Berechug vo fact(2) Speicherplatz für rekursive Aufruf Parameter fact(1) fact(2) 2 if (==0) retur 1; else retur *fact(-1); fact(2) 1 2*fact(1) 2 fact() *fact(2) fact() *fact(2) k k s 1 s 2 Rekursio 7
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 fact(1) fact(2) fact() k Aufbau des Stacks zur Berechug vo fact(1) 1 2*fact(1) 2 *fact(2) Parameter if (==0) retur 1; else retur *fact(-1); Speicherplatz für rekursive Aufruf fact(0) fact(1) fact(2) fact() k 0 1*fact(0) 1 2*fact(1) 2 *fact(2) s 2 s Rekursio 8
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Berechug vo fact(0) Speicherplatz für Basisfall fact(0) 0 if (==0) retur 1; else retur *fact(-1); fact(0) 1 0 fact(1) 1*fact(0) fact(1) 1*fact(0) 1 1 fact(2) 2*fact(1) fact(2) 2*fact(1) 2 2 fact() *fact(2) fact() *fact(2) k k s s 4 Rekursio 9
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Berechug vo fact(1) ud Abbau des Stacks fact(0) 1 0 fact(1) 1*fact(0) 1 fact(2) 2*fact(1) 2 fact() *fact(2) k schrittweise ausmultipliziere fact(1)=1*fact(0); Speicherplatz für rekursive Aufruf fact(1) 1 1 fact(2) 2*fact(1) 2 fact() *fact(2) k s 4 s 5 Rekursio 10
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Berechug vo fact(2) ud Abbau des Stacks fact(1) fact(2) fact() k 1 1 2*fact(1) 2 *fact(2) fact(2)=2*fact(1); Speicherplatz für rekursive Aufruf fact(2) 2 2 fact() *fact(2) k s 5 s 6 Rekursio 11
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Berechug vo fact(), Abbau des Stacks ud Zuweisug des Ergebisses fact(2) fact() k 2 2 *fact(2) fact()=*fact(2); Speicherplatz für rekursive Aufruf fact() 6 k s 6 s 7 fact() 6 Speicherplatz für lokale Variable k k = fact(); k 6 s 7 s 8 Rekursio 12
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Termiierug Der Aufruf eier rekursive Methode termiiert, we ach edlich viele rekursive Aufrufe ei Abbruchfall erreicht wird. Beispiel: wichtig: sost edlose Berechug Für alle atürliche Zahle 0 termiiert der Methodeaufruf fact(). Für alle egative gaze Zahle < 0 termiiert der Methodeaufruf fact()icht. besser: static it fact(it ) { if(<0) retur -1; else if (==0) else } Rekursio 1
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Rekursio ud Iteratio (1) Zu jedem rekursive Algorithmus gibt es eie sematisch äquivalete iterative Algorithmus, d.h. eie Algorithmus mit Wiederholugsaweisuge, der dasselbe Problem löst. Beispiel: static it factiterativ(it ) { } Whl: 0! = 1! = *(-1)* *1 it result = 1; while (!= 0) { result = result * ; --; } retur result; z.b. factiterativ() 1 result für >= 0 gilt: factiterativ() = fact() Rekursio 14 2 6 1 6 0
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Rekursio ud Iteratio (2) Rekursive Algorithme sid häufig elegater ud übersichtlicher als iterative Lösuge. Gute Compiler köe aus rekursive Programme auch effiziete Code erzeuge; trotzdem sid iterative Programme meist scheller als rekursive. Für mache Problemstelluge ka es wesetlich eifacher sei eie rekursive Algorithmus azugebe als eie iterative. (z.b. Türme vo Haoi ; vgl. Übuge) siehe auch ZÜ (Lotto) Rekursio 15
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Fiboacci-Zahle: rekursive Defiitio ud Methode Rekursive Defiitio der Fiboacci-Zahle: fib(0) = 1, fib(1) = 1, fib() = fib(-2) + fib(-1) für alle atürliche Zahle 2 Rekursive Methode: static it fib(it ) { if ( <= 1) retur 1; else retur fib(-2) + fib(-1); } Deutug: fib() = Azahl der eu geboree Kaiche im Jahr. Aahme: Im Jahr 0 wird ei Paar gebore. -> fib(0) = 1 Im Jahr 1 hat dieses Paar ei eues Paar gebore. I jedem Jahr 2 habe die ei- ud zweijährige Paare jeweils ei eues Paar gebore -> fib() = fib(-1) + fib(-2) -> fib(1) = 1 Rekursio 16
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Kaskade rekursiver Aufrufe fib() fib(5) 8 fib(4) fib(1) fib(2) fib(2) fib() 1 2 2 fib(0) fib(1) fib(0) fib(1) fib(1) fib(2) 1 1 1 1 1 2 Die Zeit- ud die Speicherplatzkomplexitäte der rekursive Fiboacci-Fuktio sid i jedem Fall expoetiell, i O(2 ). Warum? Wir müsse -1 Schritte mache (siehe rechter Ast); i jedem Schritt wird die Azahl der rekursive Aufrufe verdoppelt. fib(0) fib(1) 1 1 Rekursio 17 5
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Fiboacci-Zahle: Iterative Methode static it fibiterativ(it ) { it f0 = 1; fib(0) it f1 = 1; fib(1) } it f = 1; for (it i = 2; i <= ; i++) { f = f0 + f1; fib() = fib(-2) + fib(-1) f0 = f1; f0 wird fib(-1) f1 = f; f1 wird fib() } retur f; eie for-schleife Die Zeitkomplexität der iterative Methode ist liear, d.h. i O(). Die Speicherplatzkomplexität der iterative Methode ist kostat, d.h. i O(1). feste Azahl vo lokale Variable Rekursio 18
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Forme der Rekursio Lieare Rekursio: I jedem Zweig (der Falluterscheidug) kommt höchstes ei rekursiver Aufruf vor, z.b. Fakultätsfuktio fact. Kaskadeartige Rekursio: Mehrere rekursive Aufrufe stehe ebeeiader ud sid durch Operatioe verküpft, z.b. Fiboacci-Zahle fib. Verschachtelte Rekursio: Rekursive Aufrufe komme i Parameter vo rekursive Aufrufe vor, z.b. Ackerma-Fuktio. d.h. geschachtelte rekursiveaufrufe Rekursio 19
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Die Ackerma-Fuktio static it ack(it, it m) { if ( == 0) retur m + 1; else if (m == 0) retur ack( - 1, 1); else retur ack( - 1, ack(, m - 1)); } Schachtelug Die Ackerma-Fuktio ist eie Fuktio mit expoetieller Zeitkomplexität, die extrem schell wächst. Sie ist das klassische Beispiel für eie berechebare, termiierede Fuktio, die icht primitiv-rekursiv ist (erfude 1926 vo Ackerma). Beispiele: ack(4,0) = 1 ack(4,1) = 655 ack(4,2) = 2 6556 - (eie Zahl mit 19729 Dezimalstelle). ack(4,4) > Azahl der Atome im Uiversum Rekursio 20
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Quicksort Eier der schellste Sortieralgorithme (vo C.A.R. Hoare, 1960). Idee: Falls das zu sortierede Array midestes zwei Elemete hat: 1. Wähle irgedei Elemet aus dem Array als Pivot ( Dreh- ud Agelpukt ), z.b. das erste Elemet. 2. Partitioiere das Array i eie like ud eie rechte Teil, so dass alle Elemete im like Teil kleier-gleich dem Pivot sid ud alle Elemete im rechte Teil größer-gleich dem Pivot sid.. Wede das Verfahre rekursiv auf die beide Teilarrays a. Der Quicksort-Algorithmus folgt eiem ähliche Lösugsasatz wie die biäre Suche. Diese Lösugsasatz et ma Divide-ad-Coquer ( Teile ud herrsche ). Rekursio 21
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Quicksort: Beispiel Pivot = 65 65 4 75 26 92 1 Partitioierug geauer auf Folie 24-26 1 4 26 75 92 65 Sortierug (rekursiv) Sortierug (rekursiv) geauer auf Folie 2+27 1 26 4 65 75 92 Rekursio 22
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Quicksort i Java static void quicksort(double[] a) { qsort(a, 0, a.legth - 1); } // Sortiert de Teilbereich a[from]...a[to] vo a. static void qsort(double[] a, it from, it to) { if (from < to) { //mehr als ei Elemet zu sortiere double pivot = a[from]; //waehle erstes Elemet als Pivot //Partitioierug ud Rückgabe des Grezidex it gidx = partitio(a, from, to, pivot); //rekursiver Aufruf für de like Teilarray geauer auf Folie 24-26 qsort(a, from, gidx); //rekursiver Aufruf für de rechte Teilarray qsort(a, gidx + 1, to); } } Rekursio 2
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Partitioierug: Vorgehesweise Laufe vo der utere ud der obere Arraygreze mit Idizes i ud j ach ie ud vertausche icht passede Elemete a[i] ud a[j] bis sich die Idizes treffe oder überkreuzt habe. Der zuletzt erreichte Idex j wird als Grezidex der Partitioierug zurückgegebe. Vo ute kommed sid Elemete icht passed, we sie größer-gleich dem Pivot sid. Vo obe kommed sid Elemete icht passed, we sie kleier-gleich dem Pivot sid. Bemerkug: Gegebeefalls werde auch gleiche Elemete vertauscht. Dies ist aus techische Grüde ötig, damit der Idex j so stoppt, dass der letzte Wert vo j immer der richtige Grezidex ist. Rekursio 24
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Pivot = 65 Partitioierug: Beispiel icht passed a[i] 65 65 4 75 26 92 1 a[j] 65 i j a[i] 65 1 4 75 26 92 65 a[j] 65 i i j j icht passed 1 4 26 75 92 65 i j Grezidex 1 4 26 75 92 65 j Rekursio 25 i Idices überkreuzt
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Partitioierug i Java static it partitio(double[] a, it from, it to, double pivot) { it i = from - 1; it j = to + 1; while (i < j) { i++; //aechste Startpositio vo liks //vo liks ach ie laufe solage Elemete kleier als Pivot while (a[i] < pivot) i++; j--; //aechste Startpositio vo rechts //vo rechts ach ie laufe solage Elemete größer als Pivot while (pivot < a[j]) j--; if (i < j) { //vertausche a[i] ud a[j] double temp = a[i]; a[i] = a[j]; a[j] = temp; } } //Ede while falls icht überkreuzt solage och icht überkreuzt retur j; //Rückgabe des Grezidex } Rekursio 26
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Partitioierugshierarchie des Quicksort 65 4 75 26 92 1 Partitioierug geauer auf Folie 2 1 4 26 75 92 65 Partitioierug Partitioierug 1 4 26 65 92 75 Partitioierug Partitioierug 1 26 4 65 75 92 Rekursio 27
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 Zeitkomplexität vo Quicksort (1) Beispiel: Das Array vo obe hat die Läge 6. Die Hierarchie der Partitioieruge stellt eie Baum dar mit Etage, wobei = log 2 (6) + 1. jedes Mal i etwa halbiert Alle Partitioieruge eier Etage beötige zusamme maximal c * 6 Schritte (mit eier Kostate c). jedes Elemet im Array aschaue Folglich ist die Zeitkomplexität i diesem Fall durch 6 * log 2 (6) beschräkt. abhägig vom gewählte Pivot Allgemei: We ei Array der Läge immer wieder i zwei etwa gleich große Teile aufgeteilt wird, da ist die Azahl der Partitioierugs-Etage durch log 2 () beschräkt. Die Azahl der Schritte pro Etage ist durch beschräkt ud damit die gesamte Zeitkomplexität i diesem Fall durch * log 2 (). Ma ka zeige, dass die Zeitkomplexität des Quicksort im durchschittliche Fall vo der Ordug * log 2 () ist. Rekursio 28
Eiführug i die Iformatik: Programmierug ud Software-Etwicklug, WS 16/17 d.h. Array wird icht halbiert Zeitkomplexität des Quicksort (2) Im schlechteste Fall ist die Zeitkomplexität des Quicksort quadratisch, d.h. vo der Ordug 2. Dieser Fall tritt z.b. ei, we das Array scho sortiert ist. Etage ud Schritte pro Etage 1 26 4 65 75 92 Partitioierug 1 26 4 65 75 92 Partitioierug 1 26 4 65 75 92 Partitioierug 1 26 4 65 75 92 Partitioierug 1 26 4 65 75 92 Partitioierug 1 26 4 65 75 92 Rekursio 29