DEV Community

Matheus Sena
Matheus Sena

Posted on

Entendendo Big O: Como Avaliar a Eficiência de um Algoritmo

Image description

Como podemos medir o quanto complexo é um algoritmo?
Vamos explorar o que é o Big O, uma notação usada para descrever a eficiência dos algoritmos, com exemplos do mundo real e implementações em Java para facilitar o entendimento.

O Que é Big O? 🤔

Big O é uma notação matemática usada para descrever a eficiência de um algoritmo, particularmente em termos de tempo de execução (tempo necessário para executar o algoritmo) ou uso de memória (quantidade de memória necessária).

Ou seja, à medida que o tamanho da entrada (n) aumenta, o Big O nos ajuda a entender como o algoritmo se comporta.

Exemplo do Mundo Real 🛒

Vamos usar uma analogia simples do mundo real: você está em uma livraria com uma lista de livros. Imagine que você deseja encontrar um livro específico nessa lista. Existem duas maneiras principais de fazer isso:

  1. Busca Sequencial: Você começa no topo da lista e verifica cada livro na estante até encontrar o que está procurando.
  2. Lista Ordenada: Imagine que a lista está ordenada alfabeticamente. Você pode usar uma busca mais eficiente para encontrar o item.

Aplicando o Conceito de Big O 🧠

  1. Busca Sequencial (O(n)): Este método verifica cada item um por um. Se houver n itens na lista, no pior caso, você precisará verificar todos os n itens. Portanto, o tempo de execução cresce linearmente com o tamanho da lista. Isso é representado como O(n).

  2. Busca Binária em Lista Ordenada (O(log n)): Se a lista estiver ordenada, você pode realizar uma busca binária, que divide a lista pela metade a cada passo. O tempo de execução cresce logaritmicamente, o que é muito mais rápido para listas grandes. Isso é representado como O(log n).

Exemplos em Java 🖥️

1. Busca Linear (O(n)) 🚶

Este é o exemplo de uma busca linear, onde verificamos cada elemento da lista até encontrar o que estamos procurando.

