Introdução
Ao aprender a programar, entender a eficiência dos algoritmos é muito importante. Nesse momento, o conceito de ** complexidade ** (Complexity) inevitavelmente aparece. Neste artigo, explicaremos exaustivamente os fundamentos da complexidade de tempo e de espaço, uma explicação detalhada da notação O (Big O Notation) e considerações profundas com exemplos práticos, em um volume de cerca de 20.000 caracteres.
O que é complexidade
A métrica para avaliar o desempenho de um algoritmo é a complexidade. A complexidade pode ser dividida principalmente nas duas seguintes:
- ** Complexidade de Tempo ** (Time Complexity)
- ** Complexidade de Espaço ** (Space Complexity)
1. Complexidade de Tempo
A complexidade de tempo é uma métrica que representa o “tempo” ou “número de passos” necessários até que a execução de um algoritmo seja concluída.
2. Complexidade de Espaço
A complexidade de espaço é uma métrica que representa o “espaço de memória” necessário até que a execução de um algoritmo seja concluída.
O que é a Notação O (Big O Notation)
A notação O (Big O Notation) é uma notação matemática que indica o limite superior da taxa de aumento da complexidade quando o tamanho da entrada \(n\) se torna suficientemente grande.
\[ O(f(n)) = \{ g(n) \mid \text{Existem constantes positivas } c, n_0 \text{ tais que para todo } n \ge n_0 \text{ satisfaz } 0 \le g(n) \le c f(n) \} \]Regras básicas da Notação O
- ** Ignorar termos constantes ** : \(O(2n)\) se torna \(O(n)\).
- ** Manter apenas o termo de maior impacto ** : \(O(n^2 + n)\) se torna \(O(n^2)\).
graph TD
A["Tamanho da entrada n"] -->|"Avaliação"| B["Notação O"]
B --> C["Complexidade de Tempo"]
B --> D["Complexidade de Espaço"]
Principais Complexidades de Tempo e Exemplos em Python
A partir daqui, vamos ver explicações detalhadas e exemplos de código em Python para as principais classes da notação O.
1. O(1) : Tempo Constante (Constant Time)
É um algoritmo no qual o processamento é concluído em um número constante de passos, independentemente do tamanho da entrada \(n\).
| |
2. O(log n) : Tempo Logarítmico (Logarithmic Time)
À medida que o tamanho da entrada \(n\) aumenta, o tempo de execução também aumenta, mas o ritmo desse aumento é muito lento. Um exemplo típico é a busca binária.
| |
3. O(n) : Tempo Linear (Linear Time)
É um algoritmo em que o tempo de execução aumenta proporcionalmente ao tamanho da entrada \(n\).
| |
4. O(n log n) : Tempo Linearítmico (Linearithmic Time)
É o produto de O(n) e O(log n). Muitos algoritmos de ordenação por comparação eficientes (Merge Sort, Quick Sort, Heap Sort, etc.) possuem essa complexidade.
| |
5. O(n^2) : Tempo Quadrático (Quadratic Time)
O tempo de execução aumenta proporcionalmente ao quadrado do tamanho da entrada \(n\). Algoritmos de ordenação simples, como Bubble Sort e Insertion Sort, enquadram-se aqui.
| |
6. O(2^n) : Tempo Exponencial (Exponential Time)
Para cada aumento de 1 no tamanho da entrada \(n\), o tempo de execução dobra. Uma implementação recursiva simples da sequência de Fibonacci é um exemplo.
| |
7. O(n!) : Tempo Fatorial (Factorial Time)
O tempo de execução aumenta proporcionalmente ao fatorial do tamanho da entrada. A busca exaustiva (força bruta) do problema do caixeiro viajante se enquadra aqui.
| |
Estruturas de Dados e Complexidade
| Estrutura de Dados | Acesso | Busca | Inserção | Remoção | Complexidade de Espaço |
|---|---|---|---|---|---|
| Array | \(O(1)\) | \(O(n)\) | \(O(n)\) | \(O(n)\) | \(O(n)\) |
| Linked List | \(O(n)\) | \(O(n)\) | \(O(1)\) | \(O(1)\) | \(O(n)\) |
| Hash Table | - | \(O(1)\) | \(O(1)\) | \(O(1)\) | \(O(n)\) |
| BST | \(O(\log n)\) | \(O(\log n)\) | \(O(\log n)\) | \(O(\log n)\) | \(O(n)\) |
Algoritmos de Ordenação e Complexidade
| Algoritmo | Melhor | Médio | Pior | Complexidade de Espaço |
|---|---|---|---|---|
| Bubble Sort | \(O(n)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) |
| Merge Sort | \(O(n \log n)\) | \(O(n \log n)\) | \(O(n \log n)\) | \(O(n)\) |
| Quick Sort | \(O(n \log n)\) | \(O(n \log n)\) | \(O(n^2)\) | \(O(\log n)\) |
