Porque é que a complexidade computacional é O(n^4)?
"java int soma = 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++) { soma++; } } } }
Não compreendo como quando j = i, 2i, 3i... o último laço "para" corre n vezes. Acho que simplesmente não compreendo'não compreendo como chegámos a essa conclusão com base na declaração 'se'.
Editar: Sei como calcular a complexidade de todos os loops excepto porque é que o último loop executa i vezes com base no operador mod... Apenas não't vejo como's i. Basicamente, porque é que't j % eu subo para i * i em vez de i?
33
5
Let's rotulam os loops A, B e C:
"java int soma = 0; // laço A for(int i = 1; i < n; i++) { // loop B for(int j = 1; j < i * i; j++) { if(j % i == 0) { // loop C for(int k = 0; k < j; k++) { soma++; } } } }
n
iterações.n*n
. Imagine o caso quandoi=n
, depoisj=n*n
.n' iterações, porque só é executado
i' vezes, ondei' está limitado a
n' no pior dos casos.Assim, a complexidade do código é O(n×n×n×n).
Espero que isto o ajude a compreender.
Todas as outras respostas estão correctas, quero apenas emendar o seguinte. Queria ver, se a redução das execuções do k-loop interno era suficiente para reduzir a complexidade real abaixo de 'O(n⁴).`Então escrevi o seguinte:
``java para (int n = 1; n < 363; ++n) { int soma = 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) { soma++; } } } }
}
n = 356: iterações = 1989000035, n³ = 45118016, n⁴ = 16062013696, rel = 0,12383254507467704 n = 357: iterações = 2011495675, n³ = 45499293, n⁴ = 16243247601, rel = 0,12383580700180696 n = 358: iterações = 2034181597, n³ = 45882712, n⁴ = 16426010896, rel = 0,1238390505075183874 n = 359: iterações = 2057058871, n³ = 46268279, n⁴ = 16610312161, rel = 0,1238422764747628734 n = 360: iterações = 2080128570, n³ = 46656000, n⁴ = 16796160000, rel = 0,12384548432498857 n = 361: iterações = 2103391770, n³ = 47045881, n⁴ = 16983563041, rel = 0,12384867444612208 n = 362: iterações = 2126849550, n³ = 47437928, n⁴ = 17172529936, rel = 0,1238518469862343
Deixemos's dar uma vista de olhos aos dois primeiros loops.
A primeira é simples, it's looping de 1 a n. A segunda é mais interessante. Passa de 1 para i ao quadrado. Vamos's ver alguns exemplos:
No total, os loops
i e j combinados têm
1^2 + 2^2 + 3^2. Existe uma fórmula para a soma dos primeiros n quadrados,
n (n+1) (2n + 1) / 6, que é aproximadamente
O(n^3)`.Tem um último
k loop
que faz loops de 0 aj
se e só sej % i == 0
. Uma vez quej
vai de 1 ai^2
,j % i == 0
é verdadeiro parai
vezes. Uma vez que oi loop
itera sobren
, tem umO(n)
adicional.Então tem
O(n^3)
dei e j loops
e outroO(n)
dek loop
para um total deO(n^4)
Remover
if
e modulo sem alterar a complexidadeAqui está o método original:
Se estiver confuso com o "se" e o módulo, pode simplesmente refactorizê-los, com o "j" saltando directamente de "i" para "2i" para "3i"... ... :
Para facilitar ainda mais o cálculo da complexidade, pode introduzir uma variável intermediária
j2
, de modo a que cada variável de laço seja incrementada em 1 a cada iteração:Pode utilizar o
System.out.println
debugging ou old-school para verificar quei, j, k
triplet é sempre o mesmo em cada método.Expressão de formulário fechado
Como mencionado por outros, pode utilizar o facto de que a soma dos primeiros inteiros
n
é igual an * (n+1) / 2
(ver números triangulares). Se utilizar esta simplificação para cada laço, obtém :Obviamente não é a mesma complexidade que o código original, mas devolve os mesmos valores.
Se pesquisar no Google os primeiros termos, pode notar que
0 0 0 2 11 35 85 175 322 546 870 1320 1925 2717 3731
aparecem em "Stirling numbers of the first kind: s(n+2, n)", com dois0
s adicionados no início. Isto significa que osum
é o número Stirling do primeiro tipos(n, n-2)
.