Què és la recursivitat?
Una funció recursiva es crida a si mateixa per resoldre una versió més petita del mateix problema. Totes necessiten dues parts:
Cas base
La versió prou petita del problema que ja saps respondre directament, sense cap altra crida. Sense cas base (o si mai s'hi arriba), la funció es crida a si mateixa per sempre — Python para l'execució amb un error (RecursionError) quan la pila de crides arriba a un límit.
Cas recursiu
La part on la funció es crida a si mateixa amb un problema més petit (més a prop del cas base), i combina aquell resultat per respondre el problema original.
Exemple: factorial
n! = n · (n-1) · (n-2) · ... · 1. Però també es pot definir dient que n! = n · (n-1)!, amb el cas base 0! = 1! = 1.
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
Per calcular factorial(4) cal esperar el resultat de factorial(3), que al seu torn espera factorial(2)... fins arribar al cas base. Cada crida pendent ocupa un lloc a la pila de crides fins que es pot resoldre.
Exemple: MCD amb l'algorisme d'Euclides
Ja vas calcular el màxim comú divisor amb un while al capítol de bucles. Aquesta és la mateixa idea, escrita de forma recursiva.
def mcd(a, b):
if b == 0:
return a
return mcd(b, a % b)
Fixa't en la diferència amb factorial: aquí el cas recursiu no fa cap càlcul amb el que retorna la crida — simplement el retorna tal qual. Es diu recursivitat de cua, i és la que més s'assembla a un bucle per sota.
Exemple: Fibonacci (i per què no sempre és bona idea)
La seqüència de Fibonacci: cada terme és la suma dels dos anteriors (0, 1, 1, 2, 3, 5, 8...).
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
Aquí cada crida en genera dues més: la pila de crides es ramifica en forma d'arbre, no d'una sola línia com a factorial o mcd. El problema és que fib(2) es torna a calcular diverses vegades dins del mateix càlcul de fib(4) — per a nombres grans, aquesta versió es torna molt lenta. Ho veuràs clarament a la pestanya d'execució.
Recursivitat vs iteració
Qualsevol cosa que es pugui fer amb recursivitat també es pot fer amb un bucle, i viceversa. La recursivitat sol llegir-se de forma més propera a la definició matemàtica del problema; la iteració sol gastar menys memòria (cada crida recursiva ocupa pila, un bucle no). Per a mcd, compara aquesta versió amb la que ja vas escriure amb while: fan exactament el mateix.
Mira la pila de crides créixer i desfer-se
Cada crida recursiva pendent apareix al panell "Pila de crides". Fixa't en com creix mentre les crides esperen, i com es desfà (de dalt cap avall) quan cadascuna ja pot retornar.
Recursivitat lineal
Recursivitat ramificada
Codi
Variables
| Nom | Valor |
|---|
Pila de crides (la més recent a dalt)
Terminal
Problemes per resoldre
Sense solució ni comprovació automàtica: escriu el programa, executa'l (python3 programa.py) i prova'l amb els exemples. Totes les funcions són recursives: pensa primer quin és el cas base. Les pistes són plegades perquè les obris només si les necessites.
1. Potència recursiva
Escriu una funció recursiva potencia(base, exp) que calculi base elevat a exp (exp ≥ 0), sense fer servir l'operador **.
2 elevat a 5 és: 32
Pistes
Cas base: exp == 0 → retorna 1 (qualsevol nombre elevat a 0 és 1).
Cas recursiu: return base * potencia(base, exp - 1) — igual d'estructura que factorial.
2. Suma de dígits
Escriu una funció recursiva suma_digits(n) que sumi tots els dígits d'un enter positiu (per exemple, de 348 hauria de donar 3+4+8=15).
La suma dels dígits de 348 és: 15
Pistes
Cas base: si n < 10 (un sol dígit), el resultat és el mateix n.
Cas recursiu: l'últim dígit és n % 10; la resta del nombre, sense aquest últim dígit, és n // 10 (divisió entera). return n % 10 + suma_digits(n // 10)
3. Les torres de Hanoi
El trencaclosques clàssic: mou N discs de la torre A a la torre C (fent servir B com a auxiliar), sense posar mai un disc més gran sobre un de més petit. Escriu una funció hanoi(n, origen, desti, auxiliar) que escrigui, un per línia, tots els moviments necessaris per a N discs.
Mou disc 1 de A a C Mou disc 2 de A a B Mou disc 1 de C a B Mou disc 3 de A a C Mou disc 1 de B a A Mou disc 2 de B a C Mou disc 1 de A a C
Pistes
Cas base: n == 0 → no cal moure res, la funció no fa res i acaba (return tot sol).
Cas recursiu (la part realment enginyosa): per moure n discs d'origen a destí, primer mou els n-1 discs de sobre d'origen a l'auxiliar (hanoi(n-1, origen, auxiliar, desti)), després mou el disc gran d'origen a destí (un print, no una crida), i finalment mou els n-1 discs de l'auxiliar a destí (hanoi(n-1, auxiliar, desti, origen)) — fixa't que origen/destí/auxiliar canvien de paper a cada crida.
Exercicis d'autoavaluació
Aquests es generen amb valors nous a l'atzar i tenen solució comprovable.
Quina serà la sortida per terminal d'aquest programa?