Complexidade de Algoritmos: medir a eficiência do código
Dois programas podem resolver o mesmo problema, mas um leva milissegundos e o outro horas. A complexidade de algoritmos explica por quê — e como escolher o mais eficiente.
Renato Freitas
Atualizado em 27 de abril de 2026
Por que a eficiência dos algoritmos importa?
Um algoritmo que funciona perfeitamente com 100 elementos pode se tornar inutilizável com 10 milhões. A diferença entre um algoritmo ruim e um bom não é de segundos — pode ser de dias. Entender complexidade é o que separa código que escala de código que trava.
Exemplo real: buscar um nome em uma lista telefônica de 1 milhão de entradas. Se você verificar um a um desde o início, pode precisar de 1 milhão de comparações. Com a busca binária (que funciona em listas ordenadas), você encontra qualquer nome em no máximo 20 comparações. A diferença é de 1.000.000 para 20.
Complexidade de algoritmos é a área da ciência da computação que estuda como o tempo de execução e o uso de memória crescem conforme o tamanho da entrada aumenta. A ferramenta principal é a notação Big-O.
🧮 Teste você mesmo — CalcSim
Quer mais recursos? Baixar app CalcSim IA
Notação Big-O: descrevendo o crescimento
A notação Big-O descreve como o custo de um algoritmo cresce em relação ao tamanho da entrada n, no pior caso. Ela ignora constantes e termos menores para focar no comportamento dominante com entradas grandes.
O(1) — tempo constante: o algoritmo leva o mesmo tempo independentemente do tamanho da entrada. Acessar um elemento de um array pelo índice é O(1) — não importa se o array tem 10 ou 10 milhões de elementos, o acesso é igualmente rápido.
O(n) — tempo linear: o tempo cresce proporcionalmente ao tamanho da entrada. Percorrer todos os elementos de uma lista para encontrar o maior valor é O(n): com o dobro de elementos, leva o dobro do tempo.
O(n²) — tempo quadrático: o tempo cresce com o quadrado do tamanho da entrada. Algoritmos com dois loops aninhados que percorrem toda a coleção costumam ser O(n²). Com 1000 elementos são necessárias até 1.000.000 operações.
O(log n) — tempo logarítmico: o tempo cresce muito lentamente. A cada passo, o problema é dividido ao meio. A busca binária em uma lista ordenada de 1 bilhão de elementos precisa de no máximo 30 comparações.
- O(1): constante — acessar array por índice, inserir no início de uma lista ligada.
- O(log n): logarítmico — busca binária, operações em árvore balanceada.
- O(n): linear — percorrer uma lista, busca linear.
- O(n log n): linearítmico — merge sort, heap sort. Eficiente para ordenação.
- O(n²): quadrático — bubble sort, insertion sort em listas grandes.
- O(2^n): exponencial — força bruta em subconjuntos. Inviável para n > 30.
Busca linear vs busca binária
A busca linear percorre os elementos um a um desde o início até encontrar o alvo ou chegar ao final. É simples, funciona em qualquer lista (ordenada ou não), mas é O(n) — no pior caso, verifica todos os elementos.
A busca binária funciona apenas em listas ordenadas. Ela começa pelo elemento do meio: se o alvo é menor, descarta a metade superior e repete na metade inferior; se é maior, descarta a metade inferior. A cada passo, o espaço de busca é dividido ao meio, resultando em complexidade O(log n).
Para uma lista de 1.000.000 de elementos, a busca linear pode precisar de até 1.000.000 comparações. A busca binária precisa de no máximo 20. O custo de manter a lista ordenada é amortizado quando há muitas buscas — ordenar uma vez e buscar rapidamente muitas vezes vale a pena.
- Busca linear: O(n) — funciona em listas não ordenadas. Simples de implementar.
- Busca binária: O(log n) — exige lista ordenada. Muito mais rápida para listas grandes.
- Ponto de equilíbrio: para listas muito pequenas (menos de 10 elementos), a diferença é irrelevante.
Algoritmos de ordenação: do Bubble Sort ao Merge Sort
Ordenar uma coleção é uma das tarefas mais comuns em programação. A escolha do algoritmo de ordenação impacta diretamente a performance para coleções grandes.
O Bubble Sort é o mais simples de entender: percorre a lista repetidamente, comparando pares adjacentes e trocando os que estão fora de ordem. O maior elemento vai borbulhando para o final a cada passagem. É intuitivo, mas tem complexidade O(n²) — extremamente lento para listas grandes. Não é usado em produção.
O Merge Sort (ordenação por mesclagem) usa a estratégia de divisão e conquista: divide a lista ao meio recursivamente até ter listas de um elemento (que já estão ordenadas), depois mescla os pares de listas menores em ordem crescente. A mesclagem de duas listas ordenadas é O(n), e o processo se repete O(log n) vezes, resultando em O(n log n) no total. Essa é a complexidade ótima para algoritmos de ordenação baseados em comparação.
As linguagens modernas usam variantes do Merge Sort ou Quick Sort internamente. Python usa Timsort (híbrido de Merge Sort e Insertion Sort). Java e JavaScript usam variantes similares. Você raramente precisará implementar um algoritmo de ordenação do zero — mas entender a complexidade ajuda a tomar decisões informadas.
- Bubble Sort: O(n²) — educacional, nunca use em produção com dados reais.
- Insertion Sort: O(n²) no pior caso, mas O(n) para listas quase ordenadas. Útil para listas pequenas.
- Merge Sort: O(n log n) garantido. Estável (preserva a ordem original de elementos iguais).
- Quick Sort: O(n log n) na média, O(n²) no pior caso. Muito rápido na prática.
- Heap Sort: O(n log n) garantido. Usado quando memória extra é restrição.
Complexidade de espaço e como pensar em trade-offs
Além do tempo, algoritmos também consomem memória. A complexidade de espaço mede como o uso de memória cresce com o tamanho da entrada. Um algoritmo pode ser rápido (O(n log n)) mas consumir O(n) de memória extra — o que pode ser um problema em dispositivos com memória limitada.
Trade-off é a troca consciente entre recursos. Às vezes, usar mais memória permite reduzir o tempo de execução. Um exemplo clássico é a memoização: armazenar resultados já calculados para não recalcular. O cálculo recursivo de Fibonacci sem memoização é O(2^n); com memoização, cai para O(n) no tempo ao custo de O(n) em memória.
Na prática, o primeiro passo é fazer o algoritmo funcionar corretamente. Depois, se houver problema de performance, meça antes de otimizar — ferramentas de profiling mostram exatamente onde o tempo está sendo gasto. Otimizar o código certo é muito mais eficaz do que otimizar tudo às cegas.
- Complexidade de tempo: como o tempo cresce com n.
- Complexidade de espaço: como o uso de memória cresce com n.
- Trade-off tempo × espaço: mais memória pode significar menos tempo, e vice-versa.
- Medir antes de otimizar: use profiling para encontrar o verdadeiro gargalo.
Perguntas frequentes
Preciso memorizar todas as notações Big-O?
Não é necessário memorizar tabelas. O mais importante é desenvolver a intuição: um loop simples sobre n elementos é O(n); dois loops aninhados sobre n elementos são O(n²); dividir o problema ao meio a cada passo é O(log n). Com essa base, você consegue estimar a complexidade de qualquer algoritmo que escrever.
Por que o Big-O ignora constantes? O(2n) e O(n) são iguais?
Sim, na notação Big-O, O(2n) é simplificado para O(n). O objetivo do Big-O é descrever o comportamento assintótico — como o algoritmo escala para entradas muito grandes. Uma constante multiplicativa não altera o crescimento fundamental: se dobrar o tamanho da entrada dobrar o tempo, o algoritmo é linear, seja O(n) ou O(5n). O que importa é a classe de crescimento, não o coeficiente exato.
Quando um algoritmo O(n²) pode ser melhor que um O(n log n)?
Para entradas muito pequenas, sim. Algoritmos O(n²) simples como Insertion Sort têm overhead muito baixo e podem ser mais rápidos que Merge Sort para listas de menos de 10 a 20 elementos. É por isso que implementações reais de Merge Sort e Quick Sort geralmente usam Insertion Sort para sublistas pequenas. O Big-O descreve o comportamento para n grande — para n pequeno, constantes e overhead importam.
Este artigo foi útil para você?
Avalie com estrelas para nos ajudar a melhorar o conteúdo.
Faça login para avaliar este artigo.
Ainda tem dúvida?
O Professor IA explica passo a passo
Faça uma pergunta em linguagem natural e receba uma explicação personalizada sobre Lógica de Programação — ou qualquer outro tópico.
Prefere resolver pelo celular?
Baixar o app grátis →Continue aprendendo