Análise Assintótica de Algoritmos

Palavras-Chave

Em análise de algoritmos, a análise da eficiência assintótica de um algoritmo é explorada quando observamos tamanhos de entrada grande o suficiente para tornar relevante apenas a ordem de crescimento do tempo de execução, ou seja, na análise assintótica atenta-se a maneira como o tempo de execução de um algoritmo aumenta, à medida que o tamanho da entrada aumenta indefinidamente.

A ideia de complexidade assintótica foi introduzida em 1965 por Jack Edmonds (1934-) e consiste em uma função que define limites para a função $T(N)$ (complexidade local). Em outras palavras, a complexidade assintótica não fornece a complexidade local de um algoritmo, mas é capaz de estabelecer limites para o uso de recursos computacionais em sua execução. Desta forma, o conceito de complexidade assintótica viabiliza a comparação da complexidade entre diferentes algoritmos. Para tal, a complexidade assintótica é denotada através de uma COTA (ou classe de complexidade) que reduz detalhes da sua complexidade local, realçando apenas os aspectos relevantes da sua eficiência. (Figura 1).

Figura 1 – Cotas de diferentes algoritmos.

Fonte: Autor.

Geralmente, um algoritmo que é assintoticamente mais eficiente será a melhor escolha para todas as entradas, exceto as muitos pequenas.

Notação Big-O

Matematicamente, considerando um conjunto $A$ de algoritmos para a solução de um dado problema. A cada algoritmo $a \in A$ temos associado a função de avaliação correspondente $\text{test}(a)$. Nesse contexto, é de interesse identificar um algoritmo ótimo, ou seja, um algoritmo $o$ tal que $\text{test}(o)$ é melhor que $\text{test}(a)$ para todo $a \in A$. Assim, a análise da complexidade assintótica é usada na buscar do melhor algoritmo dentre um conjunto de algoritmos para solucionar um problema. Para isso se adota um conjunto de notações que auxiliam a formular conclusões sobre a comparação da complexidade de algoritmos. Essa notação é denominada notação Big-O e é apresentada abaixo:

  1. notação $O$ : limite assintótico superior;
  2. notação $\Omega$ : limite assintótico inferior;
  3. notação $\Theta$ : limite assintoticamente restrito;
  4. notação $o$ : limite assintoticamente superior não restrito;
  5. notação $\omega$ : limite assintoticamente inferior não restrito.

Notação $O$, Limite Assintótico Superior

Dadas funções assintoticamente não negativas $f$ e $g$, dizemos que $f$ está na ordem $O$ de $g$ e escrevemos $f=O(g)$ se:

$f(n) \leq c \cdot g(n)$

para algum $c$ positivo e para todo $n$ suficientemente grande.

Assim, diz-se que uma função limite superior $g(n)$ domina assintoticamente outra função $f(n)$ se existem duas constantes positivas $c$ e $n_0$ tais que, para qualquer $n \geq n_0$ temos, $f(n) \leq c \cdot g(n)$. Se escreve que $f(n)=O(g(n))$ ou $f(n) \in O(g(n))$ para expressar essa dominação (Figura 2).

Figura 2 – Notação $O$, limite assintótico superior.

Fonte: Autor.

Matematicamente, temos:

$O(g(n))=\{ \, f(n) : \exists \,\, c \,\, e \,\, n_0 > 0 \,\, | \,\, 0 \leq f(n) \leq c \cdot g(n) \,\, \forall \,\, n \geq n_0 \, \}$

Onde a notação $O$ pode ser lida de forma formal ou informal, como:

  • $f(n)$ é da ordem no máximo $g(n)$ (formal);
  • $f(n)$ é $O$ de $g(n)$ (informal);
  • $f(n)$ é igual a $O$ de $g(n)$ (informal);
  • $f(n)$ pertence a $O$ de $f(n)$ (formal).

Alguns exemplos da associação de COTAS e notação Big-O são apresentadas abaixo:

