UNDB ESTRUTURAS DE DADOS Prof. Alessandro Gonçalves [email protected] Complexidade de Algoritmos Conceituação Complexidade no pior caso, melhor caso e caso médio Exemplos Complexidade de Algoritmos Avaliação de tempo na história: 1) Algoritmos primitivos 2) A avaliação do tempo na matemática. – Ex: tábuas de Log 3) O aparecimento do computador – O tempo como fator primordial Complexidade de Algoritmos Avaliação de espaço na história: 1) No início era fundamental – Ex: quanto de memória tenho ? – Eniac: 200 bits 2) Hoje já não é tão importante – Era da fartura Complexidade de Algoritmos Dois métodos para avaliar o tempo: Empírico e Analítico Empírico 1) Medição do tempo de execução do algoritmo 2) Dependentes do hardware (memória, computador...) e software (compilador, linguagem...) 3) Permitem tratamento estatístico 4) Mais simples de ser implementado Complexidade de Algoritmos Dois métodos para avaliar o tempo: Empírico e Analítico Analítico 1) Expressões matemáticas para determinar o tempo 2) Independentes do hardware (memória, computador...) e software (compilador, linguagem...) 3) Permitem tratamento estatístico 4) Mais complexo de ser implantado Complexidade de Algoritmos Implantação do método Analítico 1) Considera-se grande quantidade de dados 2) Valores assintóticos 3) Desconsiderar constantes aditivas e multiplicativas – Ex: 2*n² + 10x + 5 é equivalente a n² + x. – As duas expressões acima são da ordem n² 4) Baseado em uma variável independente, na entrada do algoritmo(caixa preta) 5) Mais complexo de ser implantado Complexidade de Algoritmos Definindo uma expressão matemática 1) Divide-se o algoritmo em passos 2) Cada passo é composto de número fixo de operações básicas 3) A operação básica de maior frequência é conhecida como “operação dominante”. 4) O número de passos do algoritmo é a frequência da operação dominante. 5) Em algoritmos de ordenação, geralmente a operação dominante é a comparação. Complexidade de Algoritmos Exemplo em C – inverte.c Resumo: void inverte() { int x, temp; for (x = 0; x<5; x++) { temp = vet[x]; vet[x] = vet[9-x]; vet[9-x] = temp; } } Cada passo corresponde a troca entre dois elementos Variável independente: número de elementos (n) Número de passos: número de execuções do bloco Complexidade de Algoritmos Soma de matrizes (para a turma resolver) 2 3 5 0 0 2 2 3 7 0 1 2 3 -1 4 3 0 6 3 0 1 -4 1 0 -1 1 1 Algoritmo: Para i = 1 até n faça Para j = 1 até n faça Cij = Aij + Bij Fim Para J Fim Para i Variável independente ? Número de linhas da matriz Número de passos ? N² Complexidade de Algoritmos Multiplicação de matrizes (para a turma resolver) 2 5 9 2 7 69 47 3 6 8 4 3 70 55 5 2 Algoritmo: Para i = 1 até n faça Para j = 1 até n faça Dij = 0 Para k = 1 até n faça Dij = Dij + Aik * Bkj Variável independente ? ... Número de colunas da matriz A Número de passos ? N³ Complexidade de Algoritmos Definindo uma expressão matemática Nos exemplos anteriores, o número de passos dependia sempre do tamanho da entrada, não do seu valor. Mas isso nem sempre ocorre. Em geral, o número de passos depende do valor da entrada. Considere: Duas matrizes A e B e uma variável X. Se X = 0, faça C = A+B Se X <>0, faça D = A.B Número de passos ? N² ou N³ Complexidade de Algoritmos Definindo uma expressão matemática para Complexidade de Tempo Ideal seria sempre conhecer o número de passos, conforme a entrada Difícil de ser atingido na prática Alternativa: determinar o número de passos para entradas específicas e representativas Complexidade de Algoritmos Determinando entradas representativas A = Algoritmo E = {E1, E2..., En} T1 = número de passos de A, quando a entrada for Ei Complexidade de Algoritmos Complexidade do Pior Caso Número de passos da entrada mais desfavorável A mais utilizada Quase sempre é relevante(Ex: aplicações de segurança) Quando falo que um algoritmo tem uma Complexidade “X”, estamos falando da Complexidade do Pior Caso Complexidade do Melhor Caso Número de passos da entrada mais favorável Pouco utilizada Pouco relevante Aplicações específicas Complexidade do Caso Médio Número de passos da entrada média Relevante Tratamento matemático e geralmente, complexo pois depende da probabilidade Complexidade de Algoritmos q = 0.5 ou 50% Complexidade de Algoritmos Exercícios (20 minutos) 1) Calcule a complexidade do algoritmo para calcular n! 2) Sejam duas matrizes: (aij) e (bkj) tais que: 1 < i < n; 1 <= k<= n; 1<= j <= m, escrever os algoritmos para fazer C=A+ B D=A*B Determinar as complexidades Complexidade de Algoritmos Solução dos exercícios Soma := 0 Para i = n até 1 passo -2 faça Soma := Soma + n*(n-1); Fim Para Complexidade: n Complexidade de Algoritmos Solução dos exercícios Algoritmo: soma de matrizes Para i := 1 até n faça Para j := 1 até p faça Cij := aij + bij Complexidade = n.p Complexidade de Algoritmos Solução dos exercícios Algoritmo: multiplicação de matrizes Para i := 1 até n faça Para j := 1 até p faça Dij = 0 Para k := 1 até p faça Dij := Dij + aik * bkj Complexidade = n.p.k (linhas da matriz A) (colunas da matriz B) Complexidade de Algoritmos Análise de algoritmo Demo (A,n) { Entrada: um array A[] com n elementos inteiros Saída: o maior elemento em A[]; Int c; C = A[0]; For (i = 1; i < n; i ++) { If (c < A[i]) c = A[i]; } Return c; } Quantas operações primitivas são necessárias para a execução ? Complexidade de Algoritmos Análise de algoritmo 1) inicializar a variável array como A[0]. Duas operações primitivas 2) o contador i é inicializado com o valor 1 no início do laço for. Corresponde a uma operação primitiva 3) a condição i < n é verificada antes de entrar no laço for. Uma operação primitiva de comparar dois números; 4) o contador i é inicializado em 0 e incrementado em 1 no fim de cada iteração do laço, a comparação i < n é feita n vezes, assim a condição contribui com n unidades para a contagem Complexidade de Algoritmos Análise de algoritmo 5) o laço for é executado n-1 vezes. A cada iteração, A[i] é comparado com c (duas operações primitivas indexação e atribuição). O contador i é incrementado. Duas operações primitivas, soma e atribuição. Assim, a cada iteração do laço quatro ou seis operações são realizadas, dependendo se A[i] < c ou A[i] > c. Desta forma, o corpo do laço contribui para a contagem variando de 4(n-1) ou 6(n-1) 6) retorna o valor da variável que corresponde a uma operação primitiva e é executada apenas uma vez. Resumo Melhor caso: 2 + 1 + n + 4(n-1) + 1 = 5n Pior caso: 2 + 1 + n + 6(n-1) + 1 = 7n - 2 Complexidade de Algoritmos Notação O (Omicron) Refere-se ao pior caso Melhor caso: 2 + 1 + n + 4(n-1) + 1 = 5n Pior caso: 2 + 1 + n + 6(n-1) + 1 = 7n – 2 => n => n O(n) = f(x) = 7n -2 Para calcularmos O, consideramos sempre o valor mais relevante, em termos de “n” ou outras variáveis envolvidas Complexidade de Algoritmos Exercícios 1) n³ -1 2) n² + 2.log n n n 3) 3.n + 5.2 n n-1 4) (n-1) + n n n² 5) 5.3 + 4.2 6) 6.347.562 7 n 7) 5.n + 3.2 + n! 8) 3n + 7m + 2 9) 5n² + 9m + 4 10) 3n + 5m + n.m Complexidade de Algoritmos Respostas 1) O(n³) 2) O(n²) n 3) O(n) n 4) O(n) n² 5) O(2) 6) O(1) 7) O(n!) 8) O(n + m) 9) O(n² + m) 10) O(n.m) Complexidade de Algoritmos Escalas N log2n n n.log2n n² n³ 2n n! 1 0 1 0 1 1 2 1 10 3.32 10 33 100 1000 1024 3x10 6 100 6.64 100 664 10.000 1.000.000 1,28 x 1030 3x10157 9.97 1000 n! 2n 9970 n² 1.000.000 109 1,072 x 1031 3x102567 1000 O(n) n.log2n n log2n n Complexidade de Algoritmos Propriedades da notação O O(g + h) = O(g) + O(h) O(g . h) = O(g) . O(h) O(k . g) = k . O(g) = O(g) Complexidade de Algoritmos Video Torre de Hanoi Programa Torre de Hanoi em C Para solucionar um Hanói de 64 discos, são necessários 18.446.744.073.709.551.615 movimentos Complexidade de Algoritmos Propriedades da notação O Cota superior – menor complexidade de um algoritmo conhecido Nos exemplos anteriores, a complexidade de multiplicação de matrizes era O(n³) O(n³) O algoritmo de Strassen diminuiu a complexidade para O(n2,807) O(n2,807) O(n³) O algoritmo de Coppersmith e Winograd melhoraram para O (n2,376) O (n2,376) O(n2,807) O(n³) Complexidade de Algoritmos Notação Ɵ (Teta) Refere-se ao caso médio Este é o caso que é o mais difícil de ser determinado, pois, necessita de análise estatística e em conseqüência de muitos testes, contudo é muito utilizado, pois é o que representa mais corretamente a complexidade do algoritmo. Exemplo: Procurar uma palavra em um dicionário. Pode-se iniciar a busca de uma palavra na metade do dicionário. Imediatamente se sabe se foi encontrada a palavra ou, no caso contrário, em qual das duas metades deve se repetir o processo (é um processo recursivo) até se chegar ao resultado. Em cada busca (ou sub-busca), o problema (as páginas em que a palavra pode estar) vão se reduzindo à metade, o que corresponde com a função logarítmica. Este procedimento de busca (conhecido como busca binária) em uma estrutura ordenada têm complexidade logarítmica. Ɵ(log2n) Complexidade de Algoritmos Notação Ω (ômega) Refere-se ao melhor caso Pouco utilizado porque sempre precisamos saber como o algoritmo se comporta em situações difíceis (pior caso). Exemplo: Exemplo 1: Em uma lista telefônica queremos encontrar um número, assume-se que a complexidade do caso melhor é Ω (1), pois está pressupondo-se o número desejado está na primeira posição. Exemplo 2: Extrair qualquer elemento de um vetor. A indexação em um vetor ou array, leva o mesmo tempo seja qual for o índice que se queira buscar. Portanto é uma operação de complexidade constante Ω (1). Complexidade de Algoritmos Conclusão Nas complexidades algorítmicas temos: Ω( ) <= θ( ) <= O() Algoritmos ótimos podem ser obtidos de duas formas: 1) melhoramento interno do código OU 2) comparação dos algoritmos entre si, escolhendo o de menor complexidade. exemplo Complexidade de Algoritmos UNDB ESTRUTURAS DE DADOS Prof. Alessandro Gonçalves [email protected]