Aula 04 - Análise de Algoritmos. Notação Assintótica I.
Universidade Federal do Rio Grande do Sul
Instituto de Informática
Departamento de Informática Teórica
Estes slides utilizam conteúdo adaptado da bibliografia da disciplina e também de notas de aula e slides prévios dos professores Bruno Grisci, Rodrigo Machado, André Grahl Pereira, Lucas Nunes Alegre, Marcus Ritt e Luciana Buriol.
Quando analisamos algoritmos, precisamos de uma métrica de custo.
Mas o que exatamente devemos medir?
Possíveis Medidas:
P: Qual dessas medidas é a mais relevante para analisar algoritmos?
Modelo RAM (Random Access Machine):
Modelo RAM (Random Access Machine):
P: Como analisar o tempo de execução de um algoritmo?
Observação: O tempo requerido por um algoritmo depende da entrada:
Precisamos levar o tamanho da entrada em consideração.
Tempo de Execução: Para uma entrada especifica, é o número de instruções primitivas (steps) executadas.
Instruções independente de máquina:
T(n) = número de instruções executadas para entrada de tamanho n.
O teste do laço é executado uma vez a mais que o conteúdo do bloco:
| InsertionSort(A): | Custo | Repetições | |
|---|---|---|---|
| 1. | for j \gets 2 to A.length | c_1 | n |
| 2. | \quad key \gets A[j] | c_2 | n-1 |
| 3. | \quad // Insere A[j] na lista ordenada A[1 \dots j-1] | 0 | n-1 |
| 4. | \quad i \gets j-1 | c_4 | n-1 |
| 5. | \quad while i > 0 and A[i] > key | c_5 | \sum_{j=2}^n t_j |
| 6. | \quad\quad A[i+1] \gets A[i] | c_6 | \sum_{j=2}^n (t_j - 1) |
| 7. | \quad\quad i \gets i-1 | c_7 | \sum_{j=2}^n (t_j - 1) |
| 8. | \quad A[i+1] \gets key | c_8 | n-1 |
Quantas execuções?
t_j: n° de repetições do laço da linha 5 para aquele valor de j.
Somando todos os Custos:
T(n) = c_1 n + c_2(n-1) + 0(n-1) + c_4(n-1) + c_5 \sum_{j=2}^n t_j + c_6 \sum_{j=2}^n (t_j - 1) + c_7 \sum_{j=2}^n (t_j - 1) + c_8(n-1)
Problema: Como nos livramos de t_j? O valor de t_j para cada j depende da sequência de entrada, que não temos como saber a priori.
Solução: Podemos analisar o melhor e o pior caso! Isto é, podemos inferir limites inferiores e limites superiores para t_j.
\begin{align} T(n) &= c_1 n + c_2(n-1) + 0(n-1) + c_4(n-1) + c_5 \sum_{j=2}^n t_j + \\ &\quad c_6 \sum_{j=2}^n (t_j - 1) + c_7 \sum_{j=2}^n (t_j - 1) + c_8(n-1) \end{align}
\begin{align} T(n) &= c_1 n + c_2(n-1) + 0(n-1) + c_4(n-1) + c_5 \sum_{j=2}^n 1 + c_6 \sum_{j=2}^n (1 - 1) \\ &\quad + c_7 \sum_{j=2}^n (1 - 1) + c_8(n-1)\\ &= c_1 n + c_2(n-1) + c_4(n-1) + c_5(n-1) + c_8(n-1) \\ &= (c_1 + c_2 + c_4 + c_5 + c_8)n - (c_2 + c_4 + c_5 + c_8) \\ &= a \cdot n + b \end{align}
Lembrando que \sum_{j=1}^{n}=n.
Melhor Caso: função linear em n.
\sum_{j=2}^n j = \frac{n(n+1)}{2} - 1 \quad \text{e} \quad \sum_{j=2}^n (j-1) = \frac{n(n-1)}{2}
\begin{align} T(n) &= c_1 n + c_2 (n-1) + c_4 (n-1) + c_5 \left(\frac{n(n+1)}{2}-1\right) + c_6 \left(\frac{n(n-1)}{2}\right) \\ &\quad + c_7 \left(\frac{n(n-1)}{2}\right) + c_8 (n-1) \\ &= \left( \frac{c_5}{2} + \frac{c_6}{2} + \frac{c_7}{2} \right) n^2 + \left( c_1 + c_2 + c_4 + \frac{c_5}{2} - \frac{c_6}{2} - \frac{c_7}{2} + c_8 \right) n - (c_2 + c_4 + c_5 + c_8)\\ &= a n^2 + b n + c \end{align}
Pior Caso: t_j = j
Pior Caso: função quadrática em n.
P: Por que o pior caso?
Observação: Nós em geral nos preocupamos em encontrar o tempo de execução do pior caso:
P: Alternativas ao pior caso?
P: Qual é caso médio do algoritmo de Ordenação por Inserção?
Observação: Para ordenar um vetor ordenado randomicamente com elementos distintos:
Observação: Então t_j = \frac{j}{2}, isso é aproximadamente metade do pior caso, mas ainda é uma função quadrática.
P: Quais instâncias aparecem no mundo real?
No nosso caso, abstraímos com unidades constantes de computação.
Passos unitários contados:
Observação: Nessa disciplina, também iremos considerar unidades constantes de armazenamento para analisar a complexidade de espaço.
Exemplo: Procurar x=23 em A[1..10]
| Índice | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| Valor | 42 | 7 | 85 | 13 | 66 | 90 | 23 | 5 | 77 | 31 |
Exemplo: Procurar x=42 em A[1..10]
| Índice | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| Valor | 42 | 7 | 85 | 13 | 66 | 90 | 23 | 5 | 77 | 31 |
Exemplo: Procurar x=15 em A[1..10]
| Índice | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| Valor | 42 | 7 | 85 | 13 | 66 | 90 | 23 | 5 | 77 | 31 |
Melhor Caso: quando encontramos o elemento logo na primeira comparação.
Pior Caso: quando precisamos examinar todos os elementos do vetor.
Caso Médio: quando o elemento pode aparecer em qualquer posição com igual probabilidade.
P: Qual desses casos é o mais relevante para analisar algoritmos em teoria da computação?
P: Qual o pior caso?
T(n) = 3n + 1
P: Qual o pior caso?
T(n) = 2 + 2 + 1 = 5
T(n) = (n-1)(n-1)(1+5) = 6n^2 - 12n + 6
Observação: Focar a análise nas características importantes.
Eficiência: Diferentes algoritmos para resolver o mesmo problema podem diferir dramaticamente em sua eficiência.
Problema de Ordenação: Uma sequência de n=10.000.000 números.
Algoritmo A: T(n) = 2n^2.
Algoritmo B: T(n) = 50n \log n.
Constantes c_1=2 e c_2=50 não dependem de n.
P: Qual será a diferença de eficiência dos dois algoritmos?
Computador 1: Executa 10 bilhões de instruções por segundo.
A-1: \dfrac{2 \cdot (10^7)^2 \text{ instruções}}{10^{10} \text{ instruções/segundo}} = 20.000 \text{ segundos } (5{,}5 \text{ horas}).
B-1: \dfrac{50 \cdot (10^7) \cdot \log(10^7) \text{ instruções}}{10^{10} \text{ instruções/segundo}} = 1{,}16 \text{ segundos}.
Computador 2: Executa 10 milhões de instruções por segundo.
A-2: \dfrac{2 \cdot (10^7)^2 \text{ instruções}}{10^{7} \text{ instruções/segundo}} = 20.000.000 \text{ segundos } (231 \text{ dias}).
B-2: \dfrac{50 \cdot (10^7) \cdot \log(10^7) \text{ instruções}}{10^{7} \text{ instruções/segundo}} = 1.163 \text{ segundos } (\approx 19 \text{ minutos}).
Tempo de execução para diferentes algoritmos em entradas de tamanho crescente em um computador executando um milhão de instruções por segundo. Em casos com tempo \geq 10^{25} anos substituído por very long. Para referência, a idade estimada do universo é de aproximadamente 4 \times 10^{17} segundos.
Espaço de Busca:
P: Exemplo?
Observação: Em geral, nós consideramos o algoritmo mais eficiente se seu tempo de execução no pior caso tem ordem de crescimento menor (tese de Cobham-Edmonds).
P: Uma definição para um algoritmo eficiente?
Def. Nós dizemos que um algoritmo é eficiente se tem tempo de execução polinomial.
P: E se for n^{100} vs. 1.001^{n}?
Justificativa:
Exceções:
Ideia: Suprimir fatores constantes e termos de baixa ordem.
Exemplo: 8n^3 + 50n + 86 cresce como n^3.
Terminologia: O algoritmo tem tempo de execução \mathcal{O}(n^3), onde n é o tamanho da entrada.
Problema: O vetor A contem o número t?
P: Qual é o tempo de execução?
P: Quanto espaço é usado?
Problema: O vetor A contem o número t?
P: Qual é o tempo de execução? \mathcal{O}(n)
Problema: O vetor A ou o vetor B contem o número t?
P: Qual é o tempo de execução?
Problema: O vetor A ou o vetor B contem o número t?
P: Qual é o tempo de execução? \mathcal{O}(n)
Problema: O vetor A e o vetor B contem um número em comum?
P: Qual é o tempo de execução?
Problema: O vetor A e o vetor B contem um número em comum?
P: Qual é o tempo de execução? \mathcal{O}(n^2)
Problema: O vetor A tem um elemento duplicado?
P: Qual é o tempo de execução?
Problema: O vetor A tem um elemento duplicado?
P: Qual é o tempo de execução? \mathcal{O}(n^2)
Observação. Seja T(n) uma função em n=1, 2, 3, \dots (em geral o pior tempo de execução de um algoritmo).
P: Quando T(n) é \mathcal{O}(f(n))?
R: Se eventualmente (para n suficientemente grande), T(n) é limitado por cima por f(n) multiplicada por uma constante.

