Universidade Federal de Juiz de Fora UFJF. Departamento de Ciência da Computação DCC. Curso de Ciência da Computação. Análise e Projeto de Algoritmos 4º período. Filippe Jabour. Atualizado em 20/10/2003. ÍNDICE 1. Introdução... 2 1.1 Problemas computacionais e algoritmos... 2 1.2 O conceito de complexidade e suas cotas... 2 1.3 Modelos de computação... 3 1.4 Distinções entre funções de complexidade... 3 Tabela 1.1 Crescimento de funções típicas de complexidade... 3 Tabela 1.2 Instâncias solucionáveis X rapidez do computador... 3 1.5 Constante Multiplicativa... 4 Tabela 1.3 Constantes multiplicativas... 4 1.6 Tamanho de instâncias de problemas... 4 1.7 Ordens de grandeza na complexidade de algoritmos... 5 Figura 1.1 - f(n) = O(g(n))... 5 Figura 1.2 - f(n) = Ω(g(n))... 5 Figura 1.3 - f(n) = Θ(g(n))... 6 2. Bibliografia:... 6 1
1. Introdução 1.1 Problemas computacionais e algoritmos Problema: É uma pergunta de caráter geral a ser respondida. É descrito através de parâmetros de entrada e um enunciado das soluções que deve prover. Instância do problema: Obtém-se ao se fixar valores particulares para todos os parâmetros do problema. Exemplo: Enunciado: Ordenar uma lista de n números inteiros em ordem crescente. Parâmetros: Lista L = (M 1, M 2,..., M n ) de números inteiros. Solução: Uma lista contendo os mesmos números da lista dada como parâmetro, em ordem não decrescente. Instância: Por exemplo L = (3,-5,4,2-1). Algoritmo: Descrição passo a passo de como um problema é solucionável. Descrição finita. Passos bem definidos, sem ambigüidades e executáveis computacionalmente em um tempo finito. O algoritmo resolve o problema P se para qualquer instância I dos parâmetros de entrada é produzida uma solução correta (dados de saída). 1.2 O conceito de complexidade e suas cotas Complexidade de tempo, ou rapidez do algoritmo, relaciona o tamanho de uma instância ao tempo necessário para resolvê-la. A rapidez pessimista é aquela em que os dados de entrada são tais que o algoritmo terá um máximo de trabalho a ser feito. Tamanho da instância é a medida da quantidade de dados de entrada. Para o exemplo do item 1.1, o tamanho da instância corresponde ao número n de números a serem ordenados. Neste mesmo exemplo, o caso pessimista é o da lista de entrada em ordem decrescente. Cota Superior de Complexidade - CS(n): É a menor das complexidades dos diversos algoritmos conhecidos para resolver um problema, sendo n o tamanho da instância. Considere aqui rapidez como o tempo gasto para resolver o algoritmo. Cota Inferior de Complexidade - CI(n): É o limite inferior para as complexidades em tempo de todos os possíveis algoritmos (conhecidos ou não) que resolvem o problema. Aqui estamos interessados na complexidade inerente ou intrínseca do problema. A cota inferior nos diz que nenhum algoritmo pode resolver o problema com rapidez pessimista menor do que CI(n), para entradas arbitrárias do tamanho n. Em outras palavras, nenhum algoritmo pode resolver em menos tempo. A cota é uma propriedade do problema e não de um algoritmo em particular. 2
Em resumo, CI(n) é o mínimo sobre todos os algoritmos possíveis e CS(n) é o mínimo sobre todos os algoritmos conhecidos (ou existentes). CI(n) e CS(n) correspondem à rapidez mínima (tempo de execução mínimo) para o caso pessimista de uma instância de entrada de tamanho n. Aprimorar CS(n) significa descobrir um algoritmo que tenha rapidez máxima menor do que a dos outros. Aprimorar CI(n) significa encontrar um limite mínimo inferior ao anterior, para todos os algoritmos possíveis. Ou seja, aplicar técnicas que permitam aumentar a precisão com a qual o mínimo anterior foi obtido. Por fim, o objetivo é fazer com que as duas cotas coincidam: CS(n) = CI(n). Neste caso teremos o algoritmo ótimo. 1.3 Modelos de computação Ao invés de calcular exatamente os tempos de execução em máquinas específicas, a maioria das análises conta apenas o número de operações elementares. Consideraremos que cada operação elementar (a comparação entre dois valores por exemplo) gasta, para executar, uma unidade de tempo (critério do custo uniforme). A medida de complexidade é o crescimento assintótico dessa contagem de operações. 1.4 Distinções entre funções de complexidade Tabela 1.1 Crescimento de funções típicas de complexidade Função de Valor de n complexidade 20 40 60 N 0,000002 s 0,000004 s 0,000006 s n log n 0,00000864 s 0,00002129 s 0,00003544 s n 2 0,00004 s 0,00016 s 0,00036 s n 3 0,0008 s 0,0064 s 0,0216 s 2 n 0,10486 s 109951 s 1,2 x 10 11 3 n 348,68 s 1,0 x 10 12 4,0 x 10 21 Obs.: Tempo de execução quando uma operação elementar no algoritmo é executável em um décimo de microssegundo (10-7 s). Tabela 1.2 Instâncias solucionáveis X rapidez do computador Função de Tamanho da maior instância solucionável em uma hora complexidade Computador padrão Computador 100 vezes mais rápido Computador 1000 vezes mais rápido n x 100 x 1000 x n log 2 n y 22,5 y 140,2 y n 2 z 10 z 31,6 z n 3 k 4,6 k 10 k 2 n m m + 6 m + 10 3
3 n n n + 4 n + 6 Exemplo: Seja o problema de ordenar uma lista M de n (n 2) números inteiros em ordem crescente. Uma solução é que segue: para j := n-1 até 1 faça para i := 1 até j faça se M i > M i+1 então troque M i e M i+1 fim_se fim_para fim_para Análise: Operação elementar: A comparação do se da terceira linha. Cálculo do tempo de execução: A operação elementar será executada um número de vezes dado por (n-1) + (n-2) +... + 1 = ½ (n 2 n) Adotando a hipótese do custo uniforme (uma unidade de tempo para a execução de cada operação elementar) a ser vista mais adiante, temos que a complexidade do algoritmo é dada por f(n) = ½ (n 2 n) 1.5 Constante Multiplicativa Um algoritmo de complexidade rapidamente crescente com constante multiplicativa relativamente pequena pode ter melhor desempenho do que outro de complexidade lentamente crescente com uma maior constante multiplicativa, isto para pequenas instâncias. Tabela 1.3 Constantes multiplicativas Função de complexidade Valores de n p/ os quais o algoritmo é o mais rápido (tem o melhor desempenho) 20000 n 2000 n log n 200 n 2 10 n 3 10 (2 n ) 3 n n > 1024 59 n 1024 21 n 58 10 n 20 6 n 9 1 n 9 1.6 Tamanho de instâncias de problemas Suponha a entrada de um algoritmo como sendo um único número inteiro k e que ele requer um tempo c k para executar. Seja n(k) o comprimento da codificação do valor inteiro k. Se k for codificado na notação unária, então n(k) = k e o tempo de execução é c k. Critério de custos está associado à forma como o tamanho da entrada é calculado (argumento da função). Existem dois critérios. Definimos então o critério de custo uniforme: tamanho da entrada é medido em número de palavras. O custo de cada instrução é unitário. O custo de armazenamento de cada inteiro é unitário. Se k for codificado na notação binária, então n(k) = log 2 k e o tempo de execução é c 2 k. 4
Definimos então o critério de custo logarítmico: tamanho da entrada é medido em número de bits. O custo de cada instrução é proporcional ao número de bits necessários ao armazenamento do operando. O custo de armazenamento de cada inteiro é igual ao número de bits necessários a sua representação em ponto fixo. 1.7 Ordens de grandeza na complexidade de algoritmos Na determinação da complexidade de um algoritmo, ou seja, da função que relaciona o tamanho da instância do problema ao tempo que o algoritmo gasta para resolvê-lo, será adotada a noção de ordem de grandeza. Na matemática existem notações apropriadas para expressar a ordem de grandeza de uma função. Definição: f(n) = O(g(n)) (Lê-se: f de n é de ordem máxima g de n), se e somente se, existem duas constantes positivas c e n 0 tal que f(n) c g(n), para todo n n 0. Figura 1.1 - f(n) = O(g(n)) f, g c. g(n) f(n) n n 0 Definição: f(n) = Ω(g(n)) (Lê-se: f de n é de ordem mínima g de n), se e somente se, existem duas constantes positivas c e n 0 tal que f(n) c g(n), para todo n n 0. Figura 1.2 - f(n) = Ω(g(n)) f, g f(n) c. g(n) n 5 n 0
Definição: f(n) = θ(g(n)) (Lê-se: f de n é de ordem exata g de n), se e somente se, existem constantes positivas c 1, c 2 e n 0 tal que c 1 g(n) f(n) c 2 g(n), para todo n n 0. Figura 1.3 - f(n) = Θ(g(n)) f, g c 2. g(n) f(n) c 1. g(n) n n 0 Novamente nos reportando ao exemplo do item 1.1 temos: f(n) = ½ (n 2 n) ½ n 2, para n > 1 então, f(n) = O(n 2 ), ou seja, a complexidade do algoritmo é de ordem máxima n 2. 2. Bibliografia: [FORTUNA 2001] FORTUNA, Michel Heluey. UFJF. Notas de Aula. 2001. [RAUL 1998] RAUL. UFJF. Notas de Aula. 1998. [SZWARCFILTER E MARKEZON 1994] SZWARCFILTER, Jayme Luiz e MARKEZON, Lílian. Estrutura de dados e seus algoritmos. LTC Livros Técnicos e Científicos S.A., Rio de Janeiro, 1994. [TERADA 1991] TERADA, Routo. Desenvolvimento de algoritmos e estrutura de dados. Makron Books, 1991. 6