Você está na página 1de 7

Projeto e Análise de Algoritmos Exercícios

1) Prove que o seguinte algoritmo está correto:

function power(y, z)

x = 1

while z > 0 do

x

z

return(x)

= x.y = z - 1

Verificação por métodos invariantes:

propriedade invariante:

A variável x corresponde ao valor da exponencial a i , em que i é o número

de iterações do loop while (0 antes do loop).

Inicialização:

A variável x é inicializada com 1 = a 0 , sendo válido a propriedade acima.

Manutenção:

No final de cada loop x = x 0 * a = a (i 0 +1) = a i sendo i 0 o número de iterações do loop sem contar com a corrente (ou o número de multiplicações anteriores por a) e x 0 o valor de x antes do loop.

Término:

O processo finaliza quando b, que é decrementado em cada iteração, chega em 0. Em

outras palavras, o processo encerra depois de b iterações, e de acordo com a propriedade, x

encerra com o valor de a b , o que é o esperado para a exponenciação natural.

2) Prove que o seguinte algoritmo que computa o quadrado de um número está correto. Int SQR(Int n)

S = 0

I = 0

while i < n do

S = S + n

i = i + 1

return S

Verificação por métodos invariantes:

propriedade invariante:

A variável x corresponde ao valor do produto x * i, em que i é o número de iterações

do loop while (0 antes do loop).

Inicialização:

A variável x é inicializada com 0 = x * 0, sendo válido a propriedade

acima.

Manutenção:

A cada iteração, x é somado ao argumento a uma vez, ou seja , sendo i 0 o número

de iterações do loop sem contar com a corrente (ou somas anteriores de a), e modo que a propriedade é mantida. No final de cada loop x = x 0 + a = a * (i 0 + 1) = a * i sendo i 0 o número de iterações do loop sem contar com a corrente (ou o número de soma anteriores com a) e x 0 o valor de x antes do loop.

Término:

O

processo finaliza quando i, que é incrementado em cada iteração, chega em a . Em

outras palavras, o processo encerra depois de a iterações, e de acordo com a propriedade, x

encerra com o valor de a * a = a 2 , o que é o esperado.

3) Prove que o seguinte algoritmo que computa o fatorial de um número está correto. Int fatorial(Int n)

F = 1

i = 1

while i <= n do

F = F * i

i = i + 1

return F

Verificação por métodos invariantes: propriedade

invariante:

A variável x corresponde ao valor do fatorial i!, em que i é o número da atual

iteração do loop while (começando em 1).

Inicialização:

A variável x é inicializada 1 = 0 !, sendo válido a propriedade acima.

Manutenção:

A cada iteração, x é multiplicado por i, ou seja x = x * i = i 0 !* i = i!, sendo i 0 o

número de iterações passadas do loop, sem contar a presente, de modo que a propriedade é mantida.

Término:

O

processo finaliza quando i, que é incrementado em cada iteração, chega em a . Em

outras palavras, o processo encerra depois de a iterações, e de acordo com a propriedade, x

encerra com o valor de a!, o que é o esperado.

4) O algoritmo a seguir calcula a multiplicação de dois números naturais. Prove sua corretude. Int multiplica(y, z)

x = 0

while z > 0 do

if z é impar then

x = x + y

y = 2.y

z =z/2

return x

Algoritmo correto:

unsigned produto_corrigido(unsigned a, unsigned b)

{

 

if (b == 0) return 0;

x = a; resto = 0; while (b > 1)

 

{

 

if

(b%2) resto = resto + x;

x

= x * 2;

b

= b / 2;

 

}

return x+resto;

}

ou alternativamente na forma recursiva:

unsigned produto_recursivo( unsigned a, unsigned b)

{

if ( b == 0 ) return 0; if ( b == 1 ) return a; if ( b % 2 ) return a + produto_corrigido ( 2*a,b/2 ); else return produto_corrigido ( 2*a, b/2 );

}

A análise será feita a partir da forma direta, embora ambas formas são equivalentes, com a diferença que na forma direta a pilha (vide implementação da recursão em compiladores) é gerenciada manualmente usando a variável resto.

Explicação do algoritmo:

Esse algoritmo parte do pressuposto que multiplicar uma parcela por 2 e dividir a outra por 2 mantém o resultado na multiplicação. Por exemplo 2 * 12 = 4 * 6 = 8 * 3 ou genericamente a * b = (a * 2)(b = 2)

Para o caso de b ímpar (não divisível por 2), para manter o valor do produto constante, é necessário acrescentar o resto da divisão ao total (que segundo a conta abaixo equivale à constante a , ou o número sendo multiplicado por 2 pelo algoritmo):

a * b = (a * 2)(b = 2) = (a * 2)[(b ¡ 1)=2 + 1=2] = (a * 2)* (b ¡ 1)= 2 + a

em que (b-1)/2 = divisão inteira de um número ímpar (como ocorre em divisão de int em linguagem C).

Exemplo:

3 * 5 = 6 * 2 + 3 Exemplo completo:

3 * 15 = 6 * 7 + 3 = 12 * 3 + 6 + 3 = 24 * 1 + 12 + 6 + 3 = 24 + 21 = 45

