Assunto
- Complexidade de Algoritmos
- A complexidade de um algoritmo reflete o esforço computacional requerido para executá-lo.
- As principais medidas de complexidade são a velocidade de execução, o consumo de memória e a quantidade de comunicação (mais utilizada em algoritmos distribuídos).
- A análise matemática do tempo de execução de um algoritmo permite que a análise seja independente da implementação, hardware e ambiente, reduzindo o número de variáveis na obtenção de um comportamento esperado para o algoritmo.
- O esforço computacional está, normalmente, relacionado ao tamanho da entrada de dados do algoritmo (ex.: ordenação de elementos, inversão de matriz, número de Fibonacci).
- Além do tamanho da entrada, a organização dos elementos pode ser relevante na determinação do desempenho do algoritmo.
- Complexidade de Tempo
- Em geral é o tipo de complexidade que mais nos preocupamos.
- Está relacionada ao tempo de execução de um algoritmo.
- Medida em relação ao número de operações que o algoritmo executa no modelo computacional utilizado na sua criação.
- Complexidade de Espaço
- Medida de desempenho relacionada a quantidade de memória necessária para a execução de um algoritmo.
- Complexidade de Comunicação
- Em algoritmos distribuídos, o tempo de comunicação é muito relevante e é mais uma medida de complexidade que deve ser considerada.
- A complexidade de comunicação esta fora do escopo dessa disciplina, porém acredito que com a base proporcionada, aliada a de outras disciplinas, permitirá que o aluno encentre as soluções para esse tipo de problema.
- Modelo computacional para análise de desempenho
- Para analizar o desempenho de um algoritmo, precisamos definir o modelo computacional sobre o qual queremos verificar o número de operações
- Definimos uma operação fundamental que é a base do algoritmo, e assumimos que o algoritmo irá executar mais rapidamente com a redução do número de execuções dessa operação (ex.: comparação em relação a ordem em algoritmos de ordenação, comparação em relação a igualdade em algoritmos de busca).
- Exemplo: Valor máximo em uma lista.
- Tempo de Execução de um algoritmo
\(t(a,n) = \frac{c + T(a)\times{n}}{\frac{op}{s}}\)
Dada uma máquina com $X$ operações por segundo, e outra com $2X$ operações por segundo, a segunda máquina executaria o mesmo algoritmo em, aproximadamente, metade do tempo da primeira, para a mesma entrada.
- Complexidade de Algoritmos
- A complexidade assintótica é definida pelo crescimento da complexidade para entradas suficientemente grandes.
- Levando em consideração a execução de dois algoritmos na mesma máquina, com a mesma entrada, temos uma comparação entre $c_{1} + T(a_{1})\times{n}$ com $c_{2} + T(a_{2}\times{n})$, onde claramente as constantes associadas aos algoritmos ($c_{1}$ e $c_{2}$) tem influência na comparação.
- No entanto, para valores muito grandes de $n$, a diferença entre as contantes tende a ser desprezível, fazendo com que a diferença da complexidade de tempo dos algoritmos domine a comparação.
- Exemplos:
func linear_search(A, v):
for i in 1..A.length:
if A[i] == v:
return i
return nil
func binary_search(A, k):
p = 1
q = A.length
while p != q:
m = p + (q - p) / 2
if k <= A[m]:
q = m
else:
p = m + 1
if k != A[q]:
return nil
return p
func insertion_sort(A):
for j in 2 to A.length:
k = A[j]
i = j - 1
while i > 0 and A[i] > k:
A[i + 1] = A[i]
i = i - 1
A[i + 1] = k
- Complexidade Assintótica
- Usualmente o cálculo da complexidade concentra-se em determinar a ordem de magnitude do número de operações fundamentais na execução de um algoritmo. Podemos utilizar a ideia de cota assintótica superior.
- Dada a função $f(n)$, uma função $g(n)$ representa $O(g(n))$ se $(\exists n_{0} \in \mathbb{N})\ (\forall n \ge n_{0}): f(n) \le g(n)$
- Uma função que sempre domine outra (ex. $n^{2} \ge n$) representa uma cota assintótica superior.
- Basta que uma função domine a outra apenas a partir de um número $n_{0}$, com n grande o suficiente, para que seja considerada uma cota assintótica superior
- Notação Big-O
- Se uma função $c \in \mathbb{R}^{+}: c\times{g}(n)$ domina uma função $f(n)$, a partir de um $n_{0} \in \mathbb{N}$, temos $f(n) = O(g(n))$, ou seja, $(\exists c \in \mathbb{R}^{+})(\exists n_{0} \in \mathbb{N})(\forall n \ge n_{0}): f(n) \le c\times{g}(n)$
- Dizer que um algoritmo é $O(n)$ significa que o tempo de execução do algoritmo é diretamente proporcional a sua entrada, com limite superior dado por uma função linear $g(n) = c \times n$.
- Dizer que um algoritom é $O(1)$ significa dizer que o número de operações fundamentais é limitado por uma constante.
- Notação $\Omega$
- Se uma função $c \in \mathbb{R}^{+}: {g}(n)$ é dominada por uma função $c\times f(n)$, a partir de um $n_{0} \in \mathbb{N}$, temos $f(n) = O(g(n))$, ou seja, $(\exists c \in \mathbb{R}^{+})(\exists n_{0} \in \mathbb{N})(\forall n \ge n_{0}): c\times{f}(n) \ge g(n)$.
- A notação $\Omega$ define uma cota assintótica inferior.
- Notação $\Theta$
- Dadas duas funções $f(n)$ e $g(n)$ e duas constanstes $c \in \mathbb{N}$ e $d \in \mathbb{N}$ , $f(n) \ \Theta(g(n))$, se e apenas se, $c\times{g}(n) \le f(n) \le d\times{g}(n)$
- A notação $\Theta$ define um limite assintótico exato.
Questões
- Por que a medida de tempo gasto na execução de um algoritmo não é uma boa medida para determinar a complexidade de tempo para um algoritmo?
- É razoável esperar que o desempenho de um algoritmo sempre cresça com o tamanho da entrada? Justifique sua resposta.
- Calcule a complexidade do algoritmo de ordenação
Selection Sort
. Qual é a complexidade Big-O? Qual é a complexidade $\Omega$?
- Qual a complexidade de espaço dos algoritmos apresentados nessa aula?
- Projeto de Implementação: Implementar os algoritmos de ordenação
Insertion Sort
e Merge Sort
e imprimir ao final da execução o número de operações fundamentais que foram executadas.
Recursos para essa aula
Bibliografia
Toscani, Laira Vieira; Veloso, Paulo A. S. Complexidade de Algoritmos. Capítulo 2.