Skip to main content

Command Palette

Search for a command to run...

Introdução a algoritmos — Notação Big O

Updated
7 min read
Introdução a algoritmos — Notação Big O

Irei escrever sobre algoritmos e estruturas de dados em paralelo com o livro Entendendo Algoritmos: Um Guia Ilustrado Para Programadores e Outros Curiosos de Aditya Y. Bhargava.

Estou usando o livro para aprender sobre algoritmos e estruturas de dados, e escrever sobre o que estou aprendendo, pois acredito que assim eu consiga fixar melhor o conteúdo.

Capítulo 1 — Introdução a algoritmos

Notação Big O

A notação Big O é usada para descrever a complexidade de um algoritmo. Ela é usada para descrever o pior caso de um algoritmo, ou seja, o caso em que ele vai demorar mais para ser executado.

Com essa notação, podemos comparar a complexidade de dois algoritmos e dizer qual deles é mais eficiente.

O que é complexidade de um algoritmo?

A complexidade de um algoritmo é uma medida da quantidade de tempo e espaço que ele usa para executar.

Vale lembrar que a eficiência de um algoritmo não é medida em segundos, mas sim em operações. Por exemplo, se um algoritmo demora 1 segundo para executar, mas usa 10 operações, ele é mais eficiente que um algoritmo que demora 1 segundo para executar, mas usa 1000 operações.

Exemplos do livro

No livro, o autor dá alguns exemplos de algoritmos e como eles são descritos usando a notação Big O.

Um dos exemplos é o algoritmo de busca linear, usado para procurar um elemento em um array. Ele é descrito como O(n), onde n é o tamanho do array.

Outro exemplo é o algoritmo de busca binária, usado para procurar um elemento em um array ordenado. Ele é descrito como O(log n), onde n é o tamanho do array.

Para saber mais sobre pesquisa binária, veja o meu post sobre o assunto: pesquisa binária.

Tempo de execução de um algoritmo

O tempo de execução de um algoritmo é a quantidade de tempo que ele leva para ser executado.

Tempo de execução difere de complexidade de um algoritmo. A complexidade de um algoritmo é uma medida da quantidade de tempo e espaço que ele usa para executar. Já o tempo de execução é a quantidade de tempo que ele leva para ser executado.

O tempo de execução cresce a taxas diferentes para diferentes algoritmos.

Por exemplo, o tempo de execução de um algoritmo de busca linear cresce linearmente, enquanto o tempo de execução de um algoritmo de busca binária cresce logaritmicamente.

Vamos ver isso em gráficos:

Gráfico de tempo de execução de um algoritmo de busca linear

Gráfico de tempo de execução de um algoritmo de busca binária

Créditos das Imagens: 101computing.net

Podemos ver que o tempo de execução de um algoritmo de busca linear cresce linearmente, enquanto o tempo de execução de um algoritmo de busca binária cresce logaritmicamente.

Mas o que isso significa?

Quando falamos que o tempo de execução de um algoritmo cresce linearmente, queremos dizer que o tempo de execução do algoritmo aumenta conforme o tamanho do input.

Vou usar um exemplo do livro para explicar isso.

Em uma lista com 100 elementos, o algoritmo de busca linear vai precisar percorrer todos os 100 elementos para encontrar o elemento que estamos procurando. Já em uma lista com 1000 elementos, o algoritmo de busca linear vai precisar percorrer todos os 1000 elementos para encontrar o elemento que estamos procurando.

Ou seja, o tempo de execução do algoritmo de busca linear cresce linearmente conforme o tamanho do input.

Já quando falamos que o tempo de execução de um algoritmo cresce logaritmicamente, queremos dizer que o tempo de execução do algoritmo aumenta conforme o tamanho do input, mas de forma mais lenta.

Em uma lista com 100 elementos, o algoritmo de busca binária vai precisar percorrer 7 elementos para encontrar o elemento que estamos procurando. Já em uma lista com 1000 elementos, o algoritmo de busca binária vai precisar percorrer 10 elementos para encontrar o elemento que estamos procurando.

Ou seja, o tempo de execução do algoritmo de busca binária cresce logaritmicamente conforme o tamanho do input.

Se formos considerar que leva um 1ms para percorrer um elemento, podemos ver que o algoritmo de busca linear leva 100ms para percorrer uma lista com 100 elementos, enquanto o algoritmo de busca binária leva 7ms para percorrer uma lista com 100 elementos.

Viu como o algoritmo de busca binária é mais eficiente que o algoritmo de busca linear?