propriedade invariante:

Como explicado acima, a propriedade invariante é que o produto x*b+resto é sempre

constante no final do loop e igual ao resultado correto da multiplicação.

Inicialização:

A variável b = 0 é considerada um caso especial, e o resultado correto é retornado

imediatamente. Para o caso de b > 0 , x é inicializado como a e o resto como 0, de modo

que a propriedade invariante x * b + resto = a * b + 0 = a * b que não viola a propriedade invariante.

Manutenção:

A

cada iteração, 2 situações podem ocorrer como visto na explicação:

b

par: b = b =2; x = x * 2; resto = re sto: mantém x * b + re sto constante, já

que como visto x * b = (x * 2)(b =2), e logicamente x * b + resto = (x * 2)(b =2) +

resto

b ímpar: b = (b ¡

1)= 2; x = x * 2; resto = resto + x (nota: ver sobre divisão

inteira em C acima): mantém x * b + resto constante, já que como visto x * b = (x * 2)* (b ¡ 1)= 2 + x, e logicamente

x * b + re sto = (x * 2)(b =2) + (resto +x);

Término:

O algoritmo encerra quando b = 1 (nota: sempre através de divisões inteiras

sucessivas o número chega a 1, a menos que b seja 0, o que é tratado de forma especial, ou b já seja 1, de modo que o loop encerra antes da primeira iteração).

Nessa situação, x * b + resto = x * 1 + resto = x + resto = a * b, de modo que o resultado correto da multiplicação é retornado.

5) Hoje, dos algoritmo de ordenação mais utilizados, o que possui menor complexidade é o inserção, considerando o seu melhor caso, O(n). Podemos dizer que nenhum outro algoritmo poderá atingir uma complexidade melhor do que esta? Justifique.

Sim. Para resolver

pertencentes a entrada sejam avaliados, ou seja, podemos dizer que

qualquer algoritmo proposto será no mínimo Ω(n).

os valores

o problema

é necessário que todos

6) Dois algoritmos A e B possuem complexidade n5 e 2n, respectivamente. Você utilizaria o algoritmo B ao invés do A. Em qual caso? Exemplifique.

Sim. Apesar do algoritmo ser exponencial, quando o valor de n é

pequeno, esta função produz um tempo de complexidade menor do que a

função do algoritmo A. Por exemplo para valores de n iguais 2

10

20

7) Considerando, que as chaves inseridas em um Hashing não provocaram colisão. Independente do algoritmo, podemos dizer que a pesquisa por uma chave na tabela será de ordem de complexidade constante, ou seja, O(1)?

Sim. O algoritmo é ótimo, pois o acesso à chave é direto.

8) Para duas funções g(n) e f(n) temos que f(n)=θ(n) se somente se f(n)= Ω(g(n)) e f(n)=O(g(n)). Explique o teorema acima.

A notação teta indica que a função f(n) está entre um limite superior e

um limite inferior. Se f(n) é teta de g(n) que dizer que f(n) é tem limite superior em g(n) se somente se f(n)<= g(n)* c1, onde c1 é uma

constante qualquer para n>m, ou seja f(n)= O(g(n)). O mesmo é válido para notação Ω.

9) Uma outra métrica muito utilizada para avaliar algoritmos e a Métrica Empírica. Essa consiste em escolher uma métrica (tempo, número de instruções executadas, etc), propor entradas diferenciadas (geralmente com alguma característica pré-definida), ou seja, amostras. Finalmente executar o algoritmo com as entradas e analisar os resultados. Esta medida e muito utilizada para comparar dois algoritmos. Critique a métrica.

O problema desta medida é a definição da amostra e como serão avaliados

os parâmetros. Por exemplo, se utilizarmos a função time para calcular o tempo, podemos obter valores diferentes para sistemas operacionais

diferentes. Ainda, deve-se pensar que o cálculo do tempo não pode incluir

o tempo em que o processo não esteve escalonado. Porém, este método é

muito interessante quando se deseja comparar dois ou mais algoritmo, quando se deseja saber o comportamento do algoritmo para determinadas bases de dados.

10) Por muitas vezes damos atenção apenas ao pior caso dos algoritmos. Explique o porque.

Porque normalmente desejamos saber qual o limite máximo gasto para executar o um determinado algoritmo.

11) Analise a complexidade do algoritmo abaixo Leia(n); x 0 Para i 1 até n faça Para j i+1 até n faça Para k 1 até j-i faça xx + 1

12) Projete o algoritmo mais eficiente que você conseguir para encontrar a mediana de um vetor de n elementos. Análise sua complexidade.

Ordene os elementos e retorne o elemento n/2 da lista ordenada

Complexidade: O(n log n)

13) Indique se as afirmativas a seguir são verdadeiras e justifique sua resposta:

a) 2n+1 = O(2n) É verdade. Para que 2n+1 pertença a Ο(2n), é preciso achar uma constante c tal que, para algum valor de m, a seguinte desigualdade seja verdadeira:

2n+1 c.2n , para n ≥ m Notamos que a potência 2n+1 é equivalente a 2.2n, fornecendo c=2

