• Nenhum resultado encontrado

Notas de Aula

N/A
N/A
Protected

Academic year: 2021

Share "Notas de Aula"

Copied!
37
0
0

Texto

(1)

Plano de Aula

IDENTIFICAÇÃO

CURSO: 109 - Ciência da Computação DISCIPLINA: INF222 Análise de Algoritmos CARGA HORÁRIA: 54h

TURMA: TA

PROFESSOR: Samuel de Moraes Lemes OBJETIVO DO CURSO

O objetivo da Análise de Algoritmos é inicialmente possibilitar que o aluno aprenda algoritmos básicos e diferentes técnicas utilizadas para resolver problemas computacionalmente. Em seguida, o objetivo é que ele consiga utilizar esse conhecimento para fazer algoritmos que sejam os mais eficientes possíveis, capacitando os alunos a projetar algoritmos eficientes, analisar a complexidade de algoritmos corretos, medindo a sua eficiência e ter conhecimento sobre a teoria da intratabilidade. No futuro, quando em sua vida profissional os alunos precisarem projetar um algoritmo para resolver algum problema, ou analisar um algoritmo, terão a base necessária para exercer esta tarefa, inclusive para procurar e entender material mais aprofundado sobre a área se necessário.

OBJETIVO ESPECÍFICO

Analisar a complexidade de algoritmos bem como desenvolver algoritmos otimizados.

EMENTA Complexidade Pessimista; Média; Otimização; Projeto de Algoritmos; Programação Dinâmica; Intratabilidade. RECURSOS

Os recursos utilizados para ministrar esta disciplina poderão ser:

 Apostila de referência que será fornecida pelo professor via e-mail;  Notebook e Datashow;

 Xerox e Impressão de materiais relacionados à disciplina.

BIBLIOGRAFIA:

TOSCANI, Laira Vieira et alli. Complexidade de Algoritmos. Porto Alegre: Sagra Luzzato, 2001.

(2)

 KNUTH, Donald E. et alli. Matemática Concreta: Fundamentos para Ciência da Computação. 2ª. 1995.

 CORMEN, Thomas H. Algoritmos: tradução da 2ª Edição Americana. Campus.

AVALIAÇÕES:

Avaliação 1: Valor:70,0 Exercícios: 30,0

Avaliação 2: Valor:60,0 Exercícios: 30,0 Trabalho: 1,0 Avaliação 3: Valor:80,0 Exercícios: 20,0

CONTEÚDO PROGRAMÁTICO

Disciplina Dia Aula

T/P Descrição

ANÁLISE DE ALGORITMO 3 07/ago Ter T

Apresentação da Disciplina; Apresentação aos alunos; Ementa; Formas de Avaliação; Métodos Avaliativos.

ANÁLISE DE ALGORITMO 3 14/ago Ter T

Complexidade de Algoritmo; Complexidade de problemas e algoritmos; Medidas de desempenho/

Lista 1 para Próxima Aula.

ANÁLISE DE ALGORITMO 3 21/ago Ter T

Pontuar Lista 1; Correção Lista 1; Revisão matemática:

Piso, Teto, Logaritmo (Expoente, Produto, Potência, Divisão e Troca de Base), Somatórias(Fórmula Básica, Série Aritmética, Série Geométrica). Introdução Notações de complexidade.

ANÁLISE DE ALGORITMO 3 28/ago Ter T

Exemplos de Notações de complexidade; Lista 2 para a próxima aula. Espaço para Resolver os exercícios da Lista 2; Introdução de análise de complexidade pessimista; Exemplos de Análise de complexidade em algoritmos.

ANÁLISE DE ALGORITMO 3 04/set Ter T Revisão para Avaliação ANÁLISE DE ALGORITMO 3 11/set Ter T 1 Avaliação

ANÁLISE DE ALGORITMO 3 18/set Ter T

Entrega de Resultados; Correção da Avaliação; Introdução à Projetos de Algoritmos; Distribuição de trabalhos para serem apresentados

ANÁLISE DE ALGORITMO 3 25/set Ter T Método Mestre; exemplos e exercícios. ANÁLISE DE ALGORITMO 3 02/out Ter T Exercícios

ANÁLISE DE ALGORITMO 3 09/out Ter T Exercícios

ANÁLISE DE ALGORITMO 3 16/out Ter T Revisão para Avaliação ANÁLISE DE ALGORITMO 3 23/out Ter T 2 Avaliação

ANÁLISE DE ALGORITMO 3 30/out Ter T

Correção da Avaliação; Introdução à programação Dinâmica; Lista 3.

ANÁLISE DE ALGORITMO 3 06/nov Ter T

Pontuar Lista 3; Exemplos de Linha de produção;

Exercícios em sala ANÁLISE DE ALGORITMO 3 13/nov Ter T Exercícios em sala ANÁLISE DE ALGORITMO 3 20/nov Ter T Revisão para Avaliação ANÁLISE DE ALGORITMO 3 27/nov Ter T 3 Avaliação

(3)

Conceitos Gerais

1. Objetivos da Complexidade

O objetivo da Análise de Algoritmos é inicialmente possibilitar que o aluno aprenda algoritmos básicos e diferentes técnicas utilizadas para resolver problemas computacionalmente. Em seguida, o objetivo é que ele consiga utilizar esse conhecimento para fazer algoritmos que sejam os mais eficientes possíveis, capacitando os alunos a projetar algoritmos eficientes, analisar a complexidade de algoritmos corretos, medindo a sua eficiência e ter conhecimento sobre a teoria da intratabilidade. No futuro, quando em sua vida profissional os alunos precisarem projetar um algoritmo para resolver algum problema, ou analisar um algoritmo, terão a base necessária para exercer esta tarefa, inclusive para procurar e entender material mais aprofundado sobre a área se necessário.