Notação Nome Algoritmos
$O(1)$ constante Determinar se um número é par ou ímpar. Encontrar o valor máximo em um arranjo ordenado.
$O(\log{n})$ logarítmica Encontrar um valor em um arranjo ordenado usando busca binária.
$O(n)$ linear Encontrar um valor em um arranjo não ordenado usando busca sequencial.
$O(n\log{n})$ linear logarítmica Ordenar um arranjo usando quicksort.
$O(n^2)$ quadrática Ordenar um arranjo usando bubblesort.
$O(n^3)$ cúbica Multiplicar matrizes (produto matricial).
$O(c^n)$, $c>1$ exponencial Encontrar a solução exata para o problema do caixeiro viajante usando programação dinâmica.
$O(n!)$ fatorial Encontrar a solução exata para o problema do caixeiro viajante usando força bruta.

Operações Básicas

$\qquad \qquad \qquad \,\, f(n)=O(f(n))$

$\qquad \qquad c \times Of(n)=O(f(n))$

$\, O(f(n))+O(f(n))=O(f(n))$

$\qquad \quad \,\, O(O(f(n)))=O(f(n))$

$\qquad \qquad \quad \,\, O(f(n))+O(g(n))=O(max(f(n),g(n)))$

$\qquad \quad \,\,\, O(f(n))O(g(n))=O(f(n)g(n))$

$\qquad \qquad \quad f(n)O(g(n))=O(f(n)g(n))$

Notação $\Omega$, Limite Assintótico Inferior

Dadas funções assintoticamente não negativas $f$ e $g$, dizemos que $f$ está na ordem $\Omega$ de $g$ e escrevemos $f=\Omega (g)$ se:

$f(n) \geq c \cdot g(n)$

para algum $c$ positivo e para todo $n$ suficientemente grande.

Assim, diz-se que uma função limite inferior $g(n)$ domina assintoticamente outra função $f(n)$ se existem duas constantes positivas $c$ e $n_0$ tais que, para qualquer $n \geq n_0$ temos, $f(n) \geq c \cdot g(n)$. Se escreve que $f(n)=\Omega (g(n))$ ou $f(n) \in \Omega (g(n))$ para expressar essa dominação (Figura 3).

Figura 3 – Notação $\Omega$, limite assintótico inferior.

Fonte: Autor.

Matematicamente, temos:

$ \Omega (g(n))=\{ \, f(n) : \exists \,\, c \,\, e \,\, n_0 > 0 \,\, | \,\, 0 \leq c \cdot g(n) \leq f(n) \,\, \forall \,\, n \geq n_0 \, \}$

A notação $\Omega$ aparece na definição do conceito da notação $\Theta$ e não costuma ser vista sozinha fora deste escopo em análise de algoritmos uma vez que são de interesse as análises dos limites superiores ou o intervalo de operação dos algoritmos e não sua limitação inferior, ou seja, o limite inferior $\Omega$ não é importante para conclusões práticas sobre algoritmos enquanto a notação $O$ conclui que seu algoritmo é no máximo tão complexo a uma função que o limita.

Notação $\Theta$, Limite Assintoticamente Restrito

A notação $O$ fornece informações sobre a complexidade de um algoritmo, mas nem sempre revela algo importante. Constata-se esta realidade uma vez que não faz sentido para algum algoritmo de ordem $n$ dizer que suas complexidades são como, por exemplo:

$n \in O(2^n)$

$n \in O(n^n)$

Estas notações são verdadeiras, contudo, são também imprecisas. Assim, para estimar uma precisão sobre a complexidade de um algoritmo se defini um limite firme ou assintoticamente restrito,. Esse limite é conhecido como a notação $\Theta$.

Dadas funções assintoticamente não negativas $f$ e $g$, dizemos que $f$ está na mesma ordem de $g$ e escrevemos $f= \Theta (g)$ se $f=O(g)$ e $f= \Omega (g)$. Em outras palavras, $f=\Theta (g)$ significa que existe números positivos $c_1$ e $c_2$ tais que:

$c_1 \cdot g(n) \leq f(n) \leq c_2 \cdot g(n)$

para todo $n$ suficientemente grande.

Assim, diz-se que uma função $g(n)$ está na mesma ordem de outra função $f(n)$ se existem três constantes positivas $c_1$, $c_2$ e $n_0$ tais que, para qualquer $n \geq n_0$ temos, $c_1 \cdot g(n) \leq f(n) \leq c_2 \cdot g(n)$. Se escreve que $f(n)=\Theta (g(n))$ ou $f(n) \in \Theta (g(n))$ para expressar essa dominação (Figura 4).

