Big O: limite superior
"Big O" ( ) é de longe o mais comum. Quando você analisa a complexidade de um algoritmo, na maioria das vezes, o que importa é ter um limite superior sobre a rapidez com que o tempo de execução cresce quando o tamanho da entrada aumenta. Basicamente, queremos saber que a execução do algoritmo não vai demorar "muito tempo". Não podemos expressar isso em unidades de tempo reais (segundos), porque isso dependeria da implementação precisa (da maneira como o programa é escrito, da qualidade do compilador, da rapidez com que o processador da máquina é ...). Portanto, avaliamos o que não depende de tais detalhes, que é quanto tempo leva para executar o algoritmo quando alimentamos uma entrada maior. E nos preocupamos principalmente quando podemos ter certeza de que o programa está concluído, por isso geralmente queremos saber que isso levará uma quantidade tão grande de tempo ou menos.O
Dizer que um algoritmo tem um tempo de execução de para um tamanho de entrada significa que existe alguma constante modo que o algoritmo é concluído na maioria das etapas de , ou seja, o tempo de execução do algoritmo cresce no máximo tão rápido quanto (até um fator de escala). Observando o tempo de execução do algoritmo para o tamanho de entrada , informalmente significa que até algum fator de escala.n K KO ( f( N ) )nKf T ( n ) n O ( n ) T ( n ) ≤ f ( n )Kf( N )fT( N )nO ( n )T( n ) ≤ f( N )
Limite inferior
Às vezes, é útil ter mais informações que um limite superior. é o inverso de : expressa que uma função cresce pelo menos tão rápido quanto outra. significa que para uma constante , ou, para ser informal, acima a algum fator de escala.O T ( n ) = Ω ( g ( n ) ) T ( N ) ≥ K ′ g ( n ) K ′ T ( n ) ≥ g ( n )ΩOT( n ) = Ω ( g( N ) )T( N) ≥ K′g( N )K′T( n ) ≥ g( N )
Quando o tempo de execução do algoritmo pode ser determinado com precisão, combina e : ele expressa que a taxa de crescimento de uma função é conhecida, até um fator de escala. significa que para algumas constantes e . Informalmente, até algum fator de escala.O Ω T ( n ) = Θ ( h ( n ) ) K h ( n ) ≥ T ( n ) ≥ K ′ h ( n ) K K ′ T ( n ) ≈ h ( n )ΘOΩT( n ) = Θ ( h ( n ) )Kh ( n ) ≥ T( n ) ≥ K′h ( n )KK′T( n ) ≈ h ( n )
Considerações adicionais
O "pequeno" e são usados com muito menos frequência na análise de complexidade. Pouco é mais forte que grande ; onde indica um crescimento que não é mais rápido, indica que o crescimento é estritamente mais lento. Por outro lado, indica um crescimento estritamente mais rápido.ω ó ó ó ó ωoωoOOoω
Eu fui um pouco informal na discussão acima. A Wikipedia possui definições formall e uma abordagem mais matemática.
Lembre-se de que o uso do sinal de igual em e similares é um nome impróprio. A rigor, é um conjunto de funções da variável , e devemos escrever .O ( f ( n ) ) n T ∈ O ( f )T( n ) = O ( f( N ) )O ( f( N ) )nT∈ O ( f)
Exemplo: alguns algoritmos de classificação
Como isso é bastante seco, deixe-me dar um exemplo. A maioria dos algoritmos de classificação possui um tempo de execução quadrático no pior caso, ou seja, para uma entrada do tamanho , o tempo de execução do algoritmo é . Por exemplo, a classificação de seleção tem um tempo de execução , porque a seleção do ésimo elemento requer comparações , para um total de comparações. De fato, o número de comparações é sempre exatamente , que cresce como . Portanto, podemos ser mais precisos sobre a complexidade temporal da classificação: é Θ ( n 2 ) .O ( n 2 ) O ( n 2 ) k n - k n ( n - 1 ) / 2 n ( n - 1 ) / 2 n 2nO ( n2)O ( n2)kn - kn ( n - 1 ) / 2n ( n - 1 ) / 2n2Θ ( n2)
Agora faça a classificação por mesclagem . A classificação de mesclagem também é quadrática ( ). Isso é verdade, mas não muito preciso. A classificação de mesclagem na verdade tem um tempo de execução de O ( nO ( n2) no pior dos casos. Como a classificação por seleção, o fluxo de trabalho da classificação por mesclagem é essencialmente independente da forma da entrada e seu tempo de execução é sempre nO ( nl g ( n ) ) até um fator multiplicativo constante, ou seja, é.nl g (n)Θ ( nl g ( n ) )
Em seguida, considere o quicksort . Quicksort é mais complexo. Certamente é . Além disso, o pior caso de quicksort é quadrático: o pior caso é . No entanto, o melhor caso de quicksort (quando a entrada já está classificada) é linear: o melhor que podemos dizer para um limite inferior ao quicksort em geral é . Não repetirei a prova aqui, mas a complexidade média do quicksort (a média obtida de todas as permutações possíveis da entrada) é .Θ ( n 2 ) Ω ( n ) Θ ( nO ( n2)Θ ( n2)Ω ( n )Θ ( nl g ( n ) )
Há resultados gerais sobre a complexidade dos algoritmos de classificação em configurações comuns. Suponha que um algoritmo de classificação possa comparar apenas dois elementos por vez, com um resultado sim ou não ( ou x > y ). Então é óbvio que o tempo de execução de qualquer algoritmo de classificação é sempre Ω ( n ) (onde n é o número de elementos a serem classificados), porque o algoritmo precisa comparar todos os elementos pelo menos uma vez para saber onde ele se encaixará. Esse limite inferior pode ser atingido, por exemplo, se a entrada já estiver classificada e o algoritmo apenas comparar cada elemento com o próximo e mantê-los em ordem (ou seja, n - 1x ≤ yx > yΩ ( n )nn - 1comparações). O que é menos óbvio é que o tempo máximo de execução é necessariamente . É possível que o algoritmo às vezes faça menos comparações, mas deve haver um K constante, demodo que, para qualquer tamanho de entrada n , haja pelo menos uma entrada na qual o algoritmo faça mais do quecomparaçõesde K n l g ( n ) . A idéia da prova é construir a árvore de decisão do algoritmo, ou seja, seguir as decisões que o algoritmo toma do resultado de cada comparação. Como cada comparação retorna um resultado sim ou não, a árvore de decisão é uma árvore binária. Existem n !Ω ( nl g ( n ) )KnKn l g ( n )n !possíveis permutações da entrada, e o algoritmo precisa distinguir entre todas elas; portanto, o tamanho da árvore de decisão é . Como a árvore é uma árvore binária, é necessária uma profundidade de Θ ( l g ( n ! ) ) = Θ ( nn ! para ajustar todos esses nós. A profundidade é o número máximo de decisões que o algoritmo toma, portanto, a execução do algoritmo envolve pelo menos tantas comparações: o tempo máximo de execução é Ω ( nΘ ( l g ( n ! ) ) = Θ ( nl g ( n ) ) .Ω ( nl g ( n ) )
¹ Ou outro consumo de recursos, como espaço na memória. Nesta resposta, considero apenas o tempo de execução.