540 likes | 608 Views
Organização e Recuperação da Informação (ORI). Complexidade de Algoritmos. Existem várias formas de se resolver um problema e, portanto, várias for- mas de se construir algoritmos para resolvê-los. Comparações entre soluções Métodos para comparações Estimativas de tempo.
E N D
Organização e Recuperação da Informação (ORI) Complexidade de Algoritmos
Existem várias formas de se resolver • um problema e, portanto, várias for- • mas de se construir algoritmos para • resolvê-los. • Comparações entre soluções • Métodos para comparações • Estimativas de tempo...
O que é complexidade? A complexidade de um algoritmo consiste na quantidade de "trabalho" necessária para a sua execução, expressa em função das operações fundamentais, as quais variam de acordo com o algoritmo, e em função do volume de dados.
O que é complexidade? • Ou seja, normalmente, programas demoram mais para executar de acordo com sua estruturação de instruções e na medida em que se aumenta a quantidade de dados de entrada. • Esta “demora” pode ser linearmente proporcional, quadrática... • Em alguns casos, o tempo de execução não depende somente da quantidade de dados, mas sim de sua forma.
Para que? Para permitir a comparação teórica de soluções diferentes para o mesmo problema e identificar as melhores soluções (eficiência).
Exemplo Suposição: uma operação leva 1 ms para ser efetuada. A tabela a seguir apresenta o tempo necessário p/ resolver um mesmo problema de cinco maneiras diferentes. Podemos pensar no “conceito” T(n), que é apresentado na tabela, como sendo o número de comandos executados por um programa (Pascal, C, ...).
Tabela T(n) n
Conceitos • Benchmark (prática) • Quando se compara 2 ou mais programas projetados para resolver a mesma tarefa, normalmente um conjunto padrão de dados é reservado para avaliar o desempenho da solução. Isso significa que este conjunto deve ser representativo do universo de dados aos quais o programa estará exposto e pode servir como referência de teste para outras soluções. • Análise (teórica) • Exemplos : reconhecimento de face, fala...
Consideração sobre Benchmark Supondo que tenhamos escrito, depurado e testado um programa e que tenhamos selecionado um particular conjunto de dados de entrada para executa-lo. Ainda assim, dependemos da configuração do computador a ser utilizado e da eficiência do compilador.
Tipos de complexidade Espacial memória Temporal tempo (a) tempo real (b) número de instruções
Regra 90-10 • Muitos programas executam um pequeno conjunto de instruções muitas vezes (90% do tempo de execução em 10% do código).
Perspectivas de análise • Pior caso • Caso médio • Melhor caso
Perspectivas de análise Pior caso Este método é normalmente representado por O ( ). Por exemplo, se dissermos que um determinado algoritmo é representado por g(x) e a sua complexidade no pior caso é n, será representada por g(x) = O(n). Consiste, basicamente, em assumir o pior dos casos que podem acontecer, sendo muito usado e sendo normalmente o mais fácil de se determinar.
Perspectivas de análise Caso médio Representa-se por θ(). Este método é, dentre os três, o mais difícil de se determinar pois necessita de análise estatística (muitos testes). No entanto, é muito usado pois é também o que representa mais corretamente a complexidade do algoritmo.
Perspectivas de análise Melhor caso Representa-se por Ω ( ) Método que consiste em assumir que vai acontecer o melhor. Pouco usado. Tem aplicação em poucos casos.
Limite Superior Seja dado um problema, por exemplo, multiplicação de duas matrizes quadradas de ordem n (n*n). Conhecemos um algoritmo para resolver este problema(pelo método trivial) de complexidade O(n3). Sabemos assim que a complexidade deste problema não deve superar O(n3), uma vez que existe um algoritmo desta complexidade que o resolve. Um limite superior ( upper bound ) deste problema é O(n3). O limite superior de um algoritmo pode mudar se alguém descobrir um algoritmo melhor. Isso de facto aconteceu com o algoritmo de Strassen que é de O(n log 7). Assim o limite superior do problema de multiplicação de matrizes passou a ser O (nlog7). Outros pesquisadores melhoraram ainda mais este resultado. Atualmente, o melhor resultado é o de Coppersmith e Winograd de O (n 2.376). O limite superior de um algoritmo é parecido com o record mundial de uma modalidade de atletismo. Ele é estabelecido pelo melhor atleta ( algoritmo ) do momento. Assim como o record mundial o limite superior pode ser melhorado por um algoritmo ( atleta ) mais veloz.
Limite Inferior Às vezes é possível demonstrar que para um dado problema, qualquer que seja o algoritmo a ser usado o problema requer pelo menos um certo número de operações. Essa complexidade é chamada Limite inferior (Lower Bound). Veja que o limite inferior dependo do problema mas não do particular algoritmo. Usamos a letra Ω em lugar de O para denotar um limite inferior. Para o problema de multiplicação de matrizes de ordem n, apenas para ler os elementos das duas matrizes de entrada leva O(n2). Assim uma cota inferior trivial é Ω(n2). Na analogia anterior, um limite inferior de uma modalidade de atletismo não dependeria mais do atleta. Seria algum tempo mínimo que a modalidade exige, qualquer que seja o atleta. Um limite inferior trivial para os 100 metros seria o tempo que a velocidade da luz leva a percorrer 100 metros no vácuo. Se um algoritmo tem uma complexidade que é igual ao limite inferior do problema então o algoritmo é ótimo. O algoritmo de CopperSmith e Winograd é de O(n2.376) mas o limite inferior é de Ω(n2). Portante não é ótimo. É possível que este limite superior possa ainda ser melhorado.
Comparando T(n)=2n2 T(n)=10n
Teoria Seja T(n) o tempo de execução de um programa, medido em função do tamanho do conjunto de entrada n. n é um valor não negativo T(n) é um valor não negativo V n Seja f(n) outra função também definida no conjunto dos inteiros não negativos. Se T(n) é no máximo uma constante vezes f(n) – excetuando-se, possivelmente, alguns valores pequenos de n – pode-se dizer que T(n) é O(f(n)).
Teoria • Definição formal • T(n) é O(f(n)) se existe um inteiro n0 e uma constante c (c > 0) | V n > n0 T(n) ≤ c. f(n)
Exemplo Suponha que tenhamos um programa que tenha os seguintes tempos de execução: T(0) = 1; T(1) = 4; T(2) = 9 em geral podemos resumir em T(n)=(n+1)2 Assim, teríamos que T(n) é O(n2) Prova: Pela definição tem-se que se escolhermos uma constante c e um valor n0 tais que T(n) ≤ c . n2, então T(n) será O(n2). Para c = 4 e n0 > 1 temos esta relação.
Exemplo função f(n)=(n+1)2 n2 + 2.n + 1 ≤ n2 + 2.n2 + n2 ≤ 4 . n2
Princípios Gerais 1. Constantes não são importantes Se T(n) é O(d .T(n)) para qualquer d > 0 e se n0 = 0 e c ≥ 1/d Então T(n) ≤ c . (d . T(n)) ≤ c . d . T(n) ≤ 1 . T(n)
Princípios Gerais 2. Termos de ordem menor não são importantes. Seja T(n) um polinômio da forma: ak.nk + ak-1.nk-1 + ... + a1.n + a0(ak>0) Seja n0 = 1 e c = ∑ ai se ai > 0 T(n) não é superior a c.T(n) Exemplo: 2 . n3 é O (0.001 . n3) Seja n0 = 0 e c = 2 / 0.001 (2000) 2.n3≤ 2000 . (0.001 . n3) ≤ 2 n3
Princípios Gerais Outro exemplo: T(n) = 3 . n5 + 10 . n4 – 4 . n3 + n + 1 3.n5 + 10.n4 – 4.n3 + n + 1≤ 3.n5 + 10.n5 + n5 + n5 ≤ 15 n5 Constatando através das proporções... 3 n2 + 10 n + 10 é O(n2) p/ n = 10 73.2%; 24.4% e 2.4% p/ n = 100 96.7%; 3.2%; ...
Princípios Gerais A eliminação de elementos de menor ordem é conseqüência do fato de que o que é realmente importante é a taxa de crescimento e não o valor exato de T(n). Desta forma, T(n) = 2n + n3 tem como resultado a avaliação de que T(n) = O(2n) pois n3/2n tende a zero à medida que n cresce.
Princípios Gerais T(n) = 2n + n3 tem como resultado a avaliação de que T(n) = O(2n) Prova: Seja n0=10 e c = 2. Deve-se provar que para n ≥ 10 tem-se que 2n + n3 ≤ 2. 2n Subtraindo-se 2n de ambos os lados temos: n3 ≤ 2n (2 – 1) Para n = 10 tem-se que 210 = 1024 103 = 1000
Princípios Gerais Tabela O(1) Constante O(log n) Logarítmica O(n) Linear O(n log n) n log n O(n2) Quadrática O(n3) Cúbica O(2n) Exponencial
Princípios Gerais A relação de big-oh (O) é importante para se estabelecer a relação de ≤ entre as funções. Existem outras definições dentro da análise de algoritmos que apresentam outras relações.
Analisando tempo de execução Comandos simples atribuição, leitura, escrita... O(1) Exemplo: a 1 leia (x) escreva (z)
Analisando tempo de execução Repetições Os limites superiores (loops determinados) indicam o limite superior do número de vezes que os comandos dentro do loop serão repetidos. Exemplo 1: Para j 1 até n faça a[j] j Como atribuição é O(1) tem-se: n . O(1) = O(n) Exemplo 2: Para i 1 até n faça Para j 1 até n faça a[i][j] i*j Como atribuição é O(1) tem-se: n . O(1) = O(n) Como tem-se outro loop (externo): n . O(n) = O(n2)
Analisando tempo de execução Repetições (while e repeat) Não têm limite superior (indeterminado). Deve-se detectar um limite superior (pior caso). Exemplo 1: i 1 Enquanto i <> a[i] faça i i + 1 O(n)
Analisando tempo de execução Comandos condicionais Normalmente o comando condicional é O(1) – a menos que tenha uma chamada de função. Assim, as partes então e senão do comando devem ser analisadas individualmente e uma estimativa deve ser atribuída ao conjunto. Exemplo 1: Se a[1][1] = 0 então Para i 1 até n faça Para j 1 até n faça a[i][j] i*j senão Para j 1 até n faça a[i][j] 10 Quando não se tem idéia do que acontecerá assumir pior caso
Analisando tempo de execução Procedimentos Se todos os procedimentos são não recursivos, pode-se começar a computar o tempo de todo o programa à partir dos procedimentos que não chamam outros. Parte-se, então, para aqueles que utilizam os procedimentos cujos valores já foram calculados... E assim por diante...
Analisando tempo de execução Procedimentos Recursivos Análise um pouco mais difícil. função fatorial (n) se n ≤ 1 então fatorial 1 senão fatorial n . fatorial (n-1) O(1) 2.O(1) + O(n-1)
Analisando tempo de execução Indução T(n) = O(1) + T(n-1) Caso base : T(1) = O(1) = a Indução: T(n) = b + T(n-1) T(2) = b + T(1) = b + a T(3) = b + T(2) = b + b + a = 2.b + a T(4) = b + T(3) = b + 2.b + a = 3.b + a ... T(n) = (n-1).b + a O(1)
Analisando tempo de execução Substituição repetida T(m) = b + T(m-1) m > 1 T(n) = b + T(n-1) T(n-1) = b + T(n-2) ... T(2) = b + T(1) T(1) = a
Analisando tempo de execução T(n-1) Substituição repetida T(n) = b + b + T(n-2) T(n) = b + b + b + T(n-3) T(n) = b + b + b + b + T(n-4) T(n) = 3.b + T(n-3) ou 4.b + T(n-4) ... T(n) = (n-1).b + T(n-(n-1)) = (n-1).b + a T(n-2) T(n-3)
Classificação e Pesquisa Métodos de Classificação Bolha Inserção Quicksort Mergesort Pesquisa Seqüêncial Binária
Classificação - bolha O algoritmo da bolha, ou em Inglês, Bubblesort, efetua a ordenação através de trocas entre pares de números sucessivos.
Classificação - Bolha 25 57 48 37 12 92 86 33 • 25 57 48 37 12 92 86 33 • 25 57 48 37 12 92 86 33 • 2548 57 37 12 92 86 33 • 2548 5737 12 92 86 33 • 2548 37 57 12 92 86 33 • 2548 37 57 12 92 86 33 • 2548 37 12 57 92 86 33 • 2548 37 12 57 92 86 33 • 2548 37 12 57 86 92 33 • 2548 37 12 57 86 92 33 • 2548 37 12 57 86 33 92
Classificação - bolha Algoritmo Para passo 1 até n-1 faça Para pos 1 até n-1 faça Se x[pos] > x [pos+1] então troca(x[pos],x[pos+1]) Fim_se Análise: (n-1) . (n-1) = n2 + 2.n + 1 O(n2)
Classificação - Inserção O algoritmo de ordenação por inserção é bastante simples e eficiente para uma quantidade pequena de números. A idéia de funcionamento é semelhante ao ordenamento de cartas de baralho na mão de um jogador. A mão esquerda começa "vazia" e a mão direita insere uma "carta" de cada vez na posição correta. Ao final, quando todas as "cartas" foram inseridas, elas já estão ordenadas. Para encontrar, durante a inserção, a posição correta para um valor, compara-se este valor um-a-um com as cartas já na mão esquerda, até encontrarmos a posição correta. Exercício: fazer o algoritmo
Classificação – mergesort A idéia de “aceleração” por trás do algoritmo de Mergesort (e também do Quicksort) é que é mais rápido ordenar 2 seqüências de n/2 números do que uma seqüência de n números (dividir p/ conquistar). A complexidade do Mergesort é proporcional a n . log n, onde log n é o logaritmo de base 2 do número n.
Classificação – mergesort Neste algoritmo, recursivamente, divide-se a seqüência de números a ser ordenada em 2 subseqüências. Ordena-se cada uma delas e, posteriormente, combinam-se os resultados.
Classificação – mergesort Mergesort(vetor a[]; inteiro p, r) Inicio Se (p < r) então q (p + r) / 2; mergesort(a, p, q) mergesort(a, q+1, r); unir(a, p, q, r); Fim_se Fim
Classificação - Quicksort Quicksort é na maioria das vezes a melhor opção prática para ordenação porque ele é muito eficiente na média. O tempo esperado de execução também é proporcional a n . log n
Classificação - Quicksort procedimento QuickSort (vetor A; inteiros p, r) Inteiro q Inicio Se p < r então Inicio_se q particionar(A,p,r) QuickSort(A,p,q) QuickSort(A,q+1,r) Fim_se Fim
Classificação - Quicksort A parte não-trivial do Quicksort é a função particionar que divide o vetor original em 2 sub-vetores de maneira que o primeiro não contenha nenhum valor maior do que o segundo.