Teoria da Computação Aula 9 Noções de Complexidade Prof. Esp. Pedro Luís Antonelli Anhanguera Educacional
Análise de um Algoritmo em particular Qual é o custo de usar um dado algoritmo para resolver um problema específico? Características que devem ser investigadas: -análise do número de vezes que cada parte do algoritmo deve ser executada; - estudo da quantidade de memória necessária; - estudo do tempo em várias situações.
Análise de uma classe de Algoritmos Qual é o algoritmo de menor custo possível para resolver um problema em particular? Toda uma família de algoritmos é investigada. Procura-se identificar um que seja o melhor possível. Coloca-se limites para a complexidade computacional dos algoritmos pertencentes à classe
Custo de um Algoritmo Determinando o menor custo possível para resolver problemas de uma dada classe, temos a medida da dificuldade inerente para resolver o problema. Podem existir vários algoritmos para resolver o mesmo problema, como no caso da ordenação de um conjunto. - Alguns algoritmos de ordenação: -Selection Sort; -Insertion Sort; - Buble Sort; - Shell Sort; - Merge Sort; - Quick Sort; - Heap Sort; - Radix Sort; - Counting Sort; - Bucket Sort.
Custo de um Algoritmo Atenção! Algoritmo Eficaz Algoritmo Eficiente Algoritmo Ótimo Algoritmo Eficaz: É aquele resolve o problema em qualquer de suas instâncias; Algoritmo Eficiente: É aquele resolve o problema em qualquer de suas instâncias de maneira satisfatória; Algoritmo Ótimo: É aquele resolve o problema em qualquer de suas instâncias com o menor custo possível. Se a mesma medida de custo é aplicada a diferentes algoritmos, então é possível compará-los e escolher o mais adequado.
Análise de Algoritmos Não há como comparar algoritmos, utilizando máquinas/tecnologias diferentes. Ao invés de escolher uma máquina em particular: A ideia é analisar o algoritmo utilizando um modelo matemático de computação. Modelo utilizado: Máquina de Acesso Aleatório (Random Acess Machine- RAM ) Único processador, com instruções executadas uma após a outro, sem concorrência; Primitivas/Operações Básicas: Soma, subtração, multiplicação, divisão, movimentação de dados, desvios, etc; A execução de qualquer primitiva leva uma unidade de tempo.
Função de Complexidade Para medir o custo de execução de um algoritmo é comum definir uma função de custo ou função de complexidade f(n) que mede o tempo necessário para executar um algoritmo em um problema de tamanho n. Observação: A complexidade de tempo na realidade não representa tempo diretamente, mas o número de vezes que determinada operação considerada relevante é executada. Função de complexidade de espaço f(n) mede a memória necessária para executar um algoritmo em um problema de tamanho n.
Comportamento Assintótico de Funções O parâmetro n fornece uma medida da dificuldade para se resolver o problema. Para valores suficientemente pequenos de n, qualquer algoritmo custa pouco para ser executado, mesmo os ineficientes, então a escolha do algoritmo não é um problema crítico para problemas de tamanho pequeno. Logo, a análise de algoritmos é realizada para valores grandes de n. Estuda-se o comportamento assintótico das funções de custo (comportamento de suas funções de custo para valores grandes de n). O comportamento assintótico de f(n) representa o limite do comportamento do custo quando n cresce.
Principais Classes de Funções de Custo Classe do tipo f(n) = C(1) - Constante Classe do tipo f(n) = log n - Logarítmica Classe do tipo f(n) = n - Linear Classe do tipo f(n) = n. log n - Logarítmica Classe do tipo f(n) = n 2 - Quadrática Classe do tipo f(n) = n 3 - Cúbica Classe do tipo f(n) = 2 n - Exponencial Classe do tipo f(n) = n! - Exponencial
Principais Classes de Funções de Custo O ideal seria que as operações de estruturas de dados executassem com tempos de execução proporcional as funções constante ou logarítmica. Seria desejável que os algoritmos executassem em tempo linear ou n.log(n). Algoritmos com tempos de execução quadráticos ou cúbicos são pouco práticos. Algoritmos com tempos de execução exponenciais são impraticáveis a não ser para pequenas entradas. Escala de Complexidade Melhor -------------------------------------------------------------------------------- Pior
Principais Classes de Funções de Custo
Análise da Complexidade de um Algoritmo Vamos tentar determinar o tempo de execução ( ou a complexidade de tempo do algoritmo) de alguns algoritmos simples em função do tamanho da entrada (n). Para os problemas analisados vamos usar como tamanho de entrada a dimensão do vetor e ignorar os valores dos seus elementos ( modelo RAM). A complexidade de tempo de um algoritmo é o número de instruções básicas ( operações elementares ou primitivas) que executa a partir de uma entrada.
Análise da Complexidade local de um Algoritmo A análise é realizada para cada linha do algoritmo contabilizando o número de operações primitivas. Essa análise nos fornece uma função matemática expressando o total de operações com base no tamanho do problema (n) e o custo (constante) de cada operação.
Análise da Complexidade de um Algoritmo Algoritmo para somar os n elementos de um vetor Sentenças Passos Frequência Total de Passos Algoritmo Soma (a[], n) 0-0 { 0-0 int i, s =0; 1 1 1 for i=1 to n do 1 n +1 n +1 s=s+a[i]; 1 n n return s; 1 1 1 } 0-0 Total 4 2n + 3 Analisando o código acima, vemos que uma clássica repetição linear. A complexidade de tempo do algoritmo para um vetor de tamanho n é de O( n );
Análise da Complexidade de um Algoritmo Algoritmo para somar de duas matrizes Sentenças Passos Frequência Total de Passos Algoritmo Soma (a[ ][ ], b[ ] [ ], c[ ] [ ], m, n) 0-0 { 0-0 int i,j; 1-1 for i=1 to m do 1 m=1 m +1 for j=1 to n do 1 m ( n +1) mn +m c[i][j] = a[i][j] + b[i][j]; 1 m n mn } 0-0 Total 4 2mn + 2m +2 Na expressão mn se substituirmos m pelo n temos nn = n 2. Portanto a complexidade de tempo do algoritmo é de O( n 2 ).
Análise da Complexidade de um Algoritmo A constante c k representa o custo ( tempo) de cada execução da linha k. Vamos denotar por t j o número de vezes que o teste no laço enquanto na linha 5 é feito para aquele valor de j.
Análise da Complexidade de um Algoritmo A constante c k representa o custo ( tempo) de cada execução da linha k. Vamos denotar por t j o número de vezes que o teste no laço enquanto na linha 5 é feito para aquele valor de j.
Análise da Complexidade de um Algoritmo Logo, o tempo total de execução T(n) do algoritmo é a soma dos tempos de execução de cada uma das suas linhas, ou seja: Como se vê, entradas de tamanho igual podem apresentar tempos de execução diferentes já que o valor de T(n) depende dos valores dos t j.
Análise da Complexidade de um Algoritmo Diferentes Análises: Pior Caso: Tempo máximo de execução para qualquer entrada Melhor Caso: Resultado do menor tempo possível Caso Médio: Tempo médio para qualquer entrada possível. - Pode ser necessário conhecer distribuição estatística dos dados de entrada ( aleatória, Gaussiana, crescente, agrupada,...)
Análise da Complexidade no melhor caso O melhor caso para o algoritmo de ordenação por inserção ocorre quando vetor A já está ordenado. Para j=2,...n, temos A[i] <= chave na linha 5 quando i=j-1. Assim t j =1 para j=2,...,n. Logo: Este tempo de execução é da forma a a + b para constantes a e b que dependem apenas dos c j. Portanto, no melhor caso, o tempo de execução é uma função linear no tamanho da entrada
Análise da Complexidade no pior caso Quando o vetor A está em ordem decrescente, ocorre o pior caso para o algoritmo de ordenação por inserção. Para inserir a chave em A[1...j-1], temos que comparála com todos os elementos neste subvetor, sendo assim t j =j para j=2,...,n. Lembre-se que: Temos então que:
Análise Assintótica Na maioria das vezes o estudo da complexidade de algoritmos é concentrado na análise do pior caso,e no comportamento assintótico dos algoritmos. O algoritmo de ordenação por inserção temo como complexidade ( de pior caso ) uma função quadrática an 2 + bn +c, onde a, b, c são constantes absolutas que dependem apenas dos custos c j. O estudo assintótico nos permite desprezar os valores destas constantes.
Limite assintótico superior O ( big O ) ( Ozão )
Notação Assintótica Baseada em conjuntos e aplicada a funções: Abuso da Notação (comum e aceitável) f(n) = O( g(n) ) Indica que uma função f(n) é um membro do conjunto de O( g(n) )
Notação O - Propriedades
Limite assintótico superior o ( Ózinho )
Limite assintótico inferior Ω ( big Ômega )
Limite assintótico superior ω ( Ômegazinho )
Limite assintótico restrito ϴ ( Theta )
Notação assintótica: resumo
Notação assintótica: analogia Comparação assintótica de duas funções f e g e a comparação de dois números reais a e b:
Propriedades das classes:
Propriedades das classes:
Bibliografia da apresentação: ZIVIANI, N. Projeto de Algoritmos com implementações em Pascal e C. 2ª edição. São Paulo: Pioneira Thomson Learning, 2005. 552 p. Disponível em http://www2.dcc.ufmg.br/livros/algoritmos/ Goodrich, Michael e Tamassia, Roberto - Estrura de dados e algorítmos em Java 4 ed. Porto Alegre - Bookrnan. 2007. http://www.inf.ufrgs.br/~prestes/courses/complexity/aula1.pdf