Figura 4 – Notação $\Theta$, limite assintoticamente restrito.

Fonte: Autor.

Matematicamente, temos:

$ \Theta (g(n))=\{ \, f(n) : \exists \,\, c_1, \,\, c_2 \,\, e \,\, n_0 > 0 \,\, | \,\, 0 \leq c_1 \cdot g(n) \leq f(n) \leq c_2 \cdot g(n) \,\, \forall \,\, n \geq n_0 \, \}$

Logo,

$ f(n) \in \Theta (g(n)) $ se e somente se $ f(n) \in O (g(n)) $ e $ f(n) \in \Omega (g(n)) $

Contudo, cabe observar que nem toda função $\Omega$ é uma função $\Theta$ e nem toda função $O$ é uma função $\Theta$, mas uma função $\Theta$ é, necessariamente, $O$ e $\Omega$.

Notação $o$, Limite Assintoticamente Superior não Restrito

Toda as funções de $O$ que não definem um limite assintoticamente restrito pertence a “o”.

se $ f(n) \in O(g(n)) $ e $ f(n) \notin \Omega (g(n)) $ então $ f(n) \in o(g(n)) $

como, por exemplo:

$f(n)$ $g(n)$ notação
$2n^2$ $n^2$ $2n^2 \in O(n^2)$
$2n$ $n^2$ $2n^2 \in O(n^2)$  e  $2n^2 \in o(n^2)$
$\log(n)$ $c^n$ $\log(n) \in O(c^n)$  e  $\log(n) \in o(c^n)$

Matematicamente, temos:

$ o(g(n))=\{ \, f(n) : \exists \,\, c \,\, e \,\, n_0 > 0 \,\, | \,\, 0 \leq f(n) < c \cdot g(n) \,\, \forall \,\, n \geq n_0 \, \}$

Logo, $ f(n) \in o(g(n)) $, o limite $ 0 \leq f(n) < c \cdot g(n) $ se mantém válido para todas as constantes $ c>0 $. Já $ f(n) \in O(g(n)) $, o limite $ 0 \leq f(n) \leq c \cdot g(n) $ se mantém válido para alguma constantes $ c>0 $.

Se $ f(n) \in o(g(n)) $ então:

$\displaystyle \lim_{ n \to \infty} \frac{f(n)}{g(n)}=0$

Notação $\omega$, Limite Assintoticamente Inferior não Restrito

Toda as funções de $\Omega$ que não definem um limite assintoticamente restrito pertence a “$\omega$”.

se $ f(n) \notin O(g(n)) $ e $ f(n) \in \Omega (g(n)) $ então $ f(n) \in \omega (g(n)) $

como, por exemplo:

$f(n)$ $g(n)$ notação
$2n^2$ $n^2$ $2n^2 \in \Omega (n^2)$
$2n^2$ $n$ $2n^2 \in \Omega (n^2) \,\,$ e $\,\, 2n^2 \in \omega(n^2)$
$c^n$ $\log (n)$ $ c^n \in \Omega(\log (n)) \,\,$ e $\,\, c^n \in \omega(\log (n))$

Matematicamente, temos:

$ \omega (g(n))=\{ \, f(n) : \exists \,\, c \,\, e \,\, n_0 > 0 \,\, | \,\, 0 \leq c \cdot g(n) < f(n) \,\, \forall \,\, n \geq n_0 \, \}$

Logo, $ f(n) \in \omega (g(n)) $, o limite $ 0 \leq c \cdot g(n) < f(n) $ se mantém válido para todas as constantes $ c>0 $. Já $ f(n) \in \Omega (g(n)) $, o limite $ 0 \leq c \cdot g(n) \leq f(n) $ se mantém válido para alguma constantes $ c>0 $.

Se $ f(n) \in \omega (g(n)) $ então:

$\displaystyle \lim_{ n \to \infty} \frac{f(n)}{g(n)}=\infty$

As relações descritas entre os conjuntos que representam as notações apresentadas podem ser visualizadas a seguir:

Figura 5 – O conjunto de notações Big-O e a relação entre elas.

Fonte: Autor.

Limites para Comparar Ordens de Grandeza

O limite da razão das funções $f(n)$ e $g(n)$ podem levar a três casos:

