Executando verificação de segurança...
2

Big 0: Medindo Escalabilidade, Não Apenas Complexidade.

Eu vim aqui te dizer que Big O não é uma forma de medir a complexidade de um algoritmo, mas sim sua escalabilidade. Isso mesmo que você leu: Big O mede o quão escalável é seu algoritmo. Ele mede como o tempo de execução (ou consumo de recursos) cresce em função do tamanho da entrada. Muitas pessoas usam a analogia do garçom para explicar o que é uma API. Vou usar essa mesma analogia para explicar minha afirmação.

Imagine que você é um garçom e está chegando uma data muito importante. O restaurante em que você trabalha está com uma grande fila do lado de fora. Seu gerente, surpreso e feliz, pede para você contar quantas pessoas há lá fora. Você, como um funcionário proativo e pensando nas gorjetas, vai correndo verificar.

Existem N formas de você contar quantas pessoas têm na fila. Você pode contar nos dedos, pode pegar uma folha de papel e fazer risquinhos, pode usar o celular para ir anotando... enfim, N possibilidades.

A forma que você escolhe para contar as pessoas na fila pode ser entendida como seu algoritmo. Basicamente, tudo que você escreve quando está programando é um algoritmo. Agora pense: qual das formas que você escolheu é a mais escalável? Se você escolher contar usando os dedos... ok, você vai conseguir se a fila for pequena. Com 10 pessoas, você irá contar rápido. Mas se chegarem mais pessoas e essas 10 virarem 20, 30, 50 pessoas, você terá problemas e irá demorar muito tempo. Apesar de resolver o problema de contar, a forma que você escolheu — seu algoritmo — não tem escala, e agora você precisa pensar em uma forma mais escalável de resolver isso.

Apesar de explicar Big O a fundo não ser o objetivo desta publicação, há certas coisas que temos que revisar/aprender para entender o parágrafo posterior. Uma delas são algumas notações de Big O. Caso você não saiba, O == Big O ou Ordem de Crescimento.

  • O(1) – Tempo constante: Esse aqui é o ideal de um algoritmo
    • O tempo de execução não depende do tamanho da entrada. Um exemplo é o acesso a um elemento de um array.
  • O(log n) – Tempo logarítmico:
    • O tempo cresce proporcionalmente ao logaritmo do tamanho da entrada. Exemplo: busca binária.
  • O(n) – Tempo linear:
    • O tempo cresce proporcionalmente ao tamanho da entrada. Exemplos: percorrer um array.
  • O(n log n) – Tempo linearítmico (ou linear vezes logarítmico):
    • Comum em algoritmos de ordenação eficientes, como o Merge Sort e o Heap Sort.
  • O(n²) – Tempo quadrático:
    • O tempo cresce proporcionalmente ao quadrado do tamanho da entrada. Exemplos: algoritmos de ordenação ingênuos, como o Bubble Sort (na sua forma básica).
  • O(n³) – Tempo cúbico:
    • O tempo cresce com o cubo do tamanho da entrada. Exemplo: algumas soluções de problemas envolvendo matrizes.
  • O(2^n) – Tempo exponencial:
    • O tempo de execução dobra a cada incremento na entrada. Exemplos: algoritmos que resolvem problemas de combinatória de forma "brute force".
  • O(n!) – Tempo fatorial:
    • O tempo cresce de forma fatorial em relação ao tamanho da entrada. Exemplo clássico: algoritmo que gera todas as permutações de uma sequência.

Agora vamos ver outro exemplo: o uso do ArrayList em Java. Se seu objetivo é acessar elementos por índice, o ArrayList é uma excelente escolha, pois esse acesso é feito em tempo constante — O(1) pelo fato dessa estrutura de dados ter Index em seus elementos. No entanto, se você precisa inserir ou remover elementos em posições aleatórias da lista, essa estrutura pode não ser a melhor opção. Nessas operações, principalmente quando a inserção ou remoção não acontece no final, é necessário mover os elementos subsequentes para acomodar a mudança, resultando em um custo de tempo O(n). Em uma lista pequena, com 10 itens por exemplo, esse custo é desprezível. Mas imagine o impacto ao lidar com 1 milhão de itens. É aí que o Big O se torna fundamental: ele não mede a performance exata (ou o tempo de execução em uma máquina específica), mas sim a escalabilidade do algoritmo — ou seja, como o tempo de execução aumenta conforme o tamanho da entrada cresce.

