La recursividad se puede definir como la capacidad de una función para construir su respuesta usándose a si misma. La RAE define recursivo
de la siguiente manera:
La recursión es una técnica de programación y matemática, se usa para problemas con la siguiente estructura: Teoria
Toda recursión matemática debe estar compuesta por 2 elementos principales:
Inclusive, la definición de la recursión tiene 2 subelementos:
Por ahora todo parece relativamente confuso, pero veamos el caso más simple de recursión: El factorial.
La función factorial (que se expresa como "$x!$ se lee como factorial de x") está definida de la siguiente forma para los números enteros no negativos:
$$ n! = \left\{ \begin{array}{cc} 1 &n=0 \\ n\cdot (n-1)! &\text{en otro caso} \\ \end{array} \right. $$En este caso, f(x) = x!
(donde $x$ es el conjunto de parámetros), siendo $f(0) = 1$ como caso base y $f(n) = n\cdot f(n-1)$ nuestra recursión para $n \geq 1$. Además, a pesar de ser obvio, el predicado de $f$ es "$f(x)$ da como respuesta el producto de los numeros naturales entre el 1 y $x$".
Para introducir una manera un poco más formal de analizar las recursiones, veremos el conocido problema de las torres de hanoi, el cual señala lo siguiente:
Tenemos 3 postes fijos.
En uno de los postes hay 64 discos colocados con un tamaño descreciente desde la base hasta el tope.
En un movimiento, podemos llevar un disco del tope de alguno de los postes a otro poste que esté vacio o que el tamaño del primer disco de este sea mayor que el que moveremos.
El problema pide mover la torre de discos inicial y colocarlos de esa manera en otro poste.
La idea es resolver este problema en la menor cantidad de tiempo posible, por lo que debemos determinar la mínima cantidad de movimientos posibles.
Para resolver recursiones, siempre es bueno ver los casos pequeños para buscar un patrón. En este caso, vamos a generalizar: En vez de 64 discos, supongamos que hay $n$ discos.
Entoncs, definimos la función $T(n)$ que nos dará la cantidad mínima de movimientos que se deben usar para resolver el problema con $n$ discos.
Claramente, los casos más pequeños y lógicos serían:
$T(0) = 0$, no hay necesidad de movimientos si no hay discos
$T(1) = 1$, basta con colocar directamente el disco en el poste deseado
$T(2) = 3$, disco 1 -> poste 2, disco 2 -> poste 3, disco 1 -> poste 3
Ahora, esta función poco a poco toma cierta forma, pero veamos matemáticamente los límites de la función $T$:
Para mover $n$ discos a otro poste, por la naturaleza del problema, debemos mover la torre de $n-1$ discos superiores antes del $n$-ésimo disco a otro poste, luego movemos este disco al poste deseado y finalmente movemos los otros $n-1$ discos a la torre deseada. Esto se puede denotar como:
Ahora probamos el límite superior; pero si nos damos cuenta, la única situación en la que no se hacen movimientos innecesarios es la explicada anteriormente, pues necesitamos:
Al menos un poste esté libre
Salida sin problemas para el $n$-ésimo disco
Por lo que esto se logra solamente si todos los otros $n-1$ discos están en un poste diferente al original, y por las condiciones, deben estar en forma de torre decreciente de la base al tope, entonces llegamos a que:
$$ T(n) \geq T(n-1) + 1 + T(n-1) $$$$ T(n) \geq 2T(n-1) + 1 $$Juntando ambas desigualdades y el caso base $T(0)=0$, llegamos a que:
$$ T(0) = 0 $$$$ T(n) = 2T(n-1)+1, n\geq 1 $$Esta recursión se ve similar a la función $f(n) = 2^{n}-1$, pero no estamos completamente seguros a menos que demostremos su veracidad. Probemos por inducción:
$T(0) = f(0) = 2^{0}-1 = 0$
$T(n) = f(n) = 2^{n}-1$
$T(n+1) = 2T(n) + 1 = 2(2^{n}-1)+1 = 2^{n+1}-2+1 = 2^{n+1}-1$ -> Probamos la igualdad de ambas funciones.
Sin embargo, la meta de las recursiones no es adivinar la relación, sino ser más precisos y obtener conclusiones sin tener que jugar con nuestra suerte. Para evitar ello, plantearemos una manera más sencilla de analizar las recursiones separando por casos más adelante.
Este problema señala que se colocarán $n$ personas en un círculo, de manera horaria, y en una ronda se eliminan cada 2 personas de las que están presentes. Se repiten las rondas hasta que solo quede una persona. Debemos determinar el número de la persona que queda.
Una idea intuitiva, para empezar, es que la respuesta nunca será un número par, pues todos ellos son eliminados en la primera ronda.
Definamos la función $J(n)$ como la que nos dará la respuesta para $n$ personas.
Ahora, veamos los casos más pequeños:
n | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
J(n) | 1 | 1 | 3 | 1 | 3 | 5 |
Ciertamente, no parece que haya un patrón estricto, tal vez es una repetición de 1 -> 1,3 -> 1,3,5 -> 1,3,5,7 -> ..., pero esto no nos ayuda a darle forma matemática al problema.
Veamos el caso para $n=2k$:
Hay $2k$ personas inicialmente.
En la siguiente ronda han sido eliminadas todas las personas $2i$ y quedan las que tienen numeros $2i+1$.
Observación 1: La cantidad de personas ha sido reducida a $k$.
Observación 2: Ahora hay $k$ personas, pero sus números son 1,3,5,7,...,2i-1, por lo que la posición de la persona que se salva es igual a $J(k)$ pero su número para $J(2k)$ será $2J(k)-1$ por la biyección que se puede plantear.
Con lo anterior, llegamos a que:
$$ J(2k) = 2J(k)-1, k\geq 1 $$Ahora, supongamos $n=2k+1$:
Hay $2k+1$ personas inicialmente.
En la siguiente ronda han sido eliminadas todas las personas $2i$, pero con un paso más se elimina también la persona con número $1$ luego de la persona $2k$.
Observación 1: La cantidad de personas ha sido reducida a $k$.
Observación 2: Ahora hay $k$ personas, pero sus números son 3,5,7,...,2i+1, por lo que la posición de la persona que se salva es igual a $J(k)$, pero su número para $J(2k+1)$ será $2J(k)+1$.
Con lo anterior, llegamos a que:
$$ J(2k+1) = 2J(k)+1, k\geq 1 $$Entonces, con el caso base $J(1) = 1$ tenemos:
$$ J(n) = \left\{ \begin{array}{cc} 1 &n=1 \\ 2J(k)-1 &n=2k, k > 0 \\ 2J(k)+1 &n=2k+1, k > 0 \end{array} \right. $$Aun no parece muy clara la recursión, veamos (ahora que nos es más sencillo) varios de los casos pequeños:
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
J(n) | 1 | 1 | 3 | 1 | 3 | 5 | 7 | 1 | 3 | 5 | 7 | 9 | 11 | 13 | 15 |
Se ve claramente que se va tomando los primeros $2^{i}$ numeros impares y se repite el proceso aumentando $i$ en 1.
Ahora, notemos que siempre la respuesta es 1 cuando $n = 2^{k}$, y por lo que nos hemos dado cuenta sobre las respuestas, el último número que no tendrá respuesta igual a 1 y que sea mayor que $n$ es precisamente $m=2^{k+1}-1$, pues para llegar al siguiente 1 aumentaríamos $2^{k}$, y basta con restar 1 para obtener el anterior. Entonces, se da la secuencia de los $2^{k}$ primeros números impares desde $n=2^{k}$ hasta $m=2^{k+1}-1$.
Ahora, si nos damos cuenta, la secuencia de respuestas y sus parámetros están ordenadas, y además lo único que tienen diferente todos los números desde el $n$ hasta el $m$ son los bits hasta el de orden $k-1$. Ahora se nos puede ocurrir pensar en algo como que:
Si $n = 2^{m} + l$, entonces su respuesta es la posición $l$ (con indexación desde 0) de los primeros impares, la cual sería $2l+1$, con $0 \leq l < 2^{m}$.
Esto parece tener sentido debido al patrón y a nuestras observaciones, pero demostremos inductivamente que de verdad es la solución:
$J(1) = f(2^{0}+0) = 2(0)+1 = 1$.
$J(2^{m}+l) = 2l+1$.
Supongamos para $n=2^{m}+2k$ par:
Supongamos para $n=2^{m}+2k+1$ impar:
¿Cuál es la máxima cantidad de regiones que se pueden formar colocando $n$ rectas en un plano?
Igual que el problema 1, pero esta vez no son rectas, sino lineas infinitas en forma de zig-zag (Como una V rotada e infinita en extensión)
Halle la solución para las Torres de Hanoi pero si se da que los movimientos directamente del poste $A$ (original) al $C$ (deseado) no son válidos.
Los fractales son estructuras matemáticas cuya construcción se define por una profundidad como parámetro y un proceso recursivo.
Son un área de estudio relativamente reciente en las matemáticas, y por su comportamiento, inspira la curiosidad de muchas personas. Este caso no es diferente para programación competitiva, pues existen una cantidad considerable de problemas relacionados con estos.
Se toma un segmento
Dividir cada segmento en 3 partes iguales, reemplazar la parte central por dos partes iguales formando un ángulo de 60 grados. Repetir hasta llegar a la profundidad deseada
Los problemas principalmente tratarán de construir un fractal dado su procedimiento o de intentar analizar cada uno de sus pasos.
Veamos estos 4 problemas de recursión.
Esta es una manera de optimizar una exponenciación, la cual naturalmente se haría en $O(b)$ siendo $a$ la base y $b$ el exponente, para lograrla en $O(\log{b})$. La idea principalmente es barrer los bits de $b$:
Supongamos que la representación binaria de $b$ es la siguiente:
$$ b = b_{k}b_{k-1}\ldots b_{2}b_{1}b_{0} $$Entonces podemos representar:
$$ a^{b} = a^{b_{k}\cdot2^{k} + b_{k-1}\cdot2^{k-1} + \ldots + 2\cdot b_{1} + b_{0}} $$Por lo cual podemos plantear lo siguiente:
$$ a^{b} = a^{b_{0}}\cdot \left(a^{2}\right)^{b_{k}b_{k-1}\ldots b_{2}b_{1}} $$Entonces definimos la función $fast\_exp(a,b)$ como la que nos dará el resultado de $a^{b}$:
$$ fast\_exp(a,b) = a^{b} $$Pero por lo que planteamos hace un momento, debemos ver el siguiente patrón:
$$ fast\_exp(a,b) = a^{b mod 2} \cdot fast\_exp(a^{2},b/2) $$Donde $b/2$ es el cociente entero de la división de $b$ entre 2.
De esta recursión, recordando además que $fast\_exp(a,0) = 1$, podemos plantear el siguiente código:
long long fast_exp(long long a, int b){
if(b == 0) return 1LL; // Caso base
if(b % 2 == 0) return fast_exp(a*a,b/2); // Si el bit de orden 0 está apagado
return a*fast_exp(a*a,b/2); // Si el bit de orden 0 esta prendido
}
Algo beneficioso de esta función es que se puede aplicar a cualquier operación que sea asociativa en repetición, como la suma, coseno de la suma de dos angulos, etc.
La idea instintiva para hallar el MCD de dos numeros $a$ y $b$ es iterar desde 1 hasta el minimo entre ambos y actualizar cada vez que encontramos un numero que divida a ambos, pero esto tiene una complejidad de $O(min(a,b))$, lo cual no es muy eficiente si usamos números hasta $10^{18}$. Para solucionar este problema, usaremos el algoritmo de Euclides, el cual usa la siguiente recusión (considerando $a \geq b$):
$$ gcd(a,0) = a $$$$ gcd(a,b) = gcd(b,a mod b) $$Ahora, la veracidad del funcionamiento del algoritmo está demostrada, pero ¿Qué hay de su complejidad?.
Para hallar un estimado de su complejidad debemos hallar lo que sucede en cada paso:
$$ gcd(a,b) \rightarrow (a,b) $$$$ gcd(b,a mod b) \rightarrow (b,q),\text{ si } a = bk+q $$Y así se da de manera consecutiva. Sin embargo, notemos que el peor de los casos se debería dar cuando $a$ se reduce en lo mínimo posible; y al ser $a \geq b$, se da cuando $a = b+q$. De manera similar, $b = q+r$ y así consecutivamente. Si lo colocamos de una manera más fácil de ver, sería:
Si fijamos la solución final:
$$ 0 \rightarrow m \rightarrow n=m \rightarrow s=m+n \rightarrow \cdots \rightarrow b=q+r \rightarrow a=b+q $$Notamos que si colocamos la recursión de esta manera (tal que cada paso es tomar dos numeros consecutivos y colocarlos adecuadamente en los parámetros), se parece muchísimo a la sucessión de Fibonacci.
Ciertamente, el peor de los casos se dará cuando $a$ y $b$ son números de Fibonacci consecutivos, en cuyo caso, si $a = F_{n}$ y $b = F_{n-1}$, la siguiente llamada a la función dará $a=F_{n-1}$ y $b=F_{n-2}$ como parámetros, realizando unas $n-1$ iteraciones con la función.
Con lo anterior, tenemos que $gcd(F_{n},F_{n-1}) = \Theta(n)$ y para cualquier número $a$, $gcd(a,F_{n-1}) = O(n)$. Dado que $F_{n-1} = \Theta(\varphi^{n})$, esto implica aproximadamente que $gcd(a,b) = O(\log_{\varphi}{b})$ pero al ser $\varphi > 2$, entonces también se da que $gcd(a,b) = O(\log_{2}{b})$