Recursiónc++recursióncomplejidadanálisisalgoritmos
Complejidad de Algoritmos Recursivos
Aprende a analizar la complejidad temporal y espacial de funciones recursivas
OOI Oaxaca9 de febrero de 20266 min read
Análisis de recursión
Analizar la complejidad de funciones recursivas requiere:
- Identificar la relación de recurrencia
- Resolver la recurrencia o aplicar el teorema maestro
Recurrencias comunes
Recursión lineal
void f(int n) {
if (n == 0) return;
// O(1) trabajo
f(n - 1); // Una llamada
}
Recurrencia:
Complejidad:
Recursión con trabajo lineal
void f(int n) {
if (n == 0) return;
for (int i = 0; i < n; i++) { // O(n) trabajo
// ...
}
f(n - 1);
}
Recurrencia:
Complejidad:
División por 2 (Búsqueda binaria)
int buscar(vector<int>& arr, int x, int l, int r) {
if (l > r) return -1;
int mid = (l + r) / 2; // O(1) trabajo
if (arr[mid] == x) return mid;
if (arr[mid] < x) return buscar(arr, x, mid+1, r);
return buscar(arr, x, l, mid-1); // Una llamada
}
Recurrencia:
Complejidad:
Divide y vencerás (Merge Sort)
void mergeSort(int l, int r) {
if (l >= r) return;
int mid = (l + r) / 2;
mergeSort(l, mid); // T(n/2)
mergeSort(mid + 1, r); // T(n/2)
merge(l, mid, r); // O(n) trabajo
}
Recurrencia:
Complejidad:
El Teorema Maestro
Para recurrencias de la forma:
Donde:
- = número de subproblemas
- = factor de reducción
- = trabajo fuera de la recursión
Casos del teorema
Sea :
- Si :
- Si :
- Si :
Ejemplos de aplicación
| Recurrencia | a | b | f(n) | Caso | Complejidad | |
|---|---|---|---|---|---|---|
| 1 | 2 | 1 | 1 | 2 | ||
| 2 | 2 | n | n | 2 | ||
| 2 | 2 | 1 | n | 1 | ||
| 4 | 2 | n | 1 | |||
| 4 | 2 | 2 |
Fibonacci: un análisis detallado
Sin memorización
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
Recurrencia:
Complejidad: - ¡Exponencial!
Cota superior:
Con memorización
int memo[N];
int fib(int n) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
return memo[n] = fib(n-1) + fib(n-2);
}
Complejidad: - Cada estado se calcula exactamente una vez.
Complejidad espacial
La recursión usa espacio en la pila de llamadas:
void f(int n) {
if (n == 0) return;
f(n - 1); // Llamada pendiente
}
Espacio: - profundidad máxima de la pila.
Recursión de cola (optimizable)
// Puede optimizarse a O(1) espacio
int factorialCola(int n, int acc = 1) {
if (n <= 1) return acc;
return factorialCola(n - 1, n * acc);
}
Divide y vencerás
void mergeSort(int l, int r) {
if (l >= r) return;
int mid = (l + r) / 2;
mergeSort(l, mid);
mergeSort(mid + 1, r);
merge(l, mid, r);
}
Espacio en pila: - profundidad del árbol de recursión.
Análisis de árboles de recursión
Método del árbol
Para :
Nivel 0: n -> n
Nivel 1: n/2 n/2 -> n
Nivel 2: n/4 n/4 n/4 n/4 -> n
...
Nivel k: ... -> n
Profundidad: log₂(n)
Total: n × log₂(n) = O(n log n)
Ejemplo: Potencia rápida
long long pot(long long a, long long n) {
if (n == 0) return 1;
long long medio = pot(a, n/2); // Una llamada
return (n % 2 == 0) ? medio * medio : medio * medio * a;
}
Análisis:
- Una llamada recursiva
- Problema se reduce a la mitad
- Trabajo O(1) por nivel
Complejidad:
Resumen de complejidades
| Patrón | Recurrencia | Complejidad |
|---|---|---|
| Lineal simple | ||
| Lineal con trabajo lineal | ||
| Búsqueda binaria | ||
| Merge sort | ||
| Fibonacci ingenuo | ||
| Árbol binario |
Consejos para competencias
- Recursión lineal: Generalmente o
- División por 2: Generalmente
- Dos llamadas con n-1: Probablemente exponencial
- Memorización: Reduce a O(número de estados)
Límites prácticos
| Complejidad | n máximo aproximado |
|---|---|
| 10 | |
| 20-25 | |
| 500 | |
| 5,000 | |
| 1,000,000 | |
| 10,000,000 | |
| 10^18 |
Ejercicios de análisis
Ejercicio 1
¿Cuál es la complejidad?
void f(int n) {
if (n <= 1) return;
f(n - 1);
f(n - 1);
}
Ver respuesta
Complejidad:
Ejercicio 2
¿Cuál es la complejidad?
void f(int n) {
if (n <= 1) return;
for (int i = 0; i < n; i++) {
cout << i << " ";
}
f(n / 2);
}
Ver respuesta
Por teorema maestro: , ,
, como , caso 3.
Complejidad:
Siguiente paso
Aplica estos conocimientos en Algoritmos de Ordenamiento.