$ \displaystyle \lim_{ n \to \infty} \frac{f(n)}{g(n)} = \left\{ \begin{matrix} 0, \, f(n) \text{ tem ordem de grandeza menor que g(n);} \\ c>0, \, f(n) \text{ tem a mesma ordem de grandeza que g(n);} \\ \infty, \, f(n) \text{ tem ordem de grandeza maior que g(n);} \end{matrix} \right. $

Caso tenhamos uma indeterminação da forma $\frac{\infty}{\infty}$, utiliza-se da regra de L’Hôpital para sua aferição:

$\displaystyle \lim_{ n \to \infty} \frac{f(n)}{g(n)}=\lim_{ n \to \infty} \frac{f'(n)}{g'(n)}$

Comparação de funções

Muitas das propriedades relacionais de números reais também se aplicam a comparações assintótica. No caso das propriedades seguintes, suponha $f(n)$ e $g(n)$ sejam assintoticamente positivas.

Reflexividade

$f(n)=\Theta(f(n))$

$f(n)=O(f(n))$

$f(n)=\Omega(f(n))$

Simetria

$f(n)=\Theta(g(n))$ se e somente se $g(n)=\Theta(f(n))$

Simetria de Transposição

$f(n)=O(g(n))$ se e somente se $g(n)=\Omega(f(n))$

$f(n)=o(g(n))$ se e somente se $g(n)=\omega(f(n))$

Transitividade

$f(n)=\Theta(g(n))$ e $g(n)=\Theta(b(n))$ $\Rightarrow$ $f(n)=\Theta(b(n))$

$f(n)=O(g(n))$ e $g(n)=O(b(n))$ $\Rightarrow$ $f(n)=O(b(n))$

$f(n)=\Omega(g(n))$ e $g(n)=\Omega(b(n))$ $\Rightarrow$ $f(n)=\Omega(b(n))$

$f(n)=o(g(n))$ e $g(n)=o(b(n))$ $\Rightarrow$ $f(n)=o(b(n))$

$f(n)=\omega(g(n))$ e $g(n)=\omega(b(n))$ $\Rightarrow$ $f(n)=\omega(b(n))$

Padrões de Identificação de Passos em um Algoritmo

Uma sequência sem laço ou recursão conta passo constante $O(1)$:


Quando divide o problema pela metade conta passo logarítimo $O(\log{n})$:


Um único laço com $n$ passos inteiros constante conta passo linear $O(n)$:


Dois laços de tamanho $n$ alinhados conta passo quadrático $O(n^2)$:


Concluindo, a complexidade assintótica é um conceito utilizado na análise da complexidade de um algoritmo e que é largamente empregado, principalmente quando a natureza de sua complexidade é matematicamente árdua ou dificilmente capaz de ser formulada para análise exata de seu comportamento. O conceito de complexidade assintótica e o da notação Big-O propiciam uma metodologia capaz de estimar o crescimento da demanda de recursos computacionais por um algoritmo sem ter uma noção exata de seu comportamento, viabilizando o estudo da resolução de um problema por diferentes algoritmos através da observação de seu comportamento e da comparação entre suas eficiências (Figura 6).

Figura 6 – Complexidade assintóticas de diferentes algoritmos.

Fonte: Autor.

Referências

  • PIVA JÚNIOR, Dilermando (et al). Estrutura de dados e técnicas de programação. 1. ed. Rio de Janeiro, RJ: Campus, 2014. 399 p. ISBN: 9788535274370.
  • CORMEN, Thomas H et al. Algoritmos: teoria e prática. Rio de Janeiro: Elsevier, 926 p. ISBN: 9788535236996.
  • TOSCANI, Laira Vieira; VELOSO, Paulo A. S. Complexidade de algoritmos: análise, projeto e métodos. 3. ed. Porto Alegre: Bookman, 2012. 261 p. (Série livros didáticos informática UFRGS, 13) ISBN: 9788540701380.
  • ASCENCIO, Ana Fernanda Gomes. Estruturas de dados: algoritmos, análise da complexidade e implementações em Java e C/C++. São Paulo: Pearson, c2010. 432 p. ISBN: 9788576052216, 978857605816.
  • BRASSARD, Gilles; BRATLEY, Paul. Fundamentals of algorithmics. Englewood Cliffs: Prentice Hall, c1996. xx, 524 p. ISBN: 0133350681.

Livraria