2. Eficiência e Corretude de Algoritmos

A complexidade vem ganhando destaque a ponto de que afirmações que a complexidade é o coração da computação, já não surpreendem.

A análise da complexidade de um algoritmo é realizada, usualmente, de maneira muito particular. Alguns conceitos são gerais, no sentido de que só dependem da estrutura do algoritmo. Esses conceitos são apresentados quando for apresentada a análise da complexidade pessimista e da complexidade média.

A análise da complexidade é muito importante na fase de projeto do algoritmo. O estudo das técnicas de desenvolvimento de algoritmo permite prever alguns aspectos da complexidade do algoritmo resultante.

O projeto de um algoritmo eficiente é sempre um propósito de qualquer projetista e é uma preocupação já nessa fase.

No decorrer do curso, alguns métodos de desenvolvimento de algoritmos serão apresentados, e a complexidade dos algoritmos gerados será analisada.

A complexidade também pode ser vista como uma propriedade do problema, o que significa dar uma medida independente do tratamento dado ao problema. Alguns problemas são “bem comportados” e permitem chegar a limites de complexidade.

(4)

Outros problemas parecem ficar tão difíceis de serem resolvidos para instâncias grandes que parecem tornar-se intratáveis. Os problemas de intratabilidade serão apresentados no final do curso.

No entanto, para desenvolver algoritmos realmente eficientes, não basta conhecer técnicas e alternativas para problemas comuns. O programador deve ter a capacidade de prever, ao desenvolver um algoritmo, qual será o seu comportamento, quer com pouco ou com muitos dados de entrada.

A tentativa de prever o comportamento do algoritmo consiste em avaliar sua complexidade. Para isso, são feitos cálculos, que podem ser simples ou complexos. Como esses cálculos envolvem definições e notações específicas, como analisá-las, para só então sabermos a complexidade de um software.

(5)

Temos então três objetivos para a análise de algoritmos: 1. Avaliar se um algoritmo é eficiente;

2. Verificar se um algoritmo é correto ou incorreto;

3. Comparar vários algoritmos (que resolvem o mesmo problema) para decidir qual é o mais eficiente.

(6)

3. Algoritmos

Definições

Um algoritmo é definido pela matemática como um “processo de cálculo, ou de resolução de um grupo de problemas semelhantes, em que se estipulam regras formais para a obtenção do resultado, ou da solução do problema”.

Qualquer procedimento computacional bem definido que toma um valor (dado) como entrada, processa esta entrada e produz uma informação de saída.

Em computação, é comum definirmos um algoritmo como “um conjunto de passos necessários para resolver um problema”.

Outra definição é a de que “um algoritmo, intuitivamente, é uma sequência finita de instruções ou operações básicas (...), cuja execução, em tempo finito, resolve um problema computacional” (Salvetti e Barbosa, 1998).

Um algoritmo, na computação, é qualquer procedimento computacional que recebe como entrada um valor (ou conjunto de valores) e produz como saída, outro valor (ou um conjunto de valores).

Finalmente, então, podemos dizer que um algoritmo é uma sequência de passos computacionais que transforma entrada em saída.

Algoritmo Correto:

É aquele capaz de produzir a saída apropriada para uma das instâncias.

É fundamental que um algoritmo produza a solução com dispêndio de tempo e de memória razoáveis. Daí se deve a importância de projetar e analisar um algoritmo.

Esse, portanto, é o objeto que será estudado a partir de agora. Trechos de código só podem ser considerados algoritmos quando eu consigo definir claramente:

1) o problema;

2) os dados de entrada; 3) os dados de saída.

Estratégias:

• Especificar (definir propriedades)

• Arquitetura (algoritmo e estruturas de dados)

• Analise de complexidade (tempo de execução e memória) • Implementar (numa linguagem de programação)

(7)

4. Instâncias

Possíveis entradas para o algoritmo.

Uma instância de um problema consiste de um conjunto de dados de entrada e saída utilizadas durante uma única execução. Por exemplo, as figuras 11 e 12 mostram diferentes instâncias da execução do mesmo algoritmo.

Por exemplo, eu posso ter um trecho de código no qual eu identifico os seguintes elementos:

Problema: Encontrar o maior e o menor valor de um vetor com n elementos. Entrada: Vetor com n elementos

Saída: O elemento com o menor valor de todos e o elemento com o maior valor.

Neste caso, se o trecho tem apenas essa função, pode ser considerado um algoritmo e é possível analisá-lo individualmente.

Exemplos de problemas

Problema 1: Encontrar o maior e o menor valor de um vetor com n elementos. Entrada: {1, 34, 56, 32, 3, 54, 3, 356, 3, 2, 23, 78, 65, 32, 11, 1, 43, 356, 66}

Saída: Menor valor = 1 Maior valor = 356

Problema 2: Encontrar o maior e o menor valor de um vetor com n elementos. Entrada: {2,54,67,93,54,23,345,67,42,447,4,983,10,76,31,15,57,83,45,794,346,44}

(8)

Problema 3 da primariedade.

Problema: determinar se um dado número é primo. Exemplo:

