Αναδρομή (Recursion) Η Δίδυμη Αδελφή της Επανάληψης Ι-1
Αναδρομή Τι είναι η αναδρομή; Όταν μία συνάρτηση καλεί τον εαυτό της άμεσα ή έμμεσα. Γιατί να μάθουμε αναδρομή; Νέος τρόπος σκέψης. Ισχυρό παράδειγμα προγραμματισμού. Πολλοί υπολογισμοί είναι από τη φύση του αυτο-αναφορικοί. Mergesort, FFT, gcd. Συνδεδεμένες δομές δεδομένων. Ένας φάκελος που περιέχει αρχεία και άλλους φακέλους Reproductive Parts M. C. Escher, 1948 Στενά συνδεμένη με τη μαθηματική επαγωγή (mathematical induction) 2
Μέγιστος Κοινός Διαιρέτης Μέγιστος κοινός διαιρέτης των αριθμών α και β είναι ο μεγαλύτερος κοινός διαιρέτης των α και β. Ο μέγιστος κοινός διαιρέτης των α, β συμβολίζεται με ΜΚΔ(α,β). 4032 = 2 6 3 2 7 1 1272 = 2 3 3 1 53 1 ΜΚΔ = 2 3 3 1 = 24 π.χ. ΜΚΔ(4032, 1272) = 24. Εφαρμογές. Απλοποίηση κλασμάτων: 1272/4032 = 53/168. RSA κρυπτογραφικό σύστημα. 3
Μέγιστος Κοινός Διαιρέτης ΜΚΔ. Να βρεθεί ο μεγαλύτερος κοινός διαιρέτης των p και q. Ευκλείδειος αλγόριθμος [Ευκλείδης 300 πχ] gcd( p, q) p if q 0 gcd(q, p % q) otherwise Βασική περίπτωση Βήμα αναδρομής, συγκλίνει στη βασική περίπτωση gcd(4032, 1272) = gcd(1272, 216) = gcd(216, 192) = gcd(192, 24) = gcd(24, 0) = 24. 4032 = 3 1272 + 216 Το πιο γνωστό έργο του είναι τα Στοιχεία, που αποτελείται από 13 βιβλία. Το έργο του Ευκλείδη ήταν τόσο σημαντικό ώστε η γεωμετρία που περιέγραψε στα Στοιχεία του (η βάση της οποίας είναι: έστω μία ευθεία ε και ένα σημείο Α όχι πάνω σε αυτήν την ευθεία, τότε υπάρχει μόνο μία ευθεία, παράλληλη της ε, που διέρχεται από το Α) ονομάστηκε Ευκλείδεια, ενώ τα Στοιχεία σήμερα θεωρούνται ένα από τα σημαντικότερα μαθηματικά έργα όλων των εποχών. Όταν ο Πτολεμαίος Α του ζήτησε έναν πιο εύκολο τρόπο από τα Στοιχεία του για να μάθει Γεωμετρία η απάντηση του μεγάλου μαθηματικού ήταν: «Δεν υπάρχει βασιλική οδός για τη Γεωμετρία». 4
Μέγιστος Κοινός Διαιρέτης ΜΚΔ. Να βρεθεί ο μεγαλύτερος κοινός διαιρέτης των p και q. gcd( p, q) p if q 0 gcd(q, p % q) otherwise Βασική περίπτωση Βήμα αναδρομής, συγκλίνει στη βασική περίπτωση p q q p % q x x x x x x x x p = 8x q = 3x gcd(p, q) = x gcd 5
Μέγιστος Κοινός Διαιρέτης ΜΚΔ. Να βρεθεί ο μεγαλύτερος κοινός διαιρέτης των p και q. gcd( p, q) p if q 0 gcd(q, p % q) otherwise Βασική περίπτωση Βήμα αναδρομής, συγκλίνει στη βασική περίπτωση Java υλοποίηση. public static int gcd(int p, int q) { if (q == 0) return p; else return gcd(q, p % q); Βασική περίπτωση Βήμα αναδρομής 6
Παραγοντικό Συνάρτηση για τον υπολογισμό του παραγοντικού (factorial) ενός φυσικού αριθμού: 0! = 1 1! = 1 2! = 1 2 3! = 1 2 3 = 2! 3....... n! = 1 2.... (n 1) n = (n 1)! n 7
/* Iterative definition */ public static int factorial (int x){ /* assumes x 0 */ int res = 1; while (x > 1) {res = res * x; x = x 1; return res; /* Recursive definition */ public static int factorial (int x){ if (x == 0 x == 1) return 1; else return x * factorial(x 1); factorial(4) 4 * factorial(3) 4 * 3 * factorial(2) 4 * 3 * 2 * factorial(1) 4 * 3 * 2 * 1 24 Αναδρομή στοίβας 8
factorial factorial(4) Διάγραμμα Εκτέλεσης * Γράφος Εξαρτήσεων * 24 Συσσώρευση Αποτελεσμάτων * 6 factorial(3) factorial(2) * 2 factorial(1) 1 9
Παρατηρήσεις αναφορικά με Αναδρομή 1. Διαχωρισμός ανάμεσα σε τερματικές (βασικές) και αναδρομικές (γενικές) περιπτώσεις με χρήση εντολής επιλογής 2. Η αναδρομική κλήση πρέπει να είναι ένα βήμα πιο κοντά σε τερματική περίπτωση, από την κλήση που οδήγησε σε αυτήν 3. Ισοδυναμία με επανάληψη 10
Αναδρομικά Γραφικά (Recursive Graphics)
12
13
Htree H-tree της τάξης n. Σχεδιάστε ένα H. Και του μισού του μεγέθους Αναδρομικά σχεδιάστε 4 H-trees της τάξης n-1, όπου το καθένα είναι συνδεδεμένο με κάθε άκρο. άκρο μέγεθος Τάξης 1 τάξης 2 τάξης 3 14
Htree στη Java public class Htree { public static void draw(int n, double sz, double x, double y) { if (n == 0) return; double x0 = x - sz/2, x1 = x + sz/2; double y0 = y - sz/2, y1 = y + sz/2; StdDraw.line(x0, y, x1, y); StdDraw.line(x0, y0, x0, y1); StdDraw.line(x1, y0, x1, y1); draw(n-1, sz/2, x0, y0); draw(n-1, sz/2, x0, y1); draw(n-1, sz/2, x1, y0); draw(n-1, sz/2, x1, y1); draw the H, centered on (x, y) recursively draw 4 half-size Hs public static void main(string[] args) { int n = Integer.parseInt(args[0]); draw(n,.5,.5,.5); 15
Κινούμενο H-tree Κινούμενο H-tree. Παύση για 1 second μετά τη σχεδίαση κάθε H. 20% 40% 60% 80% 100% 16
Οι πύργοι του Hanoi http://en.wikipedia.org/wiki/image:hanoiklein.jpg
Οι πύργοι του Hanoi Σύμφωνα με κάποιο μύθο σε έναν ναό της Άπω Ανατολής, οι ιερείς προσπαθούσαν να μεταφέρουν μια στοίβα χρυσών δίσκων από ένα στύλο σε ένα άλλο. Ο αρχικός στύλος έχει 64 δίσκους τοποθετημένους σε φθίνουσα σειρά μεγέθους από κάτω προς τα πάνω. Η μεταφορά των δίσκων από τον πρώτο στύλο στον τρίτο πρέπει να γίνει με τους εξής κανόνες. Μόνο ένα δίσκο μπορείτε να μετακινήσετε τη φορά. Κατά τη μετακίνηση ενός δίσκου θα πρέπει να τον τοποθετήσετε πάνω σε κάποιον άλλο με μεγαλύτερες διαστάσεις. Έναρξη Τέλος Οι πύργοι του Hanoi demo: http://mazeworks.com/hanoi/index.htm http://users.sch.gr/thafounar/games/towersofhanoi/towersofhanoi.html Edouard Lucas (1883) 18
Οι πύργοι του Hanoi: Αναδρομική Λύση Μετακινήστε τους n-1 μικρότερους δίσκους δεξιά. Μετακινήστε το μεγαλύτερο δίσκο αριστερά. Μετακινήστε n-1 μικρότερους δίσκους δεξιά. 19
Ο θρύλος για τους πύργους του Hanoi «Όταν ο Βράχμα δημιούργησε τον κόσμο, έστησε σε ένα ναό στην πόλη Μπενάρες, 64 δακτυλίδια άνισου μεγέθους όλα περασμένα σε ένα μπαστούνι έτσι ώστε αν κρατήσουμε το μπαστούνι κατακόρυφα να σχηματίζουν τον γνωστό μας πύργο. Οι ιερείς του ναού έπρεπε να δουλεύουν μέρα νύχτα, χωρίς σταμάτημα, για να μεταφέρουν τα δακτυλίδια σ ένα άλλο μπαστούνι, χρησιμοποιώντας ένα τρίτο σαν βοηθητικό, έτσι ώστε να μην τοποθετήσουν μεγαλύτερο δακτυλίδι πάνω από μικρότερο και μετακινώντας ένα μόνο δακτυλίδι σε κάθε κίνηση. Ο θρύλος λέει πως πριν προλάβουν οι ιερείς να μεταφέρουν όλα τα δακτυλίδια στο άλλο μπαστούνι, ο ναός θα καταρρεύσει μέσα στην σκόνη και ο κόσμος θα χαθεί μέσα σε τρομακτικό κρότο βροντής». Ερώτηση. Θα μπορούσαν οι αλγόριθμοι για τους υπολογιστές να βοηθήσουν; 20
Οι πύργοι του Hanoi: Αναδρομική Λύση public class TowersOfHanoi { public static void moves(int n, boolean left) { if (n == 0) return; moves(n-1,!left); if (left) System.out.println(n + " left"); else System.out.println(n + " right"); moves(n-1,!left); public static void main(string[] args) { int N = Integer.parseInt(args[0]); moves(n, true); moves(n, true) : μετακίνησε τους δίσκους από τον 1 μέχρι τον n ένα στύλο αριστερά moves(n, false): μετακίνησε τους δίσκους από τον 1 μέχρι τον n ένα στύλο δεξιά μικρότερος δίσκος 21
Κυκλική περιτύλιξη LEFT RIGHT 22
Οι πύργοι του Hanoi: Αναδρομική Λύση % java TowersOfHanoi 3 1 left 2 right 1 left 3 left 1 left 2 right 1 left Κάθε άλλη κίνηση είναι σε μικρότερο δίσκο % java TowersOfHanoi 4 1 right 2 left 1 right 3 right 1 right 2 left 1 right 4 left 1 right 2 left 1 right 3 right 1 right 2 left 1 right Υποδιαιρέσεις του κανόνα 23
Οι πύργοι του Hanoi: Δένδρο Αναδρομής n, left 3, true 1 14 15 28 2, false 2, false 2 7 8 13 16 21 22 27 1, true 1, true 1, true 1, true 3 4 5 6 9 10 11 12 17 18 19 20 23 24 25 26 1 left 2 right 1 left 3 left 1 left 2 right 1 left 24
Οι πύργοι του Hanoi: Ιδιότητες της Λύσης Αξιοσημείωτες ιδιότητες της αναδρομικής λύσης. Πέρνει 2 n - 1 κινήσεις για να επιλύσει το πρόβλημα για n δίσκους. Η ακολουθία των δίσκων είναι ίδια όπως οι υποδιαιρέσεις του κανόνα. Κάθε άλλη κίνηση συνεπάγεται μικρότερος δίσκος. Ο αναδρομικός αλγόριθμος δημιουργεί μία αναδρομική λύση! Εναλλακτική μεταξύ δύο κινήσεων: to left if n is odd Μετακίνησε το μικρότερο δίσκο προς τα δεξιά εάν το n είναι ζυγός αριθμός (άρτιος) Κάνε μόνο μία νόμιμη κίνηση χωρίς να περιλαμβάνεις το μικρότερο δίσκο Ο αναδρομικός αλγόριθμος μπορεί να αποκαλύψει τη μοίρα του κόσμου. Χρειάζονται 585 δισεκατομμύρια χρόνια για n = 64 (με ρυθμό 1 δίσκο ανά δευτερόλεπτο). Επιβεβαιώνοντας το γεγονός: οποιαδήποτε λύση παίρνει τουλάχιστον τόσο καιρό! 25
Εναλλακτική Αναδρομική Λύση public static void tower (char from_peg, char to_peg, char aux_peg, int n){ if (n==1) System.out.println("Move disk 1 from peg " + from_peg + " to peg " + to_peg); else { tower(from_peg, aux_peg, to_peg, n-1); System.out.println("Move disk " + n + " from peg " + from_peg + " to peg " + to_peg); tower(aux_peg, to_peg, from_peg, n-1); 26
Βοηθητικές Συναρτήσεις public static void display_tower_3_start(){ System.out.println(" "); System.out.println(" "); System.out.println(" *1* "); System.out.println(" **2** "); System.out.println("***3*** "); System.out.println(" A B C"); public static void display_tower_3_end(){ System.out.println(" "); System.out.println(" "); System.out.println(" *1* "); System.out.println(" **2** "); System.out.println(" ***3*** "); System.out.println(" A B C"); 27
Βοηθητικές Συναρτήσεις public static void display_tower_5_start(){ System.out.println(" "); System.out.println(" "); System.out.println(" *1* "); System.out.println(" **2** "); System.out.println(" ***3*** "); System.out.println(" ****4**** "); System.out.println("*****5***** "); System.out.println(" A B C"); public static void display_tower_5_end(){ System.out.println(" "); System.out.println(" "); System.out.println(" *1* "); System.out.println(" **2** "); System.out.println(" ***3*** "); System.out.println(" ****4**** "); System.out.println(" *****5***** "); System.out.println(" A B C"); 28
Ολόκληρη η Κλάση public class TowersOfHanoi { public static void tower (char from_peg, char to_peg, char aux_peg, int n){...... public static void display_tower_3_start(){...... public static void display_tower_3_end(){...... public static void display_tower_5_start(){...... public static void display_tower_5_end(){...... public static void main (String[] args){ display_tower_3_start(); tower('a', 'C', 'B', 3); display_tower_3_end(); System.out.println(); display_tower_5_start(); tower('a', 'C', 'B', 5); display_tower_5_end(); 29
% java TowersOfHanoi *1* **2** ***3*** A B C Move disk 1 from peg A to peg C Move disk 2 from peg A to peg B Move disk 1 from peg C to peg B Move disk 3 from peg A to peg C Move disk 1 from peg B to peg A Move disk 2 from peg B to peg C Move disk 1 from peg A to peg C *1* **2** ***3*** A B C *1* **2** ***3*** ****4**** *****5***** A B C 30
Move disk 1 from peg A to peg C Move disk 2 from peg A to peg B Move disk 1 from peg C to peg B Move disk 3 from peg A to peg C Move disk 1 from peg B to peg A Move disk 2 from peg B to peg C Move disk 1 from peg A to peg C Move disk 4 from peg A to peg B Move disk 1 from peg C to peg B Move disk 2 from peg C to peg A Move disk 1 from peg B to peg A Move disk 3 from peg C to peg B Move disk 1 from peg A to peg C Move disk 2 from peg A to peg B Move disk 1 from peg C to peg B Move disk 5 from peg A to peg C Move disk 1 from peg B to peg A Move disk 2 from peg B to peg C Move disk 1 from peg A to peg C Move disk 3 from peg B to peg A Move disk 1 from peg C to peg B Move disk 2 from peg C to peg A Move disk 1 from peg B to peg A Move disk 4 from peg B to peg C Move disk 1 from peg A to peg C Move disk 2 from peg A to peg B Move disk 1 from peg C to peg B Move disk 3 from peg A to peg C Move disk 1 from peg B to peg A Move disk 2 from peg B to peg C Move disk 1 from peg A to peg C 31
*1* **2** ***3*** ****4**** *****5***** A B C 32
Διαίρει και Βασίλευε Διαίρει και Βασίλευε παράδειγμα. Διάσπασε το πρόβλημα σε μικρότερα υποπροβλήματα της ίδιας δομής. Επίλυσε τα υποπροβλήματα αναδρομικά χρησιμοποιώντας την ίδια μέθοδο. Συνδύασε τα αποτελέσματα για να δώσεις λύση στο αρχικό πρόβλημα. Divide et impera. Veni, vidi, vici. - Julius Caesar Πολλά σημαντικά προβλήματα υπόκεινται στο Διαίρει και Βασίλευε. FFT (γρήγορο μετασχηματισμό Fourier) για επεξεργασία σήματος. Συντακτικοί αναλυτές (parsers) για γλώσσες προγραμματισμού. Multigrid μέθοδοι (χρησιμοποιείται μια ιεραρχία των τοποθετημένων πλεγμάτων) για την επίλυση διαφορικών εξισώσεων. Quicksort και mergesort για ταξινόμηση. Hilbert καμπύλη για την αποσύνθεση πεδίων. Quad-tree για αποτελεσματική προσομοίωση. Midpoint μέθοδο εκτόπισης για την κλασματική Brownian κίνηση 33
Αριθμοί Fibonacci Αριθμοί Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, F(n) 0 if n 0 1 if n 1 F(n 1) F(n 2) otherwise L. P. Fibonacci (1170-1250) Fibonacci λαγοί 34
Μία Πιθανή Παγίδα με την Αναδρομή Fibonacci αριθμοί. 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, F(n) 0 if n 0 1 if n 1 F(n 1) F(n 2) otherwise Από τις γνώσεις μας στα μαθηματικά: F(n) n (1 ) n 5 n 5 = golden ratio 1.618 Είναι φυσική η αναδρομή; public static long F(int n) { if (n == 0) return 0; else if (n == 1) return 1; else return F(n-1) + F(n-2); 35
Πρόκληση 1 με την Αναδρομή (δύσκολη αλλά σημαντική) Ερώτηση: Είναι αυτός ένας αποτελεσματικός τρόπος για να υπολογιστεί η F(50); public static long F(int n) { if (n == 0) return 0; else if (n == 1) return 1; else return F(n-1) + F(n-2); Απάντηση. Όχι, όχι, όχι! Αυτός ο κώδικας είναι ιδιαίτερα μη αποτελεσματικός F(50) F(49) F(48) F(48) F(47) F(47) F(46) F(47) F(46) F(46) F(45) F(46) F(45) F(45) F(44) recursion tree for naïve Fibonacci function F(50) καλείται μία φορά. F(49) καλείται μία φορά. F(48) καλείται 2 φορές. F(47) καλείται 3 φορές. F(46) καλείται 5 φορές. F(45) καλείται 8 φορές.... F(1) καλείται 12,586,269,025 φορές. F(50) 36
Πρόκληση 2 με την Αναδρομή (εύκολη αλλά επίσης σημαντική) Ερώτηση. Είναι αυτός ένας αποτελεσματικός τρόπος για να υπολογιστεί η F(50); public static long Fib (int n) { long[] F = new long[n+1]; F[0] = 0; F[1] = 1; for (int i = 2; i <= n; i++) F[i] = F[i-1] + F[i-2]; return F[n]; Απάντηση: Ναι. Αυτός ο κώδικας το κάνει αυτό με 50 προσθήκες. Δίδαγμα: Μη χρησιμοποιείτε την αναδρομή για να εμπλακείτε σε εκθετική σπατάλη. Πλαίσιο. Αυτό είναι μία ειδική περίπτωση μίας σημαντικής τεχνικής στον προγραμματισμό γνωστής ως δυναμικός προγραμματισμός. Ενότητα ΙΙ 37
/* Revisiting the recursive definition */ public static long Fib (int x){ if (x == 0) return 0; else if (x == 1) return 1; else return Fib(x 2) + Fib(x 1); Διπλή αναδρομή στοίβας δύο αναδρομικές κλήσεις Fib + Γράφος Εξαρτήσεων 38
Fib(5) 5 Διάγραμμα Εκτέλεσης Fib(3) + Fib(4) Fib(1) + Fib(2) Fib(2) + Fib(3) 1 Fib(0) + Fib(1) Fib(0) + Fib(1) Fib(1) + Fib(2) 0 1 0 1 1 Fib(0) + Fib(1) 0 1 Όπως ήδη είδαμε ο ορισμός με τη διπλή αναδρομή στοίβας δεν είναι αποδοτικός 39
/* Efficient iterative definition */ public static long Fib (int x){ long a = 0, b = 1, temp; while (x!= 0) { temp = a; a = b; b = b + temp; x = x 1; return a; Fib(5) 5 x a b 5 0 1 4 1 1 3 1 2 2 2 3 1 3 5 0 5 8 επιστροφή της τιμής της a 40
/* Equivalent definition using tail recursion */ public static long Fib (int x){ return Fib_aux(0, 1, x); public static long Fib_aux(long a, long b, int x){ if (x == 0) return a; else return Fib_aux(b, a+b, x-1); Fib(5) Fib_aux(0,1,5) Fib_aux(1,1,4) Fib_aux(1,2,3) Fib_aux(2,3,2) Fib_aux(3,5,1) Fib_aux(5,8,0) 5 επιστροφή της τιμής της a αναδρομή ουράς 41
Παρατηρήσεις 1. Οι μεταβλητές a και b αποτελούν ένα μετακινούμενο παραθυράκι στην ακολουθία Fibonacci. 2. Αρχικοποιούνται στους πρώτους δύο όρους της ακολουθίας, και στη συνέχεια το περιεχόμενο της κάθε μίας διαδοχικά αντικαθίσταται από τον επόμενό του όρο. Ερώτηση Ποια μορφή αναδρομής, ουράς ή στοίβας, εκδηλώνουν οι συναρτήσεις gcd, Htree.draw, moves, και tower; 42
Σύνοψη Πώς να γράψετε απλά προγράμματα αναδρομής; Βασική περίπτωση, βήμα αναδρομής. Ιχνηλατήστε την εκτέλεση ενός προγράμματος αναδρομής. Χρησιμοποιήστε εικόνες. Γιατί να μάθετε αναδρομή? Νέος τρόπος σκέψης. Ισχυρό προγραμματιστικό εργαλείο. Διαίρει και Βασίλευε: Κομψή λύση σε πολλά σημαντικά προβλήματα. 43