Exemplos práticos de Java da notação Big O

Exemplos práticos de Java da notação Big O

1. Visão geral

Neste tutorial, vamos falar sobre o queBig O Notation means. We’ll go through a few examples to investigate its effect on the running time of your code.

2. A intuição da notação Big O

Muitas vezes ouvimos operformance of an algorithm described using Big O Notation.

O estudo do desempenho de algoritmos - ou complexidade algorítmica - cai no campo dealgorithm analysis. A análise de algoritmo responde à pergunta de quantos recursos, como espaço em disco ou tempo, um algoritmo consome.

Estaremos considerando o tempo como um recurso. Normalmente, quanto menos tempo um algoritmo leva para ser concluído, melhor.

3. Algoritmos de tempo constante -O(1)

Como esse tamanho de entrada de um algoritmo afeta seu tempo de execução? Key to understanding Big O is understanding the rates at which things can grow. A taxa em questão aqui étime taken per input size.

Considere este simples trecho de código:

int n = 1000;
System.out.println("Hey - your input is: " + n);

Claramente, não importa o quen é, acima. Esse trecho de código leva uma quantidade constante de tempo para ser executado. Não depende do tamanho de n.

Similarmente:

int n = 1000;
System.out.println("Hey - your input is: " + n);
System.out.println("Hmm.. I'm doing more stuff with: " + n);
System.out.println("And more: " + n);

O exemplo acima também é tempo constante. Mesmo que leve 3 vezes mais tempo para ser executado, édoesn’t depend on the size of the input, n. Denotamos algoritmos de tempo constante como segue:O(1). Observe queO(2),O(3) ou mesmoO(1000) significariam a mesma coisa.

Não nos importamos exatamente quanto tempo leva para ser executado, apenas que leva um tempo constante.

4. Algoritmos de tempo logarítmico -O(log n)

Os algoritmos de tempo constante são (assintoticamente) os mais rápidos. Logarithmic time is the next quickest. Infelizmente, eles são um pouco mais difíceis de imaginar.

Um exemplo comum de algoritmo de tempo logarítmico é o algoritmobinary search. Para ver como implementar a pesquisa binária em Java,click here.

O que é importante aqui é que orunning time grows in proportion to the logarithm of the input (in this case, log to the base 2):

for (int i = 1; i < n; i = i * 2){
    System.out.println("Hey - I'm busy looking at: " + i);
}

Sen for 8, a saída será a seguinte:

Hey - I'm busy looking at: 1
Hey - I'm busy looking at: 2
Hey - I'm busy looking at: 4

Nosso algoritmo simples executou log (8) = 3 vezes.

5. Algoritmos de tempo linear -O(n)

Após algoritmos de tempo logarítmico, obtemos a próxima classe mais rápida:linear time algorithms.

Se dizemos que algo cresce linearmente, queremos dizer que cresce diretamente proporcional ao tamanho de suas entradas.

Pense em um loop for simples:

for (int i = 0; i < n; i++) {
    System.out.println("Hey - I'm busy looking at: " + i);
}

Quantas vezes isso é executado para o loop? n vezes, é claro! Não sabemos exatamente quanto tempo levará para que isso seja executado - e não nos preocupamos com isso.

O que sabemos é que o algoritmo simples apresentado acima crescerá linearmente com o tamanho de sua entrada.

Preferiríamos um tempo de execução de0.1n do que(1000n + 1000), mas ambos ainda são algoritmos lineares; ambos crescem diretamente em proporção ao tamanho de seus insumos.

Novamente, se o algoritmo foi alterado para o seguinte:

for (int i = 0; i < n; i++) {
    System.out.println("Hey - I'm busy looking at: " + i);
    System.out.println("Hmm.. Let's have another look at: " + i);
    System.out.println("And another: " + i);
}

O tempo de execução ainda seria linear no tamanho de sua entrada,n. Denotamos algoritmos lineares da seguinte maneira:O(n).

Tal como acontece com os algoritmos de tempo constante, não nos importamos com as especificações do tempo de execução. O(2n+1) is the same as O(n), pois a notação Big O se preocupa com o crescimento dos tamanhos de entrada.

6. Algoritmos de N Log N Tempo -O(n log n)

n log n is the next class of algorithms. O tempo de execução cresce em proporção an log n da entrada:

for (int i = 1; i <= n; i++){
    for(int j = 1; j < 8; j = j * 2) {
        System.out.println("Hey - I'm busy looking at: " + i + " and " + j);
    }
}

