← voltar aos artigos
12/07/2026algoritmos · Big-O · performance

Desmistificando a Notação Big-O: O Guia Definitivo de Complexidade de Algoritmos

Do O(1) ao O(2^N): hierarquia, regras de ouro, exemplos em JavaScript e quando trocar espaço por tempo em produção.

Na computação, não basta que um algoritmo funcione; ele precisa ser eficiente. Quando lidamos com grandes volumes de dados, a diferença entre um algoritmo otimizado e um ineficiente pode ser a diferença entre uma execução de milissegundos e o travamento completo de um sistema.

A Notação Big-O é a ferramenta matemática que utilizamos para medir e comparar a eficiência de algoritmos de forma puramente teórica, independentemente do hardware, do sistema operacional ou da linguagem de programação utilizada.

1. O que é Complexidade de Código?

A complexidade de um algoritmo é dividida em duas frentes:

  • Complexidade de Tempo (Time Complexity): quanto tempo o algoritmo leva para rodar à medida que o volume de dados de entrada cresce.
  • Complexidade de Espaço (Space Complexity): quantidade de memória (RAM) que o algoritmo consome durante sua execução.

Por que não medir em segundos?

Se você rodar um código em um supercomputador e depois em um celular antigo, o tempo em segundos será drasticamente diferente. O Big-O resolve isso focando no número de operações primitivas que o código executa em relação ao tamanho da entrada, representado pela variável N.

2. A Hierarquia das Complexidades (Do Melhor ao Pior)

Para entender o comportamento dos algoritmos, analisamos como o tempo de execução cresce conforme N tende ao infinito. Abaixo estão as classes mais comuns de Big-O.

Tabela de Comparação de Crescimento

NotaçãoNomeComportamento com N=10Comportamento com N=100
O(1)Constante1 operação1 operação
O(log N)Logarítmica~3 operações~7 operações
O(N)Linear10 operações100 operações
O(N log N)Linearítmica~33 operações~664 operações
O(N²)Quadrática100 operações10.000 operações
O(2^N)Exponencial1.024 operações1.26 × 10³⁰ operações

3. Explicação Detalhada com Exemplos de Código (JavaScript)

Vamos analisar exemplos práticos para cada uma das principais complexidades.

O(1) – Complexidade Constante

O tempo de execução permanece o mesmo, não importa o tamanho da entrada. É o cenário ideal.

javascript
// O tamanho do array pode ser 10 ou 10.000.000, o tempo de acesso é instantâneo.
function obterPrimeiroElemento(arr) {
  return arr[0];
}

O(log N) – Complexidade Logarítmica

Ocorre em algoritmos que dividem o problema pela metade a cada passo. O exemplo mais clássico é a Busca Binária.

javascript
function buscaBinaria(arr, alvo) {
  let inicio = 0;
  let fim = arr.length - 1;

  while (inicio <= fim) {
    let meio = Math.floor((inicio + fim) / 2);

    if (arr[meio] === alvo) return meio; // Achou!

    if (arr[meio] < alvo) {
      inicio = meio + 1; // Descarta a metade esquerda
    } else {
      fim = meio - 1; // Descarta a metade direita
    }
  }
  return -1;
}

O(N) – Complexidade Linear

O tempo de execução cresce na mesma proporção que o tamanho da entrada. Se o array dobra de tamanho, o tempo de execução dobra.

javascript
// Um loop simples que percorre todo o array
function buscarElementoLinear(arr, alvo) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === alvo) return i;
  }
  return -1;
}

O(N log N) – Complexidade Linearítmica

Típica de algoritmos de ordenação eficientes (como Merge Sort e Quick Sort). Eles dividem o problema (log N) e passam por toda a entrada (N) para juntar as partes.

javascript
// Exemplo conceitual do Merge Sort
function mergeSort(arr) {
  if (arr.length <= 1) return arr;

  const meio = Math.floor(arr.length / 2);
  const esquerda = mergeSort(arr.slice(0, meio));
  const direita = mergeSort(arr.slice(meio));

  return merge(esquerda, direita); // Operação linear de junção
}

function merge(esquerda, direita) {
  let resultado = [], i = 0, j = 0;
  while (i < esquerda.length && j < direita.length) {
    if (esquerda[i] < direita[j]) resultado.push(esquerda[i++]);
    else resultado.push(direita[j++]);
  }
  return resultado.concat(esquerda.slice(i)).concat(direita.slice(j));
}

O(N²) – Complexidade Quadrática

Ocorre quando temos loops aninhados. O tempo de execução cresce proporcionalmente ao quadrado do tamanho da entrada. Altamente perigoso para produção.

javascript
// Comparando cada elemento com todos os outros (ex: Bubble Sort primitivo)
function encontrarDuplicatas(arr) {
  for (let i = 0; i < arr.length; i++) { // Loop 1: roda N vezes
    for (let j = i + 1; j < arr.length; j++) { // Loop 2: ~N vezes
      if (arr[i] === arr[j]) {
        console.log(`Duplicata encontrada: ${arr[i]}`);
      }
    }
  }
}

4. As Três Regras de Ouro para Calcular o Big-O

Ao analisar um código complexo do mundo real, você não precisa contar linha por linha. Você deve seguir três regras matemáticas fundamentais:

Regra 1: Focar sempre no Pior Cenário (Worst Case)

Se você está buscando um item em um array de tamanho N, você pode dar a sorte de ele ser o primeiro elemento (O(1)). Mas a análise de Big-O assume o pior caso possível: o item está na última posição ou não existe (O(N)).

Regra 2: Remover as Constantes

A notação foca na curva de crescimento, não em valores absolutos.

  • Um código que executa 2N operações é simplificado para O(N).
  • Um código que executa N/2 operações também é simplificado para O(N).

Regra 3: Ignorar os Termos Não-Dominantes

Ficamos apenas com o termo que cresce mais rápido. Se o seu algoritmo faz N² + N + 5 operações, à medida que N se torna gigante (ex: 1 milhão), o termo N² engole os outros de tal forma que eles se tornam irrelevantes. Portanto, a complexidade é O(N²).

5. Complexidade de Espaço (Space Complexity)

Não podemos esquecer da memória RAM. Se o seu algoritmo cria novas estruturas de dados baseadas no tamanho da entrada, ele está consumindo espaço assintótico.

javascript
// Complexidade de Tempo: O(N)  -> roda N vezes
// Complexidade de Espaço: O(N) -> cria um novo array proporcional à entrada
function duplicarArray(arr) {
  let novoArr = [];
  for (let i = 0; i < arr.length; i++) {
    novoArr.push(arr[i] * 2);
  }
  return novoArr;
}

6. Conclusão e Aplicação no Mundo Real

Escrever código limpo e moderno envolve escolher as estruturas de dados e os algoritmos certos. Muitas vezes, você pode trocar Espaço por Tempo (por exemplo, usar um Hash Map para reduzir uma busca de O(N²) para O(N), sacrificando um pouco mais de memória). Entender Big-O separa engenheiros de software seniores de programadores amadores.

Referências Acadêmicas

Para se aprofundar na matemática formal por trás da análise assintótica, consulte materiais das principais universidades:

EOF, Israel Santos

← voltar aos artigos