ΜΥΥ105: Εισαγωγή στον Προγραµµατισµό Αναδροµικές Συναρτήσεις Χειµερινό Εξάµηνο 2014
Ορισµός και ιδιότητες Μια συνάρτηση είναι αναδροµική αν καλεί τον εαυτό της Οι περισσότερες γλώσσες προγραµµατισµού υποστηρίζουν αναδροµικές συναρτήσεις Οι αναδροµικές συναρτήσεις είναι πολλές φορές πιο διαισθητικές από τις αντίστοιχες επαναληπτικές Η εκσφαλµάτωσή τους είναι όµως δυσκολότερη, οπότε πρέπει να χρησιµοποιούνται µε προσοχή Επίσης οι αναδροµικές συναρτήσεις µπορεί να επιβαρύνουν τη µνήµη του συστήµατος και να είναι πιο αργές 2
Αναδροµικές συναρτήσεις στα µαθηµατικά Παράδειγµα: παραγοντικό n! = 1 if n = 0 or n =1, n(n 1)! otherwise. def fact(n): if n==0 or n==1: return 1 else: return nfact(n-1) «βασική» περίπτωση επαναληπτική συνάρτηση: def fact(n): f = 1 for i in range(1,n+1): f = i return f αναδροµική περίπτωση 3
Παράδειγµα: ύψωση σε δύναµη αναδρομική συνάρτηση: def power(x, n): if n == 0: return 1 else: return x power(x, n-1) επαναληπτική συνάρτηση: def power(x, n): result = 1 for i in range(n): result = x return result 4
Παράδειγµα: αριθµοί Fibonacci Μια ακολουθία αριθµών: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,... Ορίζονται µε την αναδροµική συνάρτηση: F n = 1 if n = 1 or n =2, F n 1 + F n 2 otherwise. Χρήσεις και εµφάνιση: F n 1 lim n!1 Μαθηµατικα: συνδεση µε τη χρυσή τοµή F n Φύση: διακλαδώσεις δέντρων, διάταξη φύλλων, κλπ. = ' 5
Παράδειγµα: αριθµοί Fibonacci αναδρομική συνάρτηση: def rfib(n): if n==1 or n==2: return 1 else: return rfib(n-1)+rfib(n-2) επαναληπτική συνάρτηση: def fib(n): if n<=1: return -1 # undefined p = 1 # first number c = 1 # second number for i in range(n-2): # c becomes p, compute new c p, c = c, p +c return c p (previous) = προηγούµενος αριθµός c (current) = τρέχων αριθµός 6
Αναδροµικές συναρτήσεις σε γλώσσα προγραµµατισµού Πιο περίπλοκες από τις µαθηµατικές αναδροµές Μπορούν να δηµιουργήσουν προβλήµατα αν δεν οριστούν σωστά Άσκηση: Τι κάνει η παρακάτω συνάρτηση; Η «βασική» περίπτωση ορίζει τη συνθήκη τερµατισµού Ποιο είναι το πρόβληµα της παρακάτω συνάρτησης; Πως θα φτιάξουµε το πρόβληµα; def countdown(n): print(n) countdown(n-1) def countdown(n): print(n) if n==0: print('blast off!'); return countdown(n-1) 7
Αναδροµικές συναρτήσεις σε γλώσσα προγραµµατισµού def countdown(n): print(n) if n==0: print('blast off!'); return countdown(n-1) countdown(3) print(3) countdown(2) countdown(2) print(2) countdown(1) countdown(1) print(1) countdown(0) countdown(0) print(0) print('blast off!')) Η Python δηµιουργεί µια «στοίβα» όπου κρατάει τη «µνήµη» για κάθε αναδροµική κλήση: τοπικές µεταβλητές σηµείο της συνάρτησης από το οποίο πρέπει να συνεχίσει µετά την επιστροφή από την αναδροµική κλήση 8
Αναδροµικές συναρτήσεις σε γλώσσα προγραµµατισµού Το παράδειγµά µας εξακολουθεί να έχει προβλήµατα: Αν καλέσουµε countdown(-1) η συνάρτηση δεν τερµατίζει Αν καλέσουµε countdown(10000) το πρόγραµµα θα «κολλήσει» µετά από περίπου 1000 κλήσεις αναδροµής... η «στοίβα» του προγράµµατος θα γεµίσει RuntimeError: maximum recursion depth exceeded def countdown(n): print(n) if n==0: print('blast off!'); return countdown(n-1) 9
Σκεπτόµενοι αναδροµικά Άσκηση: φτιάξτε µια αναδροµική συνάρτηση vertical, η οποία να τυπώνει τα ψηφία ενός αριθµού «κάθετα» από το περισσότερο στο λιγότερο σηµαντικό Επιθυµητή συµπεριφορά: >>> vertical(3124) 3 1 2 4 10
Σκεπτόµενοι αναδροµικά Βήµα 1: ποια είναι η «βασική» περίπτωση; >>> vertical(6) 6 αν ο αριθµός έχει ένα µόνο ψηφίο, απλά τύπωσε το ψηφίο άρα η συνάρτησή µας πρέπει να έχει τη µορφή: def vertical(n): if n<10: # base case: n has 1 digit print(n) # just print n else: # recursive case: n has 2 or more digits # remainder of function 11
Σκεπτόµενοι αναδροµικά Βήµα 2: ποια είναι η «αναδροµική» περίπτωση; αν ο αριθµός έχει περισσότερα από ένα ψηφία (=n), τότε σπάσε τον σε δύο µέρη: στο τελευταίο ψηφίο σε όλα τα υπόλοιπα µαζί (δηλ. τα πρώτα n-1 ψηφία) π.χ. το 3124 σπάει σε 312 και 4 πρώτα χρειριζόµαστε τα πρώτα ψηφία (υποπρόβληµα), µετά το τελευταίο ψηφίο τα πρώτα n-1 ψηφία µπορούν να τυπωθούν µε τη σωστή σειρά µε µια αναδροµική κλήση ο χειρισµός του τελευταίου ψηφίου είναι η «βασική» περίπτωση 12
Σκεπτόµενοι αναδροµικά def vertical(n): if n<10: # base case: n has 1 digit print(n) # just print n else: # recursive case: n has 2 or more digits vertical(n//10) # recursively print all but last digit print(n%10) # print last digit of n >>> vertical(3124) 3 1 2 4 vertical(3124) vertical(312) print(4) vertical(31) print(2) vertical(3) print(1) print(3) 13
14
Άσκηση Τι κάνει η παρακάτω συνάρτηση; def vertical(n): if n<10: # base case: n has 1 digit print(n) # just print n else: # recursive case: n has 2 or more digits print(n%10) # print last digit of n vertical(n//10) # recursively print all but last digit >>> vertical(3124) 4 2 1 3 15
Σκεπτόµενοι αναδροµικά Άσκηση: φτιάξτε µια αναδροµική συνάρτηση pattern που να τυπώνει αστεράκια όπως στα παρακάτω παραδείγµατα >>> pattern(0) >>> pattern(1) >>> pattern(2) >>> pattern(3) 16
Σκεπτόµενοι αναδροµικά Βήµα 1: ποια είναι η «βασική» περίπτωση; >>> vertical(0) >>> αν το όρισµα είναι 0, µήν κάνεις τίποτα άρα η συνάρτησή µας πρέπει να έχει τη µορφή: def pattern(n): if n==0: return # base case for n=0 else: # recursive case: n>0 # remainder of function 17
Σκεπτόµενοι αναδροµικά Βήµα 2: ποια είναι η «αναδροµική» περίπτωση; µε ανάλυση της επιθυµητής συµπεριφοράς και λίγη σκέψη... >>> pattern(1) 1 φορά pattern(0) >>> pattern(2) pattern(1) 2 φορές pattern(1) >>> pattern(3) pattern(2) 3 φορές pattern(2) 18
Σκεπτόµενοι αναδροµικά def pattern(n): if n==0: return # base case for n=0 else: # recursive case: n>0 pattern(n-1) # recursively print pattern for n-1 print(n'') # print n stars pattern(n-1) # recursively print pattern for n-1 19
Πόσο γρήγορη είναι η συνάρτησή µας; Όταν σχεδιάζουµε µια συνάρτηση, πρέπει να σκεφτόµαστε και την ταχύτητά της Η συνάρτηση rfib είναι πολύ πιο αργή από τη fib αναδρομική συνάρτηση: επαναληπτική συνάρτηση: def rfib(n): if n==1 or n==2: return 1 else: return rfib(n-1)+rfib(n-2) def fib(n): if n<1: return -1 # undefined p = 1 # first number c = 1 # second number for i in range(n-2): # c becomes p, compute new c p, c = c, p +c return c 20
Πόσο γρήγορη είναι η συνάρτησή µας; Άσκηση: όταν εκτελούµε τη fib(7) πόσες επαναλήψεις εκτελούνται; Η fib(n) χρειάζεται περίπου n επαναλήψεις Άρα ο χρόνος της fib(n) είναι ανάλογος του n επαναληπτική συνάρτηση: def fib(n): if n<1: return -1 # undefined p = 1 # first number c = 1 # second number for i in range(n-2): # c becomes p, compute new c p, c = c, p +c return c 21
Πόσο γρήγορη είναι η συνάρτησή µας; Άσκηση: όταν εκτελούµε την rfib(7) πόσες φορές εκτελείται η rfib(3); αναδρομική συνάρτηση: def rfib(n): if n==1 or n==2: return 1 else: return rfib(n-1)+rfib(n-2) H rfib κάνει τους ίδιους υπολογισµούς πολλές φορές! 22
Πόσο γρήγορη είναι η συνάρτησή µας; Άσκηση: µελετήστε την παρακάτω συνάρτηση και σκεφτείτε πως µπορούµε να την κάνουµε πιο γρήγορη def my_pow(base, exponent): value = 1 for i in range(0, exponent): value = base return value >>> my_pow(1.00000001,100000000) 2.71828179834636 >>> 1.00000001100000000 2.7182817983473577 23
Πόσο γρήγορη είναι η συνάρτησή µας; Λύση: πως µπορούµε να ορίσουµε τη συνάρτηση my_pow αναδροµικά; a n = a n/2 a n/2 αν ο n είναι ζυγός αν είναι µονός; a n = aa n/2 a n/2 και στις 2 περιπτώσεις το a n/2 χρειάζεται να υπολογιστεί µόνο 1 φορά Πόσες αναδροµικές κλήσεις απαιτούνται για τον υπολογισµό του a n ; περίπου log 2 n 24
Πόσο γρήγορη είναι η συνάρτησή µας; def my_pow(base, exponent): if exponent==0: # base case return 1 tmp = my_pow(base, exponent//2) if exponent % 2 == 0: return tmptmp else: return basetmptmp «βασική» περίπτωση ζυγός εκθέτης µονός εκθέτης 25