learnxinyminutes-docs/pt-br/asymptotic-notation.md
2024-12-08 20:18:14 -07:00

6.1 KiB

category name contributors translators lang
Algorithms & Data Structures Asymptotic Notation
Jake Prather
http://github.com/JakeHP
João Farias
https://github.com/JoaoGFarias
pt-br

Notação Assintótica

O que é?

Notação Assintótica é uma linguagem que nos permite analisar o tempo de execução de um algoritmo através da identificação de seu comportamento com o crescimento da entrada oferecida. Isso também é conhecido como taxa de crescimento do algoritmo. O algoritmo de repente torna-se lento quando o tamanho da entrada cresce? O algoritmo mantém, em geral, seu tempo de execução rápido mesmo com aumento da entrada? Notação Assintótica nos dá a habilidade de responder estas questões.

Quais são as alternativas para responder a estas questões?

Um modo seria contar o número de operações primitivas com diferentes tamanhos de entrada. Apesar desta ser uma solução válida, o trabalho que ela requer, mesmo para algoritmos simples, não a justifica.

Outro modo é fisicamente medir a quantidade de tempo que um algoritmo requer para terminar com diferentes tamanhos de entrada. Entretanto, a precisão e relatividade (tempo obtido seria relativo apenas à máquina onde ocorreu a execução) deste método está limitado a variáveis de ambiente, como hardware, poder de processamento, etc.

Tipos de Notação Assintótica

Na primeira seção deste documento, descrevemos como Notação Assintótica identifica o comportamento de um algoritmo a medida que o tamanho da entrada cresce. Imaginemos um algoritmo como uma função f, n como o tamanho da entrada e f(n) sendo o tempo de execução. Então, para dado algoritmo f, com entrada de tamanho n, você terá tempo de execução f(n). Isto resulta em um gráfico onde a coordenada Y é o tempo de execução, a coordenada X representa o tamanho da entrada e os pontos representam o tempo de execução para dado tamanho de entrada.

Você pode representar a função, ou o algoritmo, com Notação Assintótica de várias maneiras. Você pode representar um algoritmo nas formas de Melhor Caso, Pior Caso ou Caso Médio. A maneira mais comum de analisar um algoritmo é pelo Pior Caso. Você tipicamente não avalia o melhor caso, porque essas condições não são atingidas com frequência. Um bom exemplo disto seria em algoritmos de ordenação; especificamente, na adição de elementos à árvores. O melhor caso na maioria de algoritmos pode ser de apenas uma operação. Entretanto, na maioria dos casos, o elemento a ser adicionado terá que percorrer a árvore de forma apropriada, o que pode causar a análise de um ramo inteiro. Este é o pior caso, e isto é o que você está se preparando.

Tipos de funções, limites e simplificação

Função Logarítmica - log n
Função Linear - an + b
Função Quadrática - an^2 + bn + c
Função Polinomial - an^z + . . . + an^2 + a*n^1 + a*n^0, onde *z* é uma constante
Função Exponencial -  a^n, onde a é alguma constante

Estas são as funções básicas de crescimento usadas em várias notações. A lista começa com a de crescimento mais lento (logarítmica, a de execução mais rápida) e segue para a de crescimento mais rápido (exponencial, de execução mais lenta). Repare que enquanto n, a entrada, cresce, cada uma dessas funções cresce mais rápido que quadrático, polinomial e exponencial, comparadas com logarítmica e linear.

Uma nota extremamente importante para notações é tentar usar os termos mais simples. Isto significa descartar constantes e termos de ordem mais baixa, pois quando o tamanho da entrada cresce para o infinito (limites matemáticos), os termos de ordem mais baixa e constantes tornam-se irrelevantes. Por exemplo, se você tiver uma constante muito grande, 2^9001, a simplificação não afetará sua notação.

Já que queremos as formas mais simples, mudemos nossa tabela um pouco...

Função Logarítmica - log n
Função Linear - n
Função Quadrática - n^2
Função Polinomial - n^z, onde *z* é uma constante
Função Exponencial - a^n, onde *a* é uma constante

Big-O

Big-O, também escrita como O, é uma Notação Assintótica para o pior caso. Digamos f(n) seja o tempo de execução de um algoritmo e *g(n)) um tempo de complexidade arbitrário que você quer relacionar com seu algoritmo. f(n) é O(g(n)), se, para quando constante real c (c > 0), f(n) <= c g(n) para todo tamanho de entrada n (n > 0).

Exemplo 1

f(n) = 3log n + 100
g(n) = log n

f(n) é O(g(n))?

3 log n + 100 é O(log n)?

Vejamos a definição de Big-O:

3log n + 100 <= c * log n

Há alguma constante c que satisfaça a definição para todo n?

3log n + 100 <= 150 * log n, n > 2 (Indefinido em n = 1)

Sim! A definição de Big-O foi atendida, portanto f(n) é O(g(n)).

Exemplo 2

f(n) = 3*n^2
g(n) = n

f(n) é O(g(n))?

3 * n^2 é O(n)? Vejamos a definição de Big-O:

3 * n^2 <= c * n

Há alguma constante c que satisfaça a definição para todo n?

Não, não há. f(n) não é O(g(n)).

Big-Omega

Big-Omega, também escrita como Ω, é uma Notação Assintótica para o melhor caso.

f(n)é Ω(g(n)), se para qualquer constante real c (c > 0), f(n) é >= c g(n) para todo tamanho de entrada n (n > 0).

Sinta-se livre para adicionar mais exemplos. Big-O é a notação primária usada para medir complexidade de algoritmos.

Notas Finais

É difícil manter esse tipo de tópico curto e você deveria ler os livros e artigos listados abaixo. Eles cobrem muito mais profundamente definições e exemplos. Mais x='Algorithms & Data Structures' virá; teremos um documento sobre analisar código em breve.

Livros

Artigos Online