b) 22n = O(2n) Falso. Para que 22n pertença a Ο(2n), é preciso achar uma constante c tal que, para algum valor de m, a seguinte desigualdade seja verdadeira:

22n c.2n , para n ≥ m

Uma vez que 22n equivale a 2n.2n, sempre haverá um valor de n maior do que qualquer constante c que possamos escolher. Assim, 2n não é um limite assintótico superior para 22n.

14 ) Um problema que está relacionado com o problema de ordenação é o de encontrar o k-ésimo menor elemento de uma lista não ordenada.

a. Escreva um algoritmo que encontra o k-ésimo menor elemento.

b. Argumente que seu algoritmo está correto.

c. Efetue a análise de complexidade de sua solução para melhor caso e pior caso.

15) João era um aluno de GCC111 que gostava de implementar e testar os algoritmos vistos em aula. Ele percebeu que o algoritmo InsertionSort (Ordena-Por-Inserção) era bem eficiente para vetores com poucos elementos. Ele implementou então o seguinte algoritmo:

Algoritmo-do-João(A,n) se n =< 100 então InsertSort(A,n) senão Mergesort(A,n) Pedro, um colega de João sabia que o InsertionSort tinha complexidade de pior caso θ(n2) e concluiu que o algoritmo do João tinha complexidade θ(n2). João, por outro lado, afirmou que seu algoritmo tinha complexidade θ (n lg n). Quem está certo? Por quê?

16) Mostre que a solução da recorrência T(n) = T(n/2) + 1 pertence a O(lg n).

17) Mostre que a solução de T(n) = 2T(n/2) + n é θ(n lg n).

18) Mostre que a solução de T(n) = T(n/2) + T(n/2) + n é Ω(n lg n)

19) Resolva a recorrência T(n) = T(n/3) + T(2n/3) + 1 pelo método de substituição.

20) Determine um bom limite assintótico (O) da recorrência T(n) = 3T(n/2)+n

21) Argumente que a solução da recorrência T(n) = T(n/3) + T(2n/3) + n é Ω (n lg n) usando o método da árvore de recorrência.

22) Use a árvore de recorrência para T(n) = 4T(n/2) + n e obtenha a classe θ a qual a solução

pertence.

23) Use o Teorema Master para resolver as recorrências abaixo.

(a)

T(n) = 4T(n/2) + n

Para saber se o Teorema Mestre pode ser aplicado ou não, temos que ter as constantes a _ 1 e b > 1 e a função f(n) assintoticamente positiva. Temos a = 4; b = 2; f(n) = n e as três condições são satisfeitas.

Deve-se analisar se a equação de recorrência cai em um dos três casos previstos do teorema ou não. Para isso, devemos comparar a função f(n) com a função nlogb a, ou seja:

 

f(n) : nlogb a

n

: nlog2 4

n

: n2:

 

A

função nlogb a domina a função f(n) por um fator polinomial n1. Assim,

o

caso 1 do Teorema Mestre se aplica e temos que T(n) = _(n2).

(b)

T(n) = 4T(n/2) + n2

Para saber se o Teorema Mestre pode ser aplicado ou não, temos que ter as constantes a _ 1 e b > 1 e a função f(n) assintoticamente positiva. Temos a = 4; b = 2; f(n) = n2 e as três condições são satisfeitas.

Deve-se analisar se a equação de recorrência cai em um dos três casos previstos do teorema ou não. Para isso, devemos comparar a função f(n) com a função nlogb a, ou seja:

f(n) : nlogb a

n2 : nlog2 4

n2 : n2:

As duas funções f(n) e nlogb a têm a mesma taxa de crescimento. Assim, o

caso 2 do Teorema Mestre se aplica e temos que T(n) = _(n2 log n).

(c) T(n) = 4T(n/2) + n3

Para saber se o Teorema Mestre pode ser aplicado ou não, temos que ter

as constantes a >= 1 e b > 1 e a função f(n) assintoticamente positiva. Temos a = 4; b = 2; f(n) = n3 e as três condições são satisfeitas.

Deve-se analisar se a equação de recorrência cai em um dos três casos previstos do teorema ou não. Para isso, devemos comparar a função f(n) com a função nlogb a, ou seja:

f(n) : nlogb a n3 : nlog2 4 n3 : n2:

A

função f(n) domina a função nlogb a por um fator polinomial n1. Assim,

o

caso

3

do

Teorema

Mestre

pode ser

aplicado

se

a condição

de

“regularidade” for satisfeita, ou seja, af(nb ) cf(n) para uma constante c < 1.

af(n/b ) <= cf(n) 4(n/2 )³ <= cn³ 4n³/8 <= cn³ 1/2n³ <= cn³:

A inequação é satisfeita para c = 1/2 . Portanto, o caso 3 do Teorema

Mestre se aplica e T(n) = Teta(n³).

24) Determine o limite assintótico de:

a) T(n) = 3T(n/2)+n

b) T(n) = 3T(n/3)+n log n

c) T(n) = 2T(n/5)+n

d) T(n) = 2T(n/2)-n