Entrada: 9411461 Saída: É primo.

Exemplo:

Entrada: 8411461 Saída: Não é primo.

Problemas 4 ordenação

Definição: um vetor A[1 . . . n] é crescente se A[1] ≤ . . . ≤ A[n].

Problema: rearranjar um vetor A[1 . . . n] de modo que fique crescente.

Entrada: 1 n Saída: 1 n 33 55 33 44 33 22 11 99 22 55 77 11 22 22 33 33 33 44 55 55 77 99

Por esses exemplos 3 e 4 podemos verificar que em diferentes instâncias de execução, os dados de entrada podem ser bastante variados, podendo inclusive ter uma grande variação de volume. Ou seja, na instância apresentada no Problema 3, o vetor de entrada tinha 19 valores, enquanto na instância apresentada no Problema 4, o vetor de entrada tinha 22. Da mesma, em outra instância é possível que eu tenha 500 elementos de entrada.

A questão que se levanta então é: dado um algoritmo que funciona de forma eficiente para 10 elementos, ele continuará funcionando de forma eficiente para 10.000 ou mais? O algoritmo deve trabalhar corretamente sobre todas as instâncias para as quais foi projetado para solver. Portanto, um algoritmo para o qual é possível achar uma instância de dados de entrada para a qual ele não consegue achar uma resposta correta é um algoritmo incorreto. No entanto, provar o contrário, que o algoritmo é correto para qualquer instância, não é tão simples. Para isso, o algoritmo deve ser avaliado utilizando alguns critérios.

(9)

5. Avaliação de Algoritmos

Algoritmos podem ser avaliados utilizando-se vários critérios. Geralmente o que interessa é a taxa de crescimento ou espaço necessário para resolver instâncias cada vez maiores de um problema. Podemos associar um problema a um valor chamado de ‘tamanho’ do problema, que mede a quantidade de dados de entrada.

O tempo que um algoritmo necessita expresso como uma função do tamanho do problema é chamada de complexidade temporal do algoritmo. O comportamento assintótico dos algoritmos (ou funções) representa o limite do comportamento de custo quando o tamanho cresce. O comportamento assintótico pode ser definido como o comportamento de um algoritmo para grandes volumes de dados de entrada.

A complexidade temporal de um algoritmo pode ser dividida em 3 aspectos:

1. Melhor caso – o melhor caso representa uma instância que faz o algoritmo executar utilizando o menor tempo possível.

2. Pior caso – o maior tempo demorado pelo algoritmo para executar alguma instância. 3. Caso médio – a média de tempo que o algoritmo demora na execução.

Geralmente, o mais importante é avaliar o pior caso (porque pode inviabilizar o algoritmo) e o caso médio, porque representa como o programa vai se comportar, na prática, na maioria dos casos.

Avaliação Empírica

A forma mais simples de se avaliar um algoritmo é implementá-lo em um computador e executá-lo com várias instâncias do problema. Define-se então um critério para a eficiência, como por exemplo, o tempo gasto para execução. Esse tipo de avaliação é chamado de empírica.

Com base na observação, pode-se calcular:

• o pior caso (a instância de execução que levou mais tempo), • o melhor caso (a instância de execução que gastou menos tempo)

• e o caso médio (a média do tempo gasto em todas as instâncias de execução).

O problema com esse tipo de avaliação é que o tempo gasto vai depender do computador utilizado, do compilador, da linguagem de programação, etc.

Avaliação Teórica

Na avaliação teórica, que é a que vai ser focalizada aqui, consiste em encontrar uma fórmula matemática que expresse o recurso (por exemplo, o tempo) necessário para o algoritmo executar em função do tamanho dos dados de entrada.

(10)

Associando uma função a um algoritmo

Para encontrar uma fórmula matemática que expresse quanto tempo será necessário para cada volume de dados de entrada, podemos utilizar primeiramente uma avaliação empírica.

Para isso, deve-se montar uma tabela relacionando volumes de dados com seus respectivos tempos de execução. Considere por exemplo um programa fictício Raiz, que recebe como entrada um vetor de inteiros e devolve como saída um vetor com a raiz quadrada de cada um dos elementos do vetor. Suponha que o programa é executado várias vezes, com diferentes números de elementos de entrada, tendo seu tempo cronometrado, como apresentado na Tabela 1.

Número de elementos no vetor Tempo gasto para execução (segundos)

1 0,001 10 0,01 50 0,05 100 0,1 500 0,5 1.000 1 5.000 5 10.000 10 50.000 50 100.000 100 Tabela 1

Se colocarmos esses valores em um gráfico (vide Gráfico abaixo), veremos que esses valores são representados por uma reta. Lembrando um pouco das aulas de geometria, temos que uma reta pode ser representa por uma função linear do tipo ax+b.

Tam. Entrada

(11)

Assim, se conseguirmos encontrar uma fórmula como essa que represente o comportamento temporal do algoritmo em relação ao número de dados de entrada, então podemos saber o seu comportamento para qualquer volume de dados de entrada! Basta aplicar o valor na fórmula.

Pisos e Tetos:

Piso =  X é o maior inteiro menor do que X.

 X  X 3,53

Teto =  X é o menor inteiro maior do que X

 X  X 5,36 n n n               2 2

(12)

Logaritmos:

loga n = qual número elevado a base (a) dá o valor n  log2 16 = 4

logakn = (log2n)k

log2.log2n=log2.(log2n)

logb(1a) = - logba

Expoente