Def. T(n) é \mathcal{O}(f(n)) se e somente se existe uma constante c > 0 e um n_0 \geq 0 tal que T(n) \leq c \cdot f(n) para todo n \geq n_0.
Exemplo: T(n) = 32n^2 + 17n + 1.
Uso: Ordenação por Inserção é \mathcal{O}(n^2).
Observação: c e n_0 não podem depender de n.
T(n) = 3n + 1
T(n) é \mathcal{O}(n): c = 4 e n_0 = 1, c = 3{,}5 e n_0 = 2, \dots
T(n) é \mathcal{O}(n^2), \mathcal{O}(n^3), \dots
T(n) = (n-1)(n-1)(1+5) = 6n^2 - 12n + 6
T(n) é \mathcal{O}(n^2): c = 6 e n_0 = 0, \dots
T(n) é \mathcal{O}(n^3), \mathcal{O}(n^4), \dots
T(n) não é \mathcal{O}(n).
Def. T(n) é \mathcal{O}(f(n)) se e somente se existe uma constante c > 0 e um n_0 tal que T(n) \leq c \cdot f(n) para todo n \geq n_0.
Afirmação. Se T(n) = a_k n^k + \dots + a_1 n^1 + a_0 então T(n) = \mathcal{O}(n^k).
Prova. Escolha n_0=1 e c=|a_k|+\dots+|a_1|+|a_0|.
Nós temos que mostrar que \forall n \geq n_0, T(n) \leq c \cdot n^k.
T(n) \leq |a_k|n^k+\dots+|a_1|n^1+|a_0|.
T(n) \leq |a_k|n^k+\dots+|a_1|n^k+|a_0|n^k.
T(n) \leq (|a_k| + \dots + |a_1| + |a_0|)n^k.
T(n) \leq c \cdot n^k \blacksquare
?