¿Por qué la complejidad computacional es O(n^4)?
``Java int suma = 0; for(int i = 1; i < n; i++) { for(int j = 1; j < i * i; j++) { if(j % i == 0) { for(int k = 0; k < j; k++) { suma++; } } } }
No entiendo cómo cuando j = i, 2i, 3i... el último bucle `for` se ejecuta n veces. Supongo que no entiendo cómo llegamos a esa conclusión basándonos en la sentencia `if`.
Edición: Sé cómo calcular la complejidad de todos los bucles, excepto por el hecho de que el último bucle se ejecuta i veces basándose en el operador mod... Simplemente no veo cómo es i. Básicamente, ¿por qué j % i no puede llegar a i * i en lugar de i?
33
5
Vamos a etiquetar los bucles A, B y C:
j % i == 0
se evalúa, lo que lleva O(1) tiempo.Multiplicando todo esto, obtenemos O(n × i2 × (1 + i)) = O(n × i3). Como i es en promedio O(n), esto es O(n4).
La parte complicada de esto es decir que la condición "si" sólo se cumple 1/i de las veces:
Básicamente, ¿por qué j % i no puede llegar a i * i en lugar de i?
De hecho,
j
llega hastaj < i * i
, no sólo hastaj < i
. Pero la condiciónj % i == 0
es verdadera si y sólo sij
es un múltiplo dei
.Los múltiplos de
i
dentro del rango soni
,2*i
,3*i
, ...,(i-1) * i
. Hayi - 1
de estos, por lo que el bucle C se alcanzai - 1
veces a pesar de que el bucle B iterai * i - 1
veces.n*n
iteraciones. Imagina el caso en quei=n
, entoncesj=n*n
.n
iteraciones porque se ejecuta sóloi
veces, dondei
está limitado an
en el peor caso.Así, la complejidad del código es O(n×n×n×n).
Espero que esto te ayude a entenderlo.
Todas las demás respuestas son correctas, sólo quiero corregir lo siguiente. Quería ver, si la reducción de las ejecuciones del k-loop interno era suficiente para reducir la complejidad real por debajo de
O(n⁴).
Así que escribí lo siguiente:Después de ejecutar esto, se hace evidente, que la complejidad es de hecho "n⁴". Las últimas líneas de salida se ven así:
Lo que esto muestra es que la diferencia relativa real entre el "n⁴" real y la complejidad de este segmento de código es un factor asintótico hacia un valor alrededor de "0.124..." (en realidad 0.125). Aunque no nos da el valor exacto, podemos deducir, lo siguiente:
La complejidad temporal es "n⁴/8 ~ f(n)` donde "f" es su función/método.
(Elegí 363 como límite superior excluido, porque
n = 362
es el último valor para el que obtenemos un resultado sensato. Después de eso, excedemos el espacio largo y el valor relativo se vuelve negativo).El usuario kaya3 averiguó lo siguiente:
Veamos los dos primeros bucles.
El primero es sencillo, va de 1 a n. El segundo es más interesante. Va de 1 a i al cuadrado. Veamos algunos ejemplos:
En total, los bucles
i y j
combinados tienen1^2 + 2^2 + 3^2
.Hay una fórmula para la suma de los primeros n cuadrados,
n * (n+1) * (2n + 1) / 6
, que es aproximadamenteO(n^3)
.Tienes un último "bucle k" que va de 0 a "j" si y sólo si "j % i == 0". Como
j
va de 1 ai^2
,j % i == 0
es cierto parai
veces. Como el buclei
itera sobren
, tienes unO(n)
extra.Así que tienes
O(n^3)
de los buclesi y j
y otroO(n)
del buclek
para un total deO(n^4)
.Quitar "si" y "modulo" sin cambiar la complejidad
Aquí está el método original:
Si estás confundido por el "si" y el "módulo", puedes refactorizarlos, con "j" saltando directamente de "i" a "2i" a "3i"... :
Para facilitar aún más el cálculo de la complejidad, puedes introducir una variable intermedia "j2", de modo que cada variable del bucle se incremente en 1 en cada iteración:
Puedes usar la depuración o la vieja escuela
System.out.println
para comprobar quei, j, k
triplete es siempre el mismo en cada método.Expresión de forma cerrada
Como ya se ha mencionado, se puede utilizar el hecho de que la suma de los primeros
n
números enteros es igual an * (n+1) / 2
(ver números triangulares). Si usas esta simplificación para cada bucle, obtienes :Obviamente no es la misma complejidad que el código original, pero devuelve los mismos valores.
Si buscas en Google los primeros términos, puedes notar que
0 0 0 2 11 35 85 175 322 546 870 1320 1925 2717 3731
aparecen en "Números de Stirling de la primera clase: s(n+2, n).", con dos0
s añadidos al principio. Significa que "suma" es el número de Stirling de la primera clases(n, n-2)
.