loga(n.m) = logan + logam Produto

loga(nm) = m.logan Potencia

loga(nm) = logan – logam divisão

logan = logbn . logab troca de base logan

(13)

Somatórias:

Representam, expressam, estruturas de repetição

Formula Básica

Seqüência de números : a1,a2,a3, ... , na.

A soma destes números pode ser descrita como, para n positivo:

        n k an a a a a a ak 1 ... 5 4 3 2 1 Série Aritmética: O somatório

        n k 1 1 ... 1 1 1 1 1 1 ,

é uma série aritmética e tem o valor

n n k

  1 . 1  (n)

(14)

Que em um algoritmo representa duas estruturas de repetição aninhadas: For k=1 to n O somatório

        n k n k 1 ... 5 4 3 2 1 ,

é uma série aritmética e tem o valor

2 ) 1 ( . 2 1 2 1 n n n n k n k    

  (n2) 



  n k k j 1 1 . 1

Que em um algoritmo representa duas estruturas de repetição aninhadas: For k=1 to n For j=1 to k O somatório

         n k n k 0 2 2 2 2 2 2 2 2 ... 5 4 3 2 1 0 ,

é uma série aritmética e tem o valor

6 3 2 ) 1 2 ).( 1 ( . 6 1 3 2 1 2 n n n n n n k n k      

  (n3) 



   n k k j j i 0 0 0 . 1

Que em um algoritmo representa duas estruturas de repetição aninhadas: For k=0 to n

For j=0 to k For i=0 to j

(15)

Série Geométricas (Árvores)

       n k n k x x x x x 0 3 2 ... 1   X=3

     n k n k x x x 0 1 1 1  ( ) n k   k é uma constante K=0 K=1 K=2

(16)

Notação O, Ômega e Theta

Após obter a função que representa o comportamento de um software, basta analisá-la para termos uma medida da complexidade do software. Para isso se utiliza as três notações a seguir: , Ômega e Theta. Elas são utilizadas também para comparar

funções de diferentes algoritmos.

Notação O

Esta notação é utilizada para analisar o pior caso.

Uma função g(n) é (f(n)) se c>0 e n0 tais que g(n) <= c f(n) para n>=n0. Explicação:

uma função g(n) é da ordem de complexidade de f(n) se existe uma constante c e um valor n0 tal que para qualquer valor de n maior do que n0 g(n) é menor ou igual a c.f(n).

Isso significa que:

• f(n) é um limite superior para g(n) • f(n) denomina assintoticamente g(n) Propriedades: • f(n) = (f(n)) • c. f(n) = (f(n)), c=constante • (f(n)) = (f(n)) = (f(n)) • (O(f(n))) = (f(n)) • (f(n)) = (g(n)) = (max(f(n),g(n)) • (f(n)) . (g(n)) =(f(n) . g(n)) Notação Ômega)

Esta notação é utilizada para analisar o melhor caso.

Uma função f(n) = (g(n)) se existem constantes c e n0, tal que c.g(n) <=f(n) para n>=n0. Isso significa que:

• f(n) é um limite inferior para g(n)

Notação (Theta)

Esta notação é utilizada para analisar o caso médio.

Uma função f(n) =  (g(n)) se existem constantes c1, c2 e no quais em que c1. g(n) <= f(n) <= c2. g(n) para n>=no.

A notação O é a mais utilizada, porque geralmente o mais importante é descobrir o pior caso, para saber se existe alguma possibilidade de o algoritmo falhar, isto é, não conseguir executar caso se entre com um volume de dados demasiado grande.

Fazendo uma analogia para melhor compreensão das notações, temos o seguinte, considerando que f e g são funções que representam o comportamento de dois algoritmos diferentes.

(17)

F(n) = O (g(n)) → f<=g F(n) =  (g(n)) → f>=g F(n) =  (g(n)) → f=g

Estimativas de Tempo de Execução

Sabemos que os principais recursos ocupados por um software em execução são espaço em disco, espaço em memória e tempo. Mas enquanto o espaço é uma questão que pode ser facilmente resolvida, o tempo que um programa gasta para executar pode inviabilizar o seu uso. Portanto, vamos focalizar na estimativa de tempo de execução.

Já vimos que o mais importante é tentar avaliar o tempo de execução para grandes volumes de dados de entrada. Para isso, vamos tentar achar uma fórmula que expresse o tempo de execução em função do volume de dados de entrada.

Dependendo da fórmula encontrada, podemos classificar e comparar a complexidade de diferentes algoritmos.

(18)

Estruturas algorítmicas na complexidade

É útil uma metodologia para analisar a complexidade de um algoritmo com base em sua estrutura.

A complexidade do algoritmo é obtida a partir da complexidade de suas componentes.

Precisamos da complexidade das componentes básicas e de saber como combinar estas complexidades.

Estruturas:

Atribuição var = “b”;

Sequência instrução 1; instrução 2;

Condicional se b então v=“b” senão v=“c”

Iteração Definida para i=j até n faça Iteração Indefinida enquanto b faça

(19)

Atribuição

Depende do tipo de dado a ser atribuído.

Pode ser simples como a atribuição de um valor a uma variável

Ou, complexa, a inserção de um novo nó num grafo, o tipo a ser atribuído pode ser dado por uma função.

Ex: a = retornaNumero();

No primeiro caso a atribuição será sempre um valor constante;

No segundo caso nós necessitamos analisar a função para descobrir a complexidade; Atribuição – Exemplo:

A) (i, j = variáveis)

i = 0; {inicialização} J = i; {transferência}

– Ambas tem complexidade constante = Ө(1)

B) m = Max(v) {função que retorna o maior inteiro do vetor v)

