Estou fazendo essa pergunta porque estou confuso sobre um aspecto da grande notação O.
Estou usando o livro Data Structures and Abstractions with Java de Frank Carrano. No capítulo sobre "Eficiência de algoritmos", ele mostra o seguinte algoritmo:
int sum = 0, i = 1, j = 1
for (i = 1 to n) {
for (j = 1 to i)
sum = sum + 1
}
Ele inicialmente descreve esse algoritmo como tendo uma taxa de crescimento de (n 2 + n) / 2 . Que olhar parece intuitivo.
No entanto, é então afirmado que (n 2 + n) / 2 se comporta como n 2 quando n é grande. No mesmo parágrafo ele afirma (n 2 + n) / 2 também se comporta como n 2 / 2 . Ele usa isso para classificar o algoritmo acima como O (n 2 ) .
Eu entendo isso (n 2 + n) / 2 é semelhante ao n 2 / 2 porque percentualmente, n faz pouca diferença. O que eu não entendo é por que (n 2 + n) / 2 e n 2 são semelhantes, quando n é grande.
Por exemplo, se n = 1.000.000 :
(n^2 + n) / 2 = 500000500000 (5.000005e+11)
(n^2) / 2 = 500000000000 (5e+11)
(n^2) = 1000000000000 (1e+12)
Esse último não é nada parecido. De fato, obviamente, é o dobro do meio. Então, como Frank Carrano pode dizer que são semelhantes? Além disso, como o algoritmo é classificado como O (n 2 ) . Olhando para esse loop interno, eu diria que era n 2 + n / 2
fonte
n
cresce, tanto as funções 'n ^ 2' quanto a sua função se comportam de maneira semelhante, há uma constante desconfiança em sua taxa de crescimento. Se você tem uma expressão complexa, a função que cresce mais rapidamente domina.Respostas:
Ao calcular a complexidade Big-O de um algoritmo, o que está sendo mostrado é o fator que dá a maior contribuição ao aumento no tempo de execução se o número de elementos em que você executa o algoritmo aumenta.
Se você tiver um algoritmo com complexidade de
(n^2 + n)/2
e duplicar o número de elementos, a constante2
não afeta o aumento no tempo de execução, o termon
causa uma duplicação no tempo de execução e o termon^2
causa um aumento de quatro vezes na execução Tempo.Como o
n^2
termo tem a maior contribuição, a complexidade do Big-O éO(n^2)
.fonte
O(n * log n) = O(n)
, o que não é verdade.O(n * log n) = O(n)
. Eu acho que isso dá uma boa explicação da lógica por trás da definição.A definição é que
se existe alguma constante C> 0 tal que, para todos os n maiores que alguns n_0, temos
Isso é claramente verdadeiro para f (n) = n ^ 2 eg (n) = 1/2 n ^ 2, onde a constante C deve ser 2. Também é fácil ver que isso é verdade para f (n) = n ^ 2 e g (n) = 1/2 (n ^ 2 + n).
fonte
g
seja diferente de zero, isso na verdade não é necessário, pois você sempre pode aumentar a constante C para tornar a declaração verdadeira para os finitos muitos primeiros valores de n_0.Ao falar sobre complexidade, você está interessado apenas nas mudanças de fator de tempo com base no número de elementos (
n
).Como tal, você pode remover qualquer fator constante (como o
2
aqui).Isso deixa você com
O(n^2 + n)
.Agora, para um tamanho razoável,
n
o produto, ou sejan * n
, será significativamente maior do que apenasn
, e é por isso que você também pode pular essa parte, o que deixa você com uma complexidade final deO(n^2)
.É verdade que, para números pequenos, haverá uma diferença significativa, mas isso se torna mais marginal, quanto maior o seu número
n
.fonte
Não é que "(n² + n) / 2 se comporte como n² quando n for grande", é que (n² + n) / 2 cresça como n² à medida que n aumenta .
Por exemplo, conforme n aumenta de 1.000 para 1.000.000
Da mesma forma, à medida que n aumenta de 1.000.000 para 1.000.000.000
Eles crescem da mesma forma, e é disso que trata o Big O Notation.
Se você plotar (n² + n) / 2 e n² / 2 no Wolfram Alpha , eles são tão semelhantes que são difíceis de distinguir por n = 100. Se você traçar todos os três no Wolfram Alpha , verá duas linhas separadas por um fator constante de 2.
fonte
Parece que você precisa trabalhar um pouco mais a grande notação O. Quão conveniente é essa notação, é muito enganosa por causa do uso de um sinal de igual, que não é usado aqui para denotar a igualdade de funções.
Como você sabe, essa notação expressa comparações assintóticas de funções, e escrever f = O (g) significa que f (n) cresce no máximo tão rápido quanto g (n) quanto n vai ao infinito. Uma maneira simples de traduzir isso é dizer que a função f / g é limitada. Mas é claro, temos que cuidar dos lugares onde g é zero e acabamos com a definição mais robusta que você pode ler em quase todos os lugares .
Essa notação acaba sendo muito conveniente para a computação - é por isso que é tão difundida - mas deve ser tratada com cuidado, pois o sinal de igual que vemos ali não denota uma igualdade de funções . É como dizer que 2 = 5 mod 3 não implica que 2 = 5 e se você gosta de álgebra, pode realmente entender a grande notação O como um módulo de igualdade.
Agora, voltando à sua pergunta específica, é totalmente inútil calcular alguns valores numéricos e compará-los: por maior que seja um milhão, ele não explica o comportamento assintótico. Seria mais útil a proporção trama das funções f (n) = n (n-1) / 2 e g (n) = N² - mas, neste caso especial, podemos ver imediatamente que f (n) / g (n) é menor que 1/2 se n> 0, o que implica que f = O (g) .
Para melhorar sua compreensão da notação, você deve
Trabalhe com uma definição limpa, não com uma impressão confusa, com base em coisas semelhantes - como você a experimentou, essa impressão confusa não funciona bem.
Tire algum tempo para elaborar exemplos em detalhes. Se você elaborar apenas cinco exemplos em uma semana, será suficiente para melhorar sua confiança. Este é um esforço que definitivamente vale a pena.
Nota lateral algébrica Se A é a álgebra de todas as funções Ν → Ν e C a subalgebra de funções delimitadas, dada uma função f, o conjunto de funções pertencentes a O (f) é um módulo C de A e as regras de computação no grande A notação descreve apenas como A opera nesses submódulos. Assim, a igualdade que vemos é uma igualdade dos módulos C de A , este é apenas outro tipo de módulo.
fonte
Eu acho que você entende mal o que significa a grande notação O.
Quando você vê O (N ^ 2), significa basicamente: quando o problema fica 10 vezes maior, o tempo para resolvê-lo será: 10 ^ 2 = 100 vezes maior.
Vamos perfurar 1000 e 10000 na sua equação: 1000: (1000 ^ 2 + 1000) / 2 = 500500 10000: (10000 ^ 2 + 10000) / 2 = 50005000
50005000/500500 = 99,91
Assim, enquanto o N ficou 10 vezes maior, as soluções ficaram 100 vezes maiores. Portanto, ele se comporta: O (N ^ 2)
fonte
1000000000000.00 o que?
Embora a complexidade nos forneça uma maneira de prever um custo do mundo real (segundos ou bytes, dependendo de estarmos falando sobre complexidade de tempo ou complexidade de espaço), ela não nos dá um número de segundos ou qualquer outra unidade específica.
Isso nos dá um grau de proporção.
Se um algoritmo precisar fazer algo n² vezes, será necessário n² × c para algum valor de c, que é quanto tempo leva cada iteração.
Se um algoritmo tiver que fazer algo n² ÷ 2 vezes, será necessário n² × c para algum valor de c que seja duas vezes maior que cada iteração.
De qualquer forma, o tempo gasto ainda é proporcional a n².
Agora, esses fatores constantes não são algo que podemos simplesmente ignorar; de fato, você pode ter um caso em que um algoritmo com complexidade O (n²) se sai melhor que um com complexidade O (n), porque se estamos trabalhando em um pequeno número de itens, o impacto dos fatores de consentimento é maior e pode superar outras preocupações . (De fato, mesmo O (n!) É o mesmo que O (1) para valores suficientemente baixos de n).
Mas não é disso que a complexidade nos fala.
Na prática, existem algumas maneiras diferentes de melhorar o desempenho de um algoritmo:
Ou, de outra forma, temos
f(n)×c
alguns segundos sendo usados e você pode melhorar o desempenho reduzindoc
, reduzindon
ou reduzindo of
retorno de um determinado produton
.O primeiro podemos fazer por algumas micro-opções dentro de um loop ou usando um hardware melhor. Sempre dará uma melhoria.
O segundo que podemos fazer talvez seja identificar um caso em que possamos interromper o algoritmo antes que tudo seja examinado ou filtrar alguns dados que não serão significativos. Não proporcionará uma melhoria se o custo de fazer isso exceder o ganho, mas geralmente será uma melhoria maior que o primeiro caso, especialmente com um n grande.
O terceiro que podemos fazer usando um algoritmo completamente diferente. Um exemplo clássico seria substituir uma classificação de bolha por uma classificação rápida. Com baixo número de elementos, podemos ter piorado as coisas (se c₁ for maior que c₀), mas geralmente permite os maiores ganhos, especialmente com n muito grande.
No uso prático, as medidas de complexidade permitem-nos raciocinar sobre as diferenças entre algoritmos precisamente porque ignoram a questão de como reduzir n ou c ajudará, a se concentrar em examinar
f()
fonte
n
é mantido suficientemente baixo, o Big-O não importa".Fator constante
O ponto da grande notação O é que você pode escolher um fator constante arbitrariamente grande para que O (função (n)) seja sempre maior que a função C * (n). Se o algoritmo A for um bilhão de vezes mais lento que o algoritmo B, eles terão a mesma complexidade O, desde que essa diferença não cresça quando n crescer arbitrariamente grande.
Vamos assumir um fator constante de 1000000 para ilustrar o conceito - é um milhão de vezes maior que o necessário, mas isso ilustra o ponto em que eles são considerados irrelevantes.
(n ^ 2 + n) / 2 "cabe dentro" O (n ^ 2) porque para qualquer n, não importa o tamanho, (n ^ 2 + n) / 2 <1000000 * n ^ 2.
(n ^ 2 + n) / 2 "não se encaixa" em um conjunto menor, por exemplo, O (n) porque para alguns valores (n ^ 2 + n) / 2> 1000000 * n.
Os fatores constantes podem ser arbitrariamente grandes - um algoritmo com tempo de execução de n anos tem O (n) complexidade que é "melhor" que um algoritmo com tempo de execução de n * log (n) microssegundos.
fonte
Big-O é sobre "quão complicado" é um algoritmo. Se você tiver dois algoritmos, e um leva
n^2*k
segundos para ser executado e o outro levan^2*j
segundos para ser executado, você pode argumentar sobre qual é o melhor e poderá fazer algumas otimizações interessantes para tentar afetark
ouj
, mas ambos esses algoritmos são muito lentos comparados a um algoritmo que levan*m
para ser executado. Não importa quão pequeno você faça as constantesk
ouj
, para uma entrada grande o suficiente, on*m
algoritmo sempre vencerá, mesmo quem
seja bastante grande.Então, chamamos os dois primeiros algoritmos
O(n^2)
e o segundoO(n)
. Divide bem o mundo em classes de algoritmos. É disso que se trata o big-O. É como dividir veículos em carros, caminhões e ônibus, etc. Há muita variação entre carros, e você pode passar o dia todo discutindo se um Prius é melhor que um Chevy Volt, mas no final do dia, se você precisa colocar 12 pessoas em uma, então esse é um argumento bastante sem sentido. :)fonte