Análise de Algoritmos Parte 3 Prof. Túlio Toffolo http://www.toffolo.com.br BCC202 Aula 06 Algoritmos e Estruturas de Dados I
Como escolher o algoritmo mais adequado para uma situação? (continuação)
Exercício da última aula teórica Obtenha a função de complexidade f(n) dos algoritmos abaixo. Considere apenas as operações envolvendo as variáveis x e y. Para cada algoritmo, responda: Qual o valor da variável x ao final da execução do algoritmo? O algoritmo é O(n 2 )? É Ω(n 3 )? O algoritmo é Ө(n 3 )? void exercicio1(int n) { int i, j, x, y; x = y = 0; for (i = 1; i <= n; i++) { for (j = i; j <= n; j++) x = x + 1; for (j = 1; j < i; j++) y = y + 1; } } void exercicio2(int n) { int i, j, k, x; x = 0; for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) for (k = 1; k <= j; k++) x = x + j + k; x = i; }
Perguntas?
Comportamento Assintótico de Funções Nas aulas passadas aprendemos: Como calcular a função de complexidade f(n). Ø Melhor caso x Caso médio x Pior caso Ø Qual a influência desta função em algoritmos aplicados sobre problemas de tamanho pequeno? Ø E sobre problemas grandes? Ø Estuda-se o comportamento assintótico das funções de custo. O que isto significa? 5
Qual a ordem de complexidade? void MaxMin1(int* A, int n, int* pmax, int* pmin) { int i; } *pmax = A[0]; *pmin = A[0]; for (i = 1; i < n; i++) { if (A[i] > *pmax) *pmax = A[i]; if (A[i] < *pmin) *pmin = A[i]; } 2*(n-1) 6
Qual a ordem de complexidade? void MaxMin2(int* A, int n, int* pmax, int* pmin) { int i; } *pmax = A[0]; *pmin = A[0]; for (i = 1; i < n; i++) { if (A[i] > *pmax) *pmax = A[i]; else if (A[i] < *pmin) *pmin = A[i]; } 2*(n-1) 7
Comportamento Assintótico de Funções Nas aulas passadas também aprendemos: Comportamento assintótico das função de complexidade f(n). Ø Dominação Assintótica Ø Notação O Ø Notação Ω e Notação Ө. 8
Operações com a Notação O 9
Dominação Assintótica f(n) domina assintoticamente g(n) se: Ø Existem duas constantes positivas c e m tais que, para n m, temos g(n) c f(n). 10
Notação O O valor da constante m mostrado é o menor valor possível, mas qualquer valor maior também é válido. Definição: uma função g(n) é O(f(n)) se existem duas constantes positivas c e m tais que g(n) c f(n), para todo n m. 11
Notação Ω Especifica um limite inferior para g(n). Definição: Uma função g(n) é Ω(f(n)) se: Ø Existem duas constantes positivas c e m tais que, para n m, temos g(n) c f(n). 12
Notação Ө Exemplo gráfico de dominação assintótica que ilustra a notação Ө. Especifica um limite assintótico firme para g(n). Ø Para ser Ө(f(n)), uma função deve ser ao mesmo tempo O(f(n)) e Ω(f(n)). 13
Notação Ө Ø Para mostrar que g(n) = 3n 3 + 2n 2 é Ω(n 3 ) basta fazer: c = 1, e então 3n 3 + 2n 2 n 3 para n 0. Ø Seja g(n) = n 2 para n par (n 0) g(n) = n para n ímpar (n 1). Neste caso g(n) poderá ser: Ω(n 2 ), se considerarmos apenas entradas pares Ω(n), se considerarmos apenas entradas ímpares Se considerarmos qualquer entrada possível, então g(n) é Ω(n) 14
Perguntas... n é O(n log n)? Esta afirmação é útil? n 3 é Ω(n)? Esta afirmação é útil? n é O(n)? n é Ω(n)? n é Ө(n)? n é Ө(n log n)? 15
Comparação de Programas Um programa com tempo de execução O(n) é melhor que outro com tempo O(n 2 ). Porém, as constantes de proporcionalidade podem alterar esta consideração. Exemplo: um programa leva 100n unidades de tempo para ser executado e outro leva 2n 2. Qual dos dois programas é melhor? Depende do tamanho do problema. Para n < 50, o programa com tempo 2n 2 é melhor do que o que possui tempo 100n. 16
Principais Classes de Problemas f(n) = O(1) Algoritmos de complexidade O(1) são ditos de complexidade constante. Uso do algoritmo independe de n. As instruções do algoritmo são executadas um número fixo de vezes. 17
Principais Classes de Problemas f(n) = O(log n) Um algoritmo de complexidade O(log n) é dito ter complexidade logarítmica. Típico em algoritmos que transformam um problema em outros menores. Pode-se considerar o tempo de execução como menor que uma constante grande. Quando n é mil, log 2 n 10, quando n é 1 milhão, log 2 n 20. Para dobrar log n temos que elever n ao quadrado. A base do logaritmo muda pouco estes valores: quando n é 1 milhão, o log 2 n é 20 e o log 10 n é 6. 18
Principais Classes de Problemas f(n) = O(n) Um algoritmo de complexidade O(n) é dito ter complexidade linear Em geral, um pequeno trabalho é realizado sobre cada elemento de entrada É a melhor situação possível para um algoritmo que tem de processar/produzir n elementos de entrada/saída. Cada vez que n dobra de tamanho, o tempo de execução dobra. 19
Principais Classes de Problemas f(n) = O(n log n) Típico em algoritmos que quebram um problema em outros menores, resolvem cada um deles independentemente e juntando as soluções depois. Quando n é 1 milhão, nlog 2 n é cerca de 20 milhões. Quando n é 2 milhões, nlog 2 n é cerca de 42 milhões, pouco mais do que o dobro. 20
Principais Classes de Problemas f(n) = O(n 2 ) Um algoritmo de complexidade O(n 2 ) é dito ter complexidade quadrática. Ocorrem quando os itens de dados são processados aos pares, muitas vezes em um anel dentro de outro. Quando n é mil, o número de operações é da ordem de 1 milhão. Sempre que n dobra, o tempo de execução é multiplicado por 4. Úteis para resolver problemas de tamanhos relativamente pequenos. 21
Principais Classes de Problemas f(n) = O(n 3 ) Um algoritmo de complexidade O(n 3 ) é dito ter complexidade cúbica. Úteis apenas para resolver pequenos problemas. Quando n é 100, o número de operações é da ordem de 1 milhão. Sempre que n dobra, o tempo de execução fica multiplicado por 8. 22
Principais Classes de Problemas f(n) = O(2 n ) Um algoritmo de complexidade O(2 n ) é dito ter complexidade exponencial. Geralmente não são úteis sob o ponto de vista prático. Ocorrem na solução de problemas quando se usa força bruta para resolvê-los. Quando n é 20, o tempo de execução é cerca de 1 milhão. Quando n dobra, o tempo fica elevado ao quadrado. 23
Principais Classes de Problemas f(n) = O(n!) Um algoritmo de complexidade O(n!) é dito ter complexidade exponencial, apesar de O(n!) ter comportamento muito pior do que O(2 n ). Geralmente ocorrem quando se usa força bruta para resolver o problema. n = 20 20! = 2.432.902.008.176.640.000, um número com 19 dígitos. n = 40 um número com 48 dígitos. 24
25
26
Algoritmo Polinomial Algoritmo exponencial no tempo de execução tem função de complexidade O(c n ); c > 1. Algoritmo polinomial no tempo de execução tem função de complexidade O(p(n)), onde p(n) é um polinômio. A distinção entre estes dois tipos de algoritmos torna-se significativa quando o tamanho do problema a ser resolvido cresce. Por isso, os algoritmos polinomiais são muito mais úteis na prática do que os exponenciais. 27
Quem vê, entende... Mas é quem faz que aprende! 28
Exercício 1 Indique se as afirmativas a seguir são verdadeiras ou falsas e justifique a sua resposta É melhor um algoritmo que requer 2 n passos do que um que requer 10n 10 passos. 2 n+1 = O(2 n ). f(n) = O(u(n)) e g(n) = O(v(n)) => f(n) + g(n) = O(u(n) + v(n)) f(n) = O(u(n)) e g(n) = O(v(n)) => f(n) - g(n) = O(u(n) - v(n)) 29
Exercício 2 // Considere A, B e C vetores globais void p1 (int n) { int i, j, k; for (i=0; i<n; i++) for (j=0; j<n; j++) { C[i][j]=0; for (k=n-1; k>=0; k--) C[i][j]=C[i][j]+A[i][k]*B[k][j]; } } Calcule a função de complexidade e a complexidade assintótica de p1 30
Exercício 3 void p2 (int n) { int i, j, x, y; void p3 (int n) { int i, j, x, y; } } x = y = 0; for (i=1; i<=n; i++) { for (j=i; j<=n; j++) x = x + 1; for (j=1; j<i; j++) y = y + 1; } } x = y = 0; for (i=1; i<=n; i++) { for (j=i; j<=n; j++) x = x + 1; Calcule a função de complexidade e a complexidade assintótica de p2 e p3. Qual é mais rápido? 31
Perguntas?