– Determinar o máximo da lista v, na função Max, gera a complexidade = Ө(n)

– Transferir é tempo constante = Ө(1) – Complexidade = Ө(n) + Ө(1) = Ө(n)

– Na execução da atribuição, temos esforço computacional associado à: – Avaliação da expressão a ser atribuída;

– Transferência do valor avaliado.

– Assim temos que a complexidade da instrução será a soma do tempo gasto para tratar da expressão mais a o tempo gasto para transferi-la.

(20)

Sequência:

Equação de complexidade simples.

A complexidade de uma sequência de instruções é a soma dos desempenhos de suas componentes.

a) (i, j = variáveis)

1. i = 0;

2. j = i;

• Complexidade = complex(1) + complex(2). • Complexidade = 1 + 1 = Ө(1)

a) (v = vetor de inteiros, m é um inteiro)

1. m = Max(v);

2. m= m + 1;

• Complexidade = n + 1 = Ө(n)

Na execução da sequência de instruções sobre uma entrada n, temos esforço computacional associado à:

• Execução de 1 sobre entrada d, mais a ; • Execução de 2 sobre entrada S(d). Onde S(d) é a saída dada por S com a entrada n.

Note, que a execução de uma instrução pode alterar o tamanho dos dados.

Isto pode ocorrer, por exemplo, no caso no qual as instruções atuam sobre uma lista, a execução da 1° instrução reduz o tamanho da lista e T faz uma procura sequencial na lista.

Entretanto, assintoticamente, esta alteração não influencia, pois a 1° instrução irá manipular a entrada de uma forma constante. Ex.: dividir a entrada por 2. E neste tipo de analise podemos desconsiderar as constantes.

Funcao(Vetor A) :

A = Concatena(A,A); //Entrada tam=n

For (i=1;i<tam(A);i++) //Entrada tam=2n

(21)

Condicional:

A complexidade deve indicar o caminho que gaste o maior tempo de execução. A) (variáveis inteiras i , j)

se i != j então

i = i + j;

senão

j = i + 1;

Esta estrutura condicional envolve:

Determinar a complexidade da condição, com complexidade Ө(1).

Condição verdadeira executa a instrução i = i + j, que tem complexidade Ө(1) Condição falso, executa a instrução j = i + 1, que tem complexidade Ө(1)

A complexidade desta estrutura condicional é Ө(1)

Se i == j então i = Max(v) Senão i++;

Complexidade: condição = Ө(1) + (verdade = Ө(n) ou falso = Ө(1)). Como devemos considerar o pior caso temos = O(n)

Resumindo:

Para uma estrutura condicional, o tempo de execução, T(n), nunca é maior que o tempo do teste da condição mais o tempo do maior entre os comandos relativos ao

então e os comandos que são relativos ao senão.

Estruturas de repetição Definidas (incondicional) :

Trataremos agora de uma iteração definida, tal como: 1. para i = j até n faça

2. k++;

Esta iteração causa a execução de n vezes a linha 1, que tem um custo constante, Ө(1), de atribuir e comparar, considerando que a linha 2 não irá mudar o valor de i e de n. A linha 2 tem uma instrução que será executada n-1 vezes e tem um custo constante Ө(1)

(22)

Estruturas de repetição

Desta forma podemos dizer, que o tempo de execução de uma repetição é pelo menos o tempo dos comandos dentro da repetição X (vezes) o número de vezes que a iteração é executada.

para i=1 até n faça } n \ c.n = Ө(n)

: } c /

Ou seja, uma repetição com tamanho n tem complexidade = Ө(n)

Se a complexidade dos comandos dentro dela for constante, se for relacionado a entrada, i.e = Ө(n) , a complexidade da estrutura de repetição seria Ө(n2)

Estruturas de repetição aninhadas:

A analise de toda estrutura de repetição é feita de dentro para fora.

O tempo total da execução dos comandos dentro de um grupo de repetições aninhadas é o tempo de execução dos comandos multiplicado pelo produto do tamanho de todas as repetições.

para i=1 até n faça } n \

para j=1 até n faça } n c.n = Ө(n2)

: } c /

Estruturas de repetição (exemplos) a)

1. para i = 1 até 20 faça

2. m = m + 1

Complexidade tem ordem constante = 20*1 = Ө(20) = Ө(1) b) (A vetor de n elementos inteiros)

1. para i = 1 até n faça

2. A[i] = 0 {inicialização}

Complexidade tem ordem = n*1 = Ө(n) Estruturas de repetição (exemplos) c) (A e B vetor de n elementos inteiros)

1. para i = 1 até n faça

2. X = A[i]

3. Y = Max(i)

Complexidade tem ordem = n*(1 + n) = Ө(n2)

(23)

Estruturas de repetição (exemplos) d)

1. para i = 1 até n faça

2. para j = 1 até n faça

3. para k = 1 até n faça

4. m = m + 1

Complexidade tem ordem = n*n*n*1 = Ө(n3)

Estruturas de repetição (exemplos) e)

1. para i = 1 até n faça

2. para j = 1 até n faça

3. para k = 1 até n faça

4. m = m + 1

5. para i = 1 até n faça

6. m = m - 1

7. m = m*m