Por exemplo, sen for 8, então este algoritmo será executado8 * log(8) = 8 * 3 = 24 vezes. Se temos ou não uma desigualdade estrita no loop for, é irrelevante por uma grande notação O.

7. Algoritmos de tempo polinomial -O(np)

Em seguida, temos algoritmos de tempo polinomial. Esses algoritmos são ainda mais lentos do que os algoritmosn log n.

O termo polinômio é um termo geral que contém(n2) quadrático,(n3) cúbico,(n4) quártico, etc. funções. What’s important to know is that O(n2) is faster than O(n3) which is faster than O(n4), etc.

Vamos dar uma olhada em um exemplo simples de um algoritmo de tempo quadrático:

for (int i = 1; i <= n; i++) {
    for(int j = 1; j <= n; j++) {
        System.out.println("Hey - I'm busy looking at: " + i + " and " + j);
    }
}

Este algoritmo será executado82 = 64 vezes. Observe que se aninharmos outro loop for, ele se tornará um algoritmoO(n3).

8. Algoritmos de tempo exponencial -O(k _n) _

Agora estamos entrando em território perigoso; esses algoritmos crescem na proporção de algum fator exponenciado pelo tamanho da entrada.

Por exemplo,O(2n) algorithms double with every additional input. Então, sen = 2, esses algoritmos serão executados quatro vezes; sen = 3, eles serão executados oito vezes (como o oposto dos algoritmos de tempo logarítmico).

AlgoritmosO(3n) triplicam com cada entrada adicional, algoritmosO(kn) ficarão k vezes maiores com cada entrada adicional.

Vamos dar uma olhada em um exemplo simples de um algoritmo de tempoO(2n):

for (int i = 1; i <= Math.pow(2, n); i++){
    System.out.println("Hey - I'm busy looking at: " + i);
}

Este algoritmo será executado28 = 256 vezes.

9. Algoritmos de tempo fatoriais -O(n!)

Na maioria dos casos, isso é o pior possível. Essa classe de algoritmos tem um tempo de execução proporcional afactorial do tamanho da entrada.

Um exemplo clássico disso é resolver o problema detraveling salesman usando uma abordagem de força bruta para resolvê-lo.

Uma explicação da solução para o problema do vendedor ambulante está além do escopo deste artigo.

Em vez disso, vamos examinar um algoritmoO(n!) simples, como nas seções anteriores:

for (int i = 1; i <= factorial(n); i++){
    System.out.println("Hey - I'm busy looking at: " + i);
}

ondefactorial(n) simplesmente calcula n !. Se n for 8, este algoritmo será executado8! = 40320 vezes.

10. Funções Assintóticas

Big O is what is known as an asymptotic function. Tudo isso significa que se preocupa com o desempenho de um algoritmoat the limit - ou seja, - quando muita entrada é lançada nela.

Big O não se importa com o quão bem seu algoritmo se sai com entradas de tamanho pequeno. Ele se preocupa com grandes entradas (pense em classificar uma lista de um milhão de números vs. ordenar uma lista de 5 números).

Outra coisa a se notar é quethere are other asymptotic functions. Big Θ (theta) e Big Ω (ômega) também descrevem algoritmos no limite (lembre-se,the limit isso significa apenas para entradas enormes).

Para entender as diferenças entre essas 3 funções importantes, primeiro precisamos saber que cada um dos Big O, Big Θ e Big Ω descreve aset (ou seja, uma coleção de elementos).

Aqui, os membros de nossos conjuntos são os próprios algoritmos:

  • Big O descreve o conjunto de todos os algoritmos que executamno worse do que uma certa velocidade (é um limite superior)

  • Por outro lado, Big Ω descreve o conjunto de todos os algoritmos que executamno better do que uma certa velocidade (é um limite inferior)

  • Finalmente, Big Θ descreve o conjunto de todos os algoritmos que executamat a uma certa velocidade (é como igualdade)

As definições que colocamos acima não são matematicamente precisas, mas ajudarão nosso entendimento.

Usually, you’ll hear things described using Big O, mas não faz mal saber sobre o Grande Θ e o Grande Ω.

11. Conclusão

Neste artigo, discutimos a notação Big O e comounderstanding the complexity of an algorithm can affect the running time of your code.

Uma ótima visualização das diferentes classes de complexidadecan be found here.

Como de costume, os trechos de código para este tutorial podem ser encontradosover on GitHub.