Algoritmo de Partición Entera
Generación y conteo de todas las formas de descomponer un número entero positivo en sumas de enteros positivos.
1. ¿Qué es una Partición Entera?
Una partición entera de un número n es una colección de números positivos cuya suma es n. El orden de los sumandos no importa; por ejemplo, 4 = 3+1 = 2+2 = 2+1+1 = 1+1+1+1 son cinco particiones distintas de 4.
Este concepto es fundamental en teoría de números, combinatoria y tiene aplicaciones en criptografía, generación de pruebas de software y optimización de recursos.
2. Enfoques Algorítmicos
2.1 Recursión Simple
Explora todas las posibilidades mediante llamada recursiva. Es sencillo de entender pero tiene complejidad exponencial (O(2ⁿ)) y repite cálculos.
def partition_recursive(n, max_val=None):
if max_val is None:
max_val = n
if n == 0:
return [[]]
result = []
for i in range(min(max_val, n), 0, -1):
for tail in partition_recursive(n - i, i):
result.append([i] + tail)
return result
2.2 Memoización (Recursión con Caché)
Almacena resultados intermedios para evitar recomputaciones, reduciendo la complejidad a O(n·p(n)), donde p(n) es el número de particiones.
from functools import lru_cache
@lru_cache(maxsize=None)
def partition_memo(n, max_val=None):
if max_val is None:
max_val = n
if n == 0:
return [[]]
res = []
for i in range(min(max_val, n), 0, -1):
for tail in partition_memo(n - i, i):
res.append([i] + tail)
return res
2.3 Programación Dinámica (Tabulación)
Construye la solución iterativamente usando una tabla de tamaño (n+1)×(n+1). Es la opción más eficiente para contar particiones y generar todas ellas.
def partition_dp(n):
# tabla donde dp[i][j] = lista de particiones de i usando valores ≤ j
dp = [[[] for _ in range(n + 1)] for _ in range(n + 1)]
dp[0] = [[]]
for i in range(1, n + 1):
for j in range(1, n + 1):
if j > i:
dp[i][j] = dp[i][j - 1]
else:
dp[i][j] = dp[i][j - 1] + [[j] + part for part in dp[i - j][j]]
return dp[n][n]
2.4 Generación con Algoritmo de ZS (Zoghbi‑Stojmenovic)
Un algoritmo iterativo de orden O(p(n)) que genera cada partición en tiempo amortizado constante, ideal para flujos de datos en tiempo real.
def partitions_zs(n):
a = [0] * (n + 1)
k = 1
a[0] = n
while k != 0:
x = a[k - 1] - 1
k -= 1
while x >= 1:
a[k] = x
n -= x
k += 1
x = min(x, n)
a[k] = n
yield a[:k + 1]
3. Comparativa de Rendimiento
| Método | Complejidad Temporal | Complejidad Espacial | Ventajas | Desventajas |
|---|---|---|---|---|
| Recursión Simple | O(2ⁿ) | O(n) (pila) | Fácil de leer, ideal para prototipos. | Explosión combinatoria, no apto para n>20. |
| Memoización | O(n·p(n)) | O(n·p(n)) (caché) | Reduce repeticiones, mantiene claridad recursiva. | Consume memoria para n grandes. |
| Programación Dinámica | O(n²) + O(p(n)) | O(n²) | Determinístico, fácil de depurar, buen balance. | Construye tabla completa aunque solo se necesiten algunas particiones. |
| Algoritmo ZS | O(p(n)) (amortizado) | O(n) | Generación en streaming, bajo consumo de memoria. | Implementación más compleja, menos legible. |
4. Casos de Uso del Mundo Real
- Distribución de recursos: dividir un presupuesto o capacidad de almacenamiento entre varios departamentos manteniendo restricciones de tamaño.
- Pruebas de software: generar combinaciones de datos de entrada para validar algoritmos que operan sobre sumas o particiones.
- Criptografía: análisis de funciones de partición en esquemas de ocultación de información.
- Inteligencia Artificial: generación de estructuras de árbol de decisión balanceadas.
5. Buenas Prácticas y Optimización
5.1 Evitar Recursión Profunda
Para n > 30 la recursión simple supera el límite de pila de Python. Utiliza sys.setrecursionlimit() con cautela o prefiere enfoques iterativos.
5.2 Uso de Generadores
Cuando solo necesitas procesar cada partición una vez, emplea yield (como en el algoritmo ZS) para reducir la huella de memoria.
5.3 Cache Distribuida
En entornos de micro‑servicios, almacena resultados parciales en Redis o Memcached para compartir entre instancias.
5.4 Profiling y Benchmarking
Utiliza cProfile y timeit para comparar implementaciones antes de elegir la que mejor se ajuste a tu carga de trabajo.
6. Depuración y Resolución de Problemas Comunes
- Resultado vacío: Verifica que la condición base sea
n == 0y que el parámetromax_valesté inicializado correctamente. - Duplicados: Asegúrate de generar las particiones en orden no creciente (i.e., usar
min(max_val, n)) para evitar permutaciones idénticas. - Desbordamiento de memoria: Prefiere generadores o la versión tabular con
array('I')para almacenar índices en lugar de listas completas. - Lentitud inesperada: Revisa que la caché no se esté limpiando frecuentemente y que no haya conversiones costosas de tipos (p.ej., de
listatuple) dentro del bucle.
Algoritmo de Partición Entera: Conceptos, Implementación en Python y Mejores Prácticas