Entender essas diferenças é muito importante, tanto para entrevistas de emprego quanto para resolver exercícios do LeetCode. E aqui vai um gancho deixado pelo meu último paragrafo e um pouco off-topic. Invistam tempo estudando estrutura de dados, pode parecer um pouco chato e massante no inicio, pois temos sempre aquela emoção de partir para o framework, mas nunca se esqueça que nenhum edifício fica em pé sem uma boa base. Eu vou escrever o que sei sobre isso por aqui e vou usar Java para os exemplos!

Até aqui eu acredito que cumpri a intenção do meu post. Fique a vontade para corrigir algum equivoco ou adicionar algo nos comentários . Eu sou apenas um graduando de Engenharia de Software Tranquilo que esta em busca da sua primeira oportunidade. Aqui esta meu Linkedln caso queiram se conectar: Marcos Vinicius 🤝🏻

cara-tranquilo.jpg

Carregando publicação patrocinada...
5

De fato, Big-O é sobre o quão bem o desempenho escala conforme a quantidade de dados aumenta.

Mas só pra ser chato, mesmo assim vc cometeu um erro/vício que muitos de nós cometemos: colocou "tempo" nas definições. Só que Big-O não é sobre tempo. Ele é uma definição matemática que - e aqui vou ser bem teórico e genérico - descreve o comportamento assintótico (o "limite") de uma função quando o seu parâmetro se aproxima de determinado valor. Quando aplicado a algoritmos, ele dá uma ideia de como eles escalam conforme o tamanho dos dados aumenta (lembrando que ele é um limite superior, portanto considera o pior caso).

Outra definição encontrada aqui:

A ideia da notação Big-O é descrever o comportamento geral (também chamado de assintótico, pois é o comportamento no limite conforme os dados crescem) do algoritmo em termos do crescimento do número de operações conforme cresce o número de elementos processados (a quantidade de itens é descrita, genericamente, por n).


Então o correto não é "tempo linear", "tempo constante", etc, e sim "complexidade linear", "complexidade constante" e por aí vai.

Mas dois algoritmos diferentes que são O(1), por exemplo, não necessariamente vão demorar o mesmo tempo (pode ser que no primeiro as instruções demorem mais, mas se todas sempre demoram o mesmo tempo, ainda será O(1)).

O mesmo algoritmo pode demorar mais em um hardware mais fraco.

Dois algoritmos O(n) não necessariamente são equivalentes, pois as constantes são desconsideradas: se um algoritmo percorre um array 2 vezes e outro percorre 5 vezes, ambos são O(n) - pois O(2n) e O(5n) são equivalentes do ponto de vista da definição matemática: a ideia é que, conforme N fica muito grande (quanto mais se aproxima do infinito), mais insignificante se tornam os fatores 2 e 5, e portanto eles são eliminados, ficando apenas o N.

E por aí vai, Big-O te dá uma informação importante sobre o algoritmo, mas não substitui um teste com dados reais no hardware final. Até porque, se forem poucos dados, aí as preocupações de escala se tornam menos relevantes. Afinal, para poucos dados, tudo é rápido.

O que o Big-O está contando de fato é a quantidade de "operações". Pegando um trecho deste link, em tradução livre:

Quando alguém diz que a complexidade de "tempo" do MergeSort é O(nlogn), na verdade eles querem dizer que o número de comparações que o MergeSort faz é O(nlogn). Isso por si só não diz muito sobre a complexidade de tempo que qualquer MergeSort em particular tenha, porque isso depende de quanto tempo demora para fazer uma comparação. Em outras palavras, O(nlogn) se refere a comparações como uma operação primitiva.

O importante aqui é que Big-O é aplicado a algoritmos, mas sempre há um modelo computacional por trás. Ao dizer que a complexidade de tempo do MergeSort é O(nlogn), implicitamente estamos referenciando um modelo computacional no qual o tempo da comparação é constante e todo o resto é "de graça".

Por exemplo, comparar dois números pode ser mais rápido que comparar duas strings, ou dois objetos complexos que exigem que vários campos sejam comparados (dois usuários, que devem ser ordenados por nome e idade), etc. O algoritmo de ordenação continua tendo a mesma complexidade (a mesma quantidade de comparações), mas o tempo vai variar de acordo com o que está sendo comparado.

Outro link que pode ajudar.

1

Muito obrigado pela contribuição. Eu vou editar o post e colocar esses links. Também vou ler quando chegar em casa. E quem lê a parte dos comentários vai ver essa excelente contribuição!!

Mais uma vez, obrigado por compartilhar conhecimento