public class BuscaLinear { public static int buscar(int[] array, int valor) { for (int i = 0; i < array.length; i++) { if (array[i] == valor) { return i; // Retorna o índice do valor } } return -1; // Retorna -1 se não encontrar } public static void main(String[] args) { int[] numeros = {10, 20, 30, 40, 50}; int indice = buscar(numeros, 30); System.out.println("Encontrado no índice: " + indice); } } 
Enter fullscreen mode Exit fullscreen mode

Neste exemplo, o tempo de execução depende diretamente do tamanho da lista. Se a lista tiver 1000 elementos, pode ser necessário verificar todos eles no pior caso.

2. Busca Binária (O(log n)) 🚀

Se a lista estiver ordenada, podemos usar a busca binária para encontrar o elemento de forma mais eficiente.

import java.util.Arrays; public class BuscaBinaria { public static int buscar(int[] array, int valor) { int inicio = 0; int fim = array.length - 1; while (inicio <= fim) { int meio = (inicio + fim) / 2; if (array[meio] == valor) { return meio; } else if (array[meio] < valor) { inicio = meio + 1; } else { fim = meio - 1; } } return -1; // Retorna -1 se não encontrar } public static void main(String[] args) { int[] numeros = {10, 20, 30, 40, 50}; Arrays.sort(numeros); // Certifica-se de que o array está ordenado int indice = buscar(numeros, 30); System.out.println("Encontrado no índice: " + indice); } } 
Enter fullscreen mode Exit fullscreen mode

Aqui, a busca binária divide a lista pela metade em cada iteração, permitindo que você encontre o item desejado muito mais rapidamente do que na busca linear.

Tipos Comuns de Big O

1. O(1) - Tempo Constante

O tempo de execução é constante e não depende do tamanho da entrada.

Exemplo de Mundo Real: Acender uma luz em uma sala. O tamanho da sala não afeta o tempo que leva para acender a luz.

Exemplo em Java:

public class ExemploO1 { public static void main(String[] args) { int[] numeros = {10, 20, 30, 40, 50}; int primeiroNumero = numeros[0]; // O(1) System.out.println("Primeiro número: " + primeiroNumero); } } 
Enter fullscreen mode Exit fullscreen mode

2. O(n) - Tempo Linear 🚶

O tempo de execução cresce linearmente com o tamanho da entrada.

Exemplo de Mundo Real: Percorrer um corredor reto em uma biblioteca. Quanto maior o corredor, mais tempo leva para atravessá-lo.

Exemplo em Java:

public class ExemploOn { public static void main(String[] args) { int[] numeros = {10, 20, 30, 40, 50}; int soma = 0; for (int numero : numeros) { // O(n) soma += numero; } System.out.println("Soma: " + soma); } } 
Enter fullscreen mode Exit fullscreen mode

3. O(log n) - Tempo Logarítmico 📉

O tempo de execução cresce logaritmicamente à medida que a entrada cresce.

Exemplo de Mundo Real: Procurar um nome em um diretório telefônico ordenado, onde a cada etapa você reduz pela metade o número de páginas a serem verificadas.

Exemplo em Java:

import java.util.Arrays; public class ExemploOlogn { public static int buscaBinaria(int[] array, int valor) { int inicio = 0; int fim = array.length - 1; while (inicio <= fim) { int meio = (inicio + fim) / 2; if (array[meio] == valor) { return meio; // Valor encontrado } else if (array[meio] < valor) { inicio = meio + 1; } else { fim = meio - 1; } } return -1; // Valor não encontrado } public static void main(String[] args) { int[] numeros = {10, 20, 30, 40, 50}; Arrays.sort(numeros); // Certifica-se de que o array está ordenado int indice = buscaBinaria(numeros, 30); // O(log n) System.out.println("Valor encontrado no índice: " + indice); } } 
Enter fullscreen mode Exit fullscreen mode

4. O(n^2) - Tempo Quadrático 🏢

O tempo de execução cresce quadraticamente com o tamanho da entrada.

Exemplo de Mundo Real: Uma competição onde cada pessoa deve apertar a mão de todas as outras. Com n pessoas, haverá n * n apertos de mão.

Exemplo em Java:

public class ExemploOn2 { public static void main(String[] args) { int[] numeros = {64, 25, 12, 22, 11}; selectionSort(numeros); // O(n^2) System.out.println("Array ordenado: "); for (int numero : numeros) { System.out.print(numero + " "); } } public static void selectionSort(int[] array) { int n = array.length; for (int i = 0; i < n - 1; i++) { int indiceMin = i; for (int j = i + 1; j < n; j++) { if (array[j] < array[indiceMin]) { indiceMin = j; } } int temp = array[indiceMin]; array[indiceMin] = array[i]; array[i] = temp; } } } 
Enter fullscreen mode Exit fullscreen mode

5. O(2^n) - Tempo Exponencial 🌋

O tempo de execução cresce exponencialmente com o tamanho da entrada.

Exemplo de Mundo Real: Tentar adivinhar uma senha de n caracteres onde cada caractere pode ser uma letra ou número. O número de combinações possíveis cresce exponencialmente.

Exemplo em Java:

public class ExemploO2n { public static void main(String[] args) { int n = 10; System.out.println("Fibonacci de " + n + " é " + fibonacci(n)); // O(2^n) } public static int fibonacci(int n) { if (n <= 1) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); } } 
Enter fullscreen mode Exit fullscreen mode

Conclusão 🏁

Compreender a complexidade de tempo e a eficiência de um algoritmo é fundamental para otimizar o desempenho do seu código. O Big O fornece uma maneira de descrever como o tempo de execução ou o uso de memória de um algoritmo

Top comments (0)