Tipos de algoritmos e suas complexidades

Já vimos dois exemplos de algoritmos e suas complexidades: o algoritmo de busca linear e o algoritmo de busca binária.

O algoritmo de busca linear é descrito como O(n), onde n é o tamanho do array. Já o algoritmo de busca binária é descrito como O(log n), onde n é o tamanho do array.

Vamos ver mais alguns exemplos de algoritmos e suas complexidades:

O(1) — Constante

O algoritmo com complexidade O(1) é um algoritmo que sempre vai executar o mesmo número de operações, independente do tamanho do input.

Um exemplo de algoritmo com complexidade O(1) é o algoritmo de busca sequencial, usado para procurar um elemento em um array.

O gráfico abaixo mostra o tempo de execução do algoritmo de busca sequencial:

Gráfico de tempo de execução de um algoritmo de busca sequencial

Créditos da Imagem: 101computing.net

Como podemos ver, o tempo de execução do algoritmo de busca sequencial é constante, independente do tamanho do input.

O(n2) — Quadrático

O algoritmo com complexidade O(n2) é um algoritmo que vai executar o número de operações igual ao quadrado do tamanho do input.

Um exemplo de algoritmo com complexidade O(n2) é o algoritmo de ordenação por seleção, usado para ordenar um array.

O gráfico abaixo mostra o tempo de execução do algoritmo de ordenação por seleção:

Gráfico de tempo de execução de um algoritmo de ordenação por seleção

Créditos da Imagem: 101computing.net

Como podemos ver, o tempo de execução do algoritmo de ordenação por seleção cresce quadraticamente, ou seja, o tempo de execução do algoritmo de ordenação por seleção é igual ao quadrado do tamanho do input.

O(2n) — Exponencial

O algoritmo com complexidade O(2n) é um algoritmo que vai executar o número de operações igual a 2 elevado ao tamanho do input.

Um exemplo de algoritmo com complexidade O(2n) é o algoritmo de busca exaustiva, usado para procurar um elemento em um array.

O gráfico abaixo mostra o tempo de execução do algoritmo de busca exaustiva:

Gráfico de tempo de execução de um algoritmo de busca exaustiva

Créditos da Imagem: 101computing.net

Como podemos ver, o tempo de execução do algoritmo de busca exaustiva cresce exponencialmente, ou seja, o tempo de execução do algoritmo de busca exaustiva é igual a 2 elevado ao tamanho do input.

Caixeiro Viajante

Um problema muito comum em algoritmos é o problema do caixeiro viajante, sendo um problema de otimização.

O problema do caixeiro viajante é um problema em que temos um conjunto de cidades e queremos encontrar a rota mais curta para visitar todas as cidades uma única vez e voltar para a cidade de origem.

Vamos ver um exemplo de problema do caixeiro viajante:

Problema do caixeiro viajante

Créditos da Imagem: optimoroute.com

Como podemos ver, temos um conjunto de cidades e queremos encontrar a rota mais curta para visitar todas as cidades uma única vez e voltar para a cidade de origem.

Uma solução para esse problema seria percorrer todas as cidades e calcular a distância entre elas, e depois escolher a rota com a menor distância.

Seguindo do princípio que teríamos somente 5 cidades, teríamos 5! = 120 combinações possíveis de rotas.

Mas se tivéssemos 6 cidades, teríamos 6! = 720 combinações possíveis de rotas.

Esse problema é chamado de problema de otimização combinatória, pois temos um conjunto de combinações possíveis e queremos encontrar a melhor combinação.

Segue a tabela com o número de combinações possíveis para cada número de cidades:

Como podemos ver, o número de combinações possíveis cresce exponencialmente, ou seja, o número de combinações possíveis é igual a 2 elevado ao número de cidades.

Como o número de combinações possíveis cresce exponencialmente, não é possível calcular todas as combinações possíveis para um número grande de cidades.

Esse problema é chamado de problema NP-Completo, sendo um problema que não é possível calcular todas as combinações possíveis.

Dessa forma, não é possível encontrar a melhor solução para esse problema, mas podemos encontrar uma solução aproximada para esse problema.

Deixei esse problema para o final, por ser um problema que é muito comum em algoritmos e gera muita curiosidade pela sua complexidade.

Finalização

Essa foi uma pequena documentação dos meus estudos, espero que possa ajudar mais pessoas.
Qualquer dúvida, correção ou sugestão pode deixar nos comentários.

More from this blog

L

Luiz Schons

28 posts