Complexidade tem ordem = (n*n*n*1) + (n*1) + 1 = Ө(n3) Estruturas de repetição (exemplos)

Discuta situações onde os comandos dentro da interação manipulam o i ou o n. • Soma em i

• Multiplicação em i • Subtração em n • Divisão em n • Torna indefinida

• Estruturas de repetição indefinidas (condicionais)

• Neste tipo de interação, geralmente o número de iterações não fica determinado na estrutura de repetição, contrastando com a iteração definida. • A complexidade depende de uma analise da execução da estrutura.

• Iremos considerar apenas repetições que terminam.

• Devemos analisar a quantidade de vezes que a condição da estrutura de repetição será verdadeira, (h), no pior caso.

• A complexidade será a somatória, h vezes, do custo da execução de cada iteração.

• Exemplos:

• Para variáveis inteiras i:

• i = 0;

• Enquanto i <= 10 faça • i++;

Se o crescimento de i é constante, nos temos h=10. • h=10 + 1(i=0) + 1(i>10)  12.  Ө(12)  Ө(1)

(24)

• Exemplos:

• Para variáveis inteiras i, f:

• f = 1; i=n;

• Enquanto i > 1 faça • f = f*i;

• i--;

• A quantidade de vezes que a estrutura executa é dependente do tamanho de

n, h=n.

• Como o tempo de execução das instruções das linhas 3 e 4 são constantes  Ө(n)

• Exemplos:

• Para variáveis inteiras i, n:

• i=n;

• Enquanto i > 1 faça • se (i==10) saia(); • i--;

• A quantidade de vezes que a estrutura executa é dependente do tamanho de

n, h=n-10h=n.

• Como o tempo de execução das instruções das linhas 3 e 4 são constantes  Ө(n)

• Exemplos:

• Para variáveis inteiras i, n:

• i=1;

• Enquanto i < n faça • se (i==10) saia(); • i++;

• A quantidade de vezes que a estrutura executa não é dependente do tamanho de n, h=10.

• Como o tempo de execução das instruções das linhas 3 e 4 são constantes  Ө(1)

• Exemplos:

• Para variáveis inteiras i, n:

• i=sel(1,2);

• Enquanto i < n faça • se (i==10) saia(); • i=i+2;

• A quantidade de vezes que a estrutura executa é dependente do tamanho de

n, h=n/2(pior caso), e h=5(melhor caso).

• Como o tempo de execução das instruções das linhas 3 e 4 são constantes  O(n)

(25)

• Exemplos:

• Para variáveis inteiras i, n e Vetor A:

• n = Tam(A); encontrei = falso;

• Enquanto i < n || encontrei==false faça • se (A[i]==‘x’) encontrei = true; • i++;

• A quantidade de vezes que a estrutura executa é dependente do tamanho de

n, h=n(pior caso), e h=1(melhor caso).

• Como o tempo de execução das instruções das linhas 3 e 4 são constantes  O(n)

(26)

Exercícios:

Exercício 1

• Para variáveis inteiras i, Max, Min e Vetor A:

• n = Tam(A); Max = A[1]; Min = A[1]; i=0; • Enquanto i <= n faça

• se (A[i]>Max) Max = A[i]; • se (A[i]<Min) Min = A[i]; • i++;

• A quantidade de vezes que a estrutura executa é dependente do tamanho de

n, h=n

• Como o tempo de execução das instruções das linhas 3, 4 e 5 são constantes  O(n)

• Exercício 2:

• Para variáveis inteiras i, Max, Min e Vetor A, B:

• n = Tam(A); B={“a”,”b”,”c”}; i=0; • Enquanto i <= n faça

• m = Tam(B); j=0; existe = false; • Enquanto j <= m faça

• se (B[j]==A[i]) existe == true; • Se existe == false

• insere(A[i], B)

• i++;

• Analise Exercício anterior: • : • Ө(1) • Enquanto i <= n faça Ө(n) • Ө(1) • Enquanto j <= m faça Ө(m) • Ө(1) • Ө(1) • Ө(1) Complexidade = Ө(nm)  m + c = n  Ө(n2)

(27)

Métodos para projetos de algoritmos

Abordagem D&C (Dividir - Conquistar)

Recursividade: técnica de desenvolvimento de algoritmo, no qual o algoritmo chama a si mesmo para resolver um problema.

n<- n * fatorial (n - 1) 8 <- 8 * fatorial (7) fatorial(1) ou fatorial (0) = 1 fatorial(n - 1) => n * fatorial(n - 1) /*Algoritmo Fatorial*/ fatorial (int i) if(i == i || i == 0) return 1; else return i * fatorial(i--);

Definir a Abordagem D & C

Nesta abordagem, o problema deve ser dividido em vários subproblemas que são similares ao problema original, mas, menores e combina as soluções para criar uma solução para o problema original.

Etapas deste processo:

Dividir: Algoritmo divide um problema em subproblema com características simples ao original.

Conquistar: Cada subproblema é resolvido recursivamente

Combinar: A solução de cada subproblema é combinada para gerar a solução do problema original.

(28)
(29)

Análise da complexidade

Algoritmos recursivos

Encontrar uma equação de recorrência.

T(n): tempo de execução necessário para resolver um problema de tamanho n.

Caso da parada  Definir c

 Se o tamanho de n é suficientemente pequeno n<=c.  Neste caso => T(n) => O(1)

