7. Introdução à Complexidade de Algoritmos Fernando Silva DCC-FCUP Estruturas de Dados Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 1/1 Análise de Algoritmos No desenvolvimento de algoritmos é importante ter a noção da eficiência de um algoritmo, i.e. da eficiência que pode ter uma implementação do algoritmo. Eficiência normalmente mede-se em tempo de execução ou espaço (memória) necessário à execução do algoritmo (ou programa associado). Tempo de Execução de um algoritmo varia com o input e normalmente aumenta com o tamanho do input. I I caso médio é difícil de determinar. normalmente, olha-se para o pior caso possível de tempo de execução: F F mais simples de analizar crucial nas aplicações mais exigentes, jogos, etc. Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 2/1 Como aferir a eficiência Como aferir a eficiência, por exemplo, o tempo de execução? Análise empírica – executando o programa com inputs de tamanho e composição variados, mas I I I nem sempre é simples concretizar o algoritmo, ou os resultados podem não ser conclusivos, comparação de algoritmos obriga a usar igual software e hardware. Análise teórica I I I I usa uma descrição de mais alto nível do algoritmo em vez da implementação, caracteriza o tempo de execução como uma função do tamanho do input, n, tem em conta todos os possíveis inputs, permite avaliar eficiência de forma independente do ambiente de hardware/software. Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 3/1 Exemplo de análise de complexidade - findMax() Como calcular o tempo de execução do algoritmo seguinte: Pressupostos (RAM -Random Access Memory): memória ilimitada e endereços contêm um no arbitrário ou caracteres, aceder ao conteúdo de um endereço custa uma unidade de tempo. max= A[0]; → 1 leitura de A[0] + 1 atribuição a max. Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 4/1 Determinar complexidade do findMax() Casos possíveis: caso mais favorável (A[0] é o maior elemento): t(n) = 2 + 1 + n + 4(n − 1) + 1 = 5n operações primitivas pior caso: t(n) = 2 + 1 + n + 6(n − 1) + 1 = 7n − 2 caso médio? difícil de calcular, depende da distribuição do input; usar teoria de probabilidades. Normalmente, olharemos para o pior caso, pois dá-nos um limite superior do tempo de execução. Cálculo do tempo de execução: seja a o tempo observado para a operação primitiva mais rápida seja b o tempo observado para a operação primitiva mais lenta então, o pior tempo de execução T (n) será a(7n − 2) ≤ T (n) ≤ b(7n − 2) ⇒ T (n) limitado por 2 funções lineares Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 5/1 Taxa de crescimento do tempo de execução Saliente-se que: o tempo de execução, T (n), pode ser afectado pela alteração do ambiente hardware/software, mas tal não acontece se considerarmos a taxa de crescimento de T (n) A taxa de crescimento de T (n) corresponde ao crescimento de T (n) quando se aumenta o valor de n. O exemplo findMax() mostrava T (n) limitado por 2 funções lineares em n, significando que o tempo de execução varia na mesma proporção que n. Logo, diz-se que o crescimento de T (n) é linear. Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 6/1 Taxas de crescimento (1) logarítmica linear quadrática cúbica polinomial exponencial ≈ log2 n ≈n ≈ n2 ≈ n3 ≈ nk ≈ an (a > 1) Crescimento de n log2 n 2 1 8 3 16 4 ... ... 1024 10 algumas funções: √ n n n log2 n 1.4 2 2 2.8 8 24 4.0 16 64 ... ... ... 32 1024 10240 Fernando Silva (DCC-FCUP) n2 4 64 256 ... > 106 n3 8 512 4096 ... > 109 7. Introdução à Complexidade de Algoritmos 2n 4 256 65536 ... > 10308 Estruturas de Dados 7/1 Taxas de crescimento (2) Se assumirmos que cada operação pode ser executada em 1µs (micro-sec), qual será o maior problema (função de n) para um programa que execute em 1 seg., 1 min., 1 hora? T (n) 400n 20ndlog ne 2n2 n4 2n Tamanho máximo problema (n) 1 seg. 1 min. 1 hora 2500 150,000 9,000,000 4096 166,666 7,826,087 707 5,477 42,426 31 88 244 19 25 31 No caso de crescimento exponencial, apenas conseguimos tratar problemas para dimensões muito pequenas de n. Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 8/1 A definição “big-Oh” O() Definição de ordem de complexidade assimptótica: Sejam f (n) e g(n) funções de N0 7−→ R. f (n) é O(g(n)) (ou seja, f (n) é da ordem de g(n)) se ∃c > 0, ∃n0 ≥ 1 : f (n) ≤ cg(n), ∀n ≥ n0 Interpretação gráfica: O(g(n)) corresponde ao limite superior da taxa de crescimento de f (n). dizer que f (n) é O(g(n)), significa dizer que f (n) não cresce mais do que g(n). Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 9/1 Exemplos - O() 2n + 10 é O(n) porque: 2n + 10 ≤ cn ⇒ (c − 2)n ≥ 10 ⇒ n≥ para c = 3 e n0 = 10 verifica-se sempre que 2n + 10 ≤ cn 10 (c − 2) outras funções: 20n3 + 10n log n + 5 ak nk + ak−1 nk−1 + . . . + a0 3 log n + log log n 2100 5 n Fernando Silva (DCC-FCUP) — — — — — 7. Introdução à Complexidade de Algoritmos O(n3 ) O(nk ) O(log n) O(1) O( n1 ) Estruturas de Dados 10 / 1 Regras de simplificação Algumas regras que olhando para expressões mais complexas, permitem-nos fazer simplificações: Se d(n) ∼ O(f (n)), então ad(n) (com a > 0) ∼ O(f (n)) Se d(n) ∼ O(f (n)) e e(n) ∼ O(g(n)), então d(n) + e(n) ∼ O(f (n) + g(n)) Se d(n) ∼ O(f (n)) e e(n) ∼ O(g(n)), então d(n)e(n) ∼ O(f (n)g(n)) Se d(n) ∼ O(f (n)) e f (n) ∼ O(g(n)), então d(n) ∼ O(g(n)) Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 11 / 1 Complexidade do método da bolha (bubble-sort) Façamos a análise de complexidade do método da bolha: Efectivamente, o número de iterações total é a soma das iterações que o ciclo j faz para cada valor de i, i.e. Pn−1 i=1 (n − i) = (n − 1) + (n − 2) + . . . + 2 + 1 = = n(n−1) 2 Fernando Silva (DCC-FCUP) = n2 2 − n 2 ⇒ O(n2 ) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 12 / 1 Complexidade de funções recursivas Considere-se a função factorial fact() e seja T (n) o tempo de execução para fact(n), então: c + T (n − 1), ∴ T (n) = d, se n > 1 se n ≤ 1 assumindo n ≥ 2: T (n − 1) = c + T (n − 2) ⇒ T (n) = 2c + T (n − 2) em geral: T (n) = ic + T (n − i), n > i se i = n − 1, então T (n) = c(n − 1) + T (1) = c(n − 1) + d donde podemos concluir T (n) ≈ O(n), i.e. fact() tem complexidade n. Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 13 / 1 Complexidade da pesquisa binária (1) Considere-se o método pesqbin() (pesquisa binária não recursiva): Inicialmente o intervalo de valores é n (e = 0 e d = n − 1). Em cada passo do ciclo, o intervalo reduz-se a metade. Portanto, a questão que se coloca é: I I dado um inteiro n, quantas divisões inteiras por 2 são necessárias para que chegue a 1? i.e. qual dos valores seguintes será o primeiro a ser < 1? n/2, n/4, n/8, . . . , n/2k , . . . k < 1,à Complexidade 7. Introdução Temos de resolver n/2 o que dádekAlgoritmos > log2 n Fernando Silva (DCC-FCUP) Estruturas de Dados 14 / 1 Complexidade da pesquisa binária (2) É necessário resolver a equação: n/2k < 1 se aplicarmos logaritmos, temos k > log2 n Podemos então dizer que: I com k = dlog2 ne sabemos que ao fim de um máximo de k iterações, encontramos o valor ou podemos concluir que o valor que procuramos não existe. Em resumo, dizemos que a função pesqbin() tem complexidade logarítmica, O(log2 n), pois o número de iterações necessárias não excede log2 n, sendo n a dimensão dos dados. Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 15 / 1 Complexidade de árvores binárias de pesquisa (1) Ver primeiro capítulo sobre árvores! Se inserirmos n valores aleatórios numa árvore binária de pesquisa, sabemos que I I o comprimento médio do caminho da raíz até uma folha é O(log2 n) – correspondente à profundidade da árvore. verificar se x pertence à árvore é assim O(log2 n) Se a árvore for completa, então nenhum caminho da raíz até um dado nó pode ter mais do que 1 + log2 n nós. I métodos insertBTNode(), contains() e removeBTNode() são O(log2 n). É comum que ao inserirmos n valores aleatórios, a árvore não seja completa, pode até ficar uma sequência ordenada. Neste caso cada inserção é O(n). Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 16 / 1 Complexidade de árvores binárias de pesquisa (2) A questão a saber é I I se uma árvore binária em média estará próxima de uma árvore completa ou de uma sequência? isto decide se o tempo médio é O(log2 n) ou O(n). A demonstração deste resultado é complexa, envolvendo probabilidades, basta-nos aqui saber que é possível mostrar por indução que a complexidade será O(log2 n). Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 17 / 1