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(n2)? É Ω(n3)? • O algoritmo é Ө(n3)? 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]; 2*(n-1) if (A[i] < *pMin) *pMin = A[i]; } } 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) 2*(n-1) *pMin = A[i]; } } 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) = 3n3 + 2n2 é Ω(n3) basta fazer: c = 1, e então 3n3 + 2n2 ≥ n3 para n ≥ 0. Ø Seja g(n) = n2 para n par (n ≥ 0) g(n) = n para n ímpar (n ≥ 1). Neste caso g(n) poderá ser: Ω(n2), 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? • n3 é Ω(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(n2). • 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 2n2. Qual dos dois programas é melhor? • Depende do tamanho do problema. • Para n < 50, o programa com tempo 2n2 é 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, log2n 10, quando n é 1 milhão, log2n 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 log2n é 20 e o log10n é 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, nlog2n é cerca de 20 milhões. • Quando n é 2 milhões, nlog2n é cerca de 42 milhões, pouco mais do que o dobro. 20 Principais Classes de Problemas • f(n) = O(n2) • Um algoritmo de complexidade O(n2) é 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(n3) • Um algoritmo de complexidade O(n3) é 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(2n) • Um algoritmo de complexidade O(2n) é 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(2n). • 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(cn); 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 2n passos do que um que requer 10n10 passos. • 2n+1 = O(2n). • 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; 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 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; } } • Calcule a função de complexidade e a complexidade assintótica de p2 e p3. Qual é mais rápido? 31 Perguntas?