Casos recursivos

 Se o tamanho de n é maior que c.

 O problema, de tamanho n, será dividido em subproblemas.  Encontrar o custo de dividir este problema B(n).

 Encontrar o custo de combinar os problemas C(n).

MERGESORT (Vetor A, int i, int f) { if(i < f) then { m=[(i+f)/2]; MERGESORT(A,i,m) MERGESORT(A,m+1,f) MERGE(A,i,m,f) } } T(n) = (1), se n<=1 At(n/b) + C(n) +B(n) 2T(n/2) + (n), se n > 1

(30)

Método Mestre

Resolve a complexidade de algoritmos recursivos, que tenham o seguinte formato: T(n) = aT(n/b) + C(n) + B(n), onde e>= 1 e b >= 1, são constantes, e C(n) + B(n) = n a = quantidade de vezes em que o problema é chamado em sua recursão.

C(n) = é o custo de Combinar as soluções

B(n) = é o custo de dividir o problema em subproblemas.

Então T(n) pode ser limitado assintoticamente (tem a complexidade definida) por:

1. Se f(n)(nlogbae), para uma constante e > 0, então: [f(n) < g(n)]

) ( ) (n nlog ab T 2. Se f(n)(nlog ab ) , então: [f(n) = g(n)] ) log . ( ) (n nlog n T ba

3. Se f(n)(nlogba) para uma constante e > 0 e af(n/b)cf(n), para alguma

constante c < 1 e n suficientemente grande, então : [f(n) > g(n)]

)) ( ( ) (n f n T

Analisar a complexidade das seguintes equações:

1. T(n) = T(2n/3) + Θ(1) (2º caso) T(n) = Θ(logn) 2. T(n) = 3T(n/4) + Θ(n.logn) (3º caso) T(n) = Θ(n.logn) 3. T(n) = 4T(n/2) + Θ(n) (1º caso) T(n) = Θ(n.logn) 4. T(n) = 4T(n/2) + Θ(n2) (2º caso) T(n) = Θ(n2.logn) 5. T(n) = 4T(n/2) + Θ(n3) (3º caso) T(n) = Θ(n3) EXPOENTE(a,n) se n=0 entao retorna 1 senao { an = EXPOENTE(a,      2 n ); an = an * an; se(n mod 2)=1 an = an * a; retorna(an); }

(31)

Busca Binária

Problema: dado um vetor ordenado A com n números reais e um real x, determinar a posição 1=< i <= n tal que A[i] = X, ou que não exista tal x.

Proposta com D&C

buscaBinaria(A, e, d, x) se(e==d)entao { se(A[e] == x)entao retorne(e); senao retorne(-1); } senao i=[(e+1)/2] se(A[i]==x) retorne(i); senao { se(A[i]>x) i=buscaBinaria(A, e, i-1, x); senao i=buscaBinaria(A, e, e+1, x); } retorna(i);

(32)

Máximo e Mínimo

Problema: dado um conjunto s de n >=2 números reais, determinar o maior e o menor número.

MaxMin(s)

se tam(s)<= 2 entao

retorne(RC max(s[0], s[1]), min(s[0], s[1])); senao { s1 = s[1...tam(s)/2]; s2 = s[tam(s/2)...n]; r1= MaxMin(s1); r2= MaxMin(s2); retornar(R[Max(r1[0], r2[0], Min(r[1], r2[1]))]); }//Fim Senão T(n) = (1), para n<=2 2T(n/2) + (1), para n > 2 a=2; b= 2; c=O1; n n f n n f   ) ( ) ( 1 ,

(33)

VERIFICA_HEAP(a,i)

1. E= esq(i)

2. D= dir(i)

3. Se A[E] > A[i] e E <= Tam_Heap(A) entao

4. Maior = E

5. Senao

6. Maior = i

7. SeA[D] >= A[Maior] e D <= Tam_Heap(A) entao

8. Maior = D

9. Se Maior <> i entao

10. Troca(A[i], A[Maior])

11. Verifica_Heap(A, Maior)

CONSTROI_HEAP(A)

1. For (i = Com(A)/2 ate 1) faca

2. Verifica_Heap(A,i)

HEAP_SORT(A)

1. CONSTROI_HEAP(A)

2. Para i=Comp(A) ate 2 faca

3. Trocar(A[i], A[1])

4. Comp(A)

5. VERIFICA_HEAP(A,1)

F1[j] = e1 + a[1]; se j=1

Min(F1 [j-1]; F2[j-1] + T2[j-1]) + a[1]; Para F2[j] =

logn nlogn nlogn c logn C 1 C X X=min(C1; c2) + a(x)

(34)

Programação Dinâmica

Resolve problemas combinando as soluções dos subproblemas (igual ao D & C).

Aplicável quando os subproblemas não são independentes, ou seja, compartilham sub subproblemas

Dividir & Conquistar, neste contexto, não é eficiente.

Programação Dinâmica resolve cada subproblema uma só vez e então grava sua resposta em uma tabela.

Projeto feito em 4 etapas:

1. Caracterizar a estrutura de uma solução ótima.

“Definir como podemos encontrar a melhor solução para o problema, encontrar um esquema para isto.”

1. Definir recursivamente o valor de uma solução ótima.

“Encontrar uma solução recursiva para o problema, pelo menos a sua equação de recorrência”

Projeto feito em 4 etapas:

3. Calcular o valor de uma solução ótima.

“Propor uma solução com Programação Dinâmica, baseada na solução recursiva, que seja capaz de encontrar o valor da melhor solução.”

1. Construir uma solução ótima a partir das informações calculadas.

“Propor um algoritmo, que seja capaz de montar a solução ótima, baseado nos resultados do algoritmo de P.D.” E(2; 10) T= 1 2 2 1 1 1 A=4 8 3 7 4 4 8 4 X=[1 5]

Programação: Resolve o problema

Passo 1: Define uma solução recursiva para solucionar o problema Passo 2: Cria solução recursiva como base para programação dinâmica Passo 3: Projetar estrutura de dados para resolver o problema do sol. Rec. Passo 4: Traduz o resultado alcançado na estrutura

2 4 8 3 7 1 10 4 4 8 4 5 1 1 1 2 2 1 E 1 E 2 S11 S22 F22 X2 X1 X2 23 26

(35)

k-> 1 2 3 4 5 6 7 8 9 10 11 12

Si 1 3 0 5 3 5 6 8 8 2 12 14

Fi 4 5 6 7 8 9 10 11 12 13 14 15

(36)

Funcao_SelAtiv(S,i,j) Int k[] = existe(S,i,j); Se(k==0) Retorna 0; Senão { Int valor=0; Int Maior=0;

Para w=1 ate <= Comp[k] faca {

Valor = SelAtiv(S,i,k[w]) + SelAtiv(S,k[w],j); Se(Valor > Maior) Maior = Valor; } Retorna Maior; } Funcao Existe(S,i,j) { Int V[];

Para wa i+1 ate j-1 faca

Se(S[1,w] >= S[2,i] && S[2,w] <= S[1,j]) V=V+{w}; Retorna V; } Algoritmo Principal(S) { S=S+{S[2]Tem(S)} S=S+{S[2]Tem(S)}

Int quant = SelAtiv(S,1,tam(S))

Escreva(Existe” + quant + “atividades”); }

SelAtivPD(S) {

Para i=1 ate tam(S) faca {

M[i,j] = 0; Se(i < tam(s))

M[i, i+1] = 0; }

Para l=3 ate tam(S) faca {

J = i + l-1; m[i,j] = 0;

Para k = i+1 ate j-1 faca {

Se(e compatível (i, k, j)) Valor R= }

}

(37)

Teoria da Intratabilidade

Foco no problema

Classificação de problema:

- Problema tratável: Estes problemas são resolvidos por algum algoritmo eficiente, que produz a solução em tempo polinomial.

- Problema intratável: São problemas que não podem ser resolvidos por nenhum algoritmo eficiente, ou seja, a solução deste problema gasta um tempo exponencial para ser encontrada.

Entretanto há um problema intratável que pode se tornar tratável se for desenvolvido um algoritmo que o resolva.

A grande maioria dos problemas intratáveis são aqueles no qual é possível identificar várias possíveis soluções e você precisa da melhor. Problemas de otimização.

Mesmo um algoritmo que encontra cada solução de forma eficiente pode ter uma complexidade exponencial, pois pode haver uma quantidade exponencial de possíveis soluções.

Tipos de Problemas

Problemas de decisão

O objetivo consiste em decidir se a resposta é sim ou não. Existe um caminho entre Rio Verde e Uberlândia.

O conjunto de atividade      5 7 , 8 4 , 5 1 é mutuamente compatível.

O objetivo é encontrar alguma estrutura que satisfaça um conjunto de propriedades, ou seja, encontrar uma solução para o problema.

Um caminho entre Rio Verde e Uberlândia.

Rio Verde -> Goiânia -> Brasília -> Caldas -> Uberlândia

Um conjunto de atividades mutuamente compatível

     10 9 , 8 5 , 4 1 O Problema da otimização

O objetivo é encontrar alguma estrutura que satisfaça dentro de possíveis soluções, a melhor delas esta escolhe de ser feita baseado em algum critério.

O melhor caminho entre Rio Verde e Uberlândia.

Rio Verde -> Bom Jesus -> Itumbiara -> Tupaciguara -> Uberlândia. O maior conjunto de atividades mutuamente compatível.

      12 11 , 11 8 , 8 5 , 5 3 , 3 1

Referências

Documentos relacionados

Para ambas as variáveis, germinação e classificação do vigor das plântulas houve diferença significativa (P&lt;0,05) entre as três cultivares de ervilha (‘Ervilha Jota Flor

A frio A quente Superficial Profunda Mistura a frio Mistura a quente Como base reciclada Como camada de ligação Como revestimento Agregados Cimento Portland e Cal Emulsão especial,

A exergia associada ao processo de transferência de calor é determinada pelo máximo trabalho que poderia se obter tendo o meio ambiente como referência. A exergia associada ao

O problema resultante P CER ´ e considerado a forma extensiva do programa estoc´ astico, pois explicita as vari´ aveis de decis˜ ao de segundo est´ agio para todos os cen´

Os suplementos mais utilizados, foram BCAA isoladamente; associado com maltodextrina; Whey Protein isolado e associado com BCAA e com maltodextrina (Nutrição) e Whey

-15º C 5x Cabo apto para as instalações domésticas para a ligação dos altifalantes dos equipamentos de música e para a difusão dos sinais de música por toda a

Junto com a eliminação de glicose, envolve diretamente a perda de eletrólitos e água ocorrendo poliuria, polidipsia, desidratação e hemoconcentração..

Segundo Quadrat, essas ações “visavam combater qualquer tentativa de integração das esquerdas.”31 Quadrat afirma ainda que, num primeiro momento, o Brasil não se mostrou