Você está na página 1de 7

Análise e Complexidade de Algoritmos

Aula 2
Prof. Sergio Jeferson Rafael Ordine
12 de Fevereiro, 2011

1 Analisando um Algoritmo de forma mais


sistemática
Analisaremos o tempo de execução do algoritmo abaixo:
Algoritmo 1: Ordenação por Inserção (Insertion Sort)
Entrada:
v Vetor com números inteiro
n tamanho do vetor
Saı́da: vetor v ordenado

1 inı́cio
2 para j ← 2 a n faça
3 elementoAtual ← v[j]
4 i←j−1
5 enquanto i>0 E v[i]>elementoAtual faça
6 v[i + 1] ← v[i]
7 i←i−1
8 fim
9 v[i + 1] ← elementoAtual
10 fim
11 fim
Este algoritmo percorre o vetor desde sua segunda posição até a última,
armazenando o elemento desta posição. Em seguida o laço nas linhas 5 a
8 ”empurra”os elementos maiores que ele uma posição a frente e por fim
insere o elemento na posição correta (por isso seu nome).Como o vetor é
ordenado posição a posição sempre teremos um vetor parcialmente ordenado

1
e ao empurrarmos para frente todos os elementos maiores que o elemento j
isto nos garante que mantemos a ordenação a cada passo.
Este procedimento é muito similar a forma como arrumamos um jogo de
cartas na mão em alguns jogos de baralho.
Para analisarmos este algoritmo adotaremos um método bem mais siste-
mática que o adotado na aula passada. Vamos considerar que cada comando
tem um custo de execução (para executá-lo uma vez) e que este comando
será executado determinado número de vezes. Com isto teremos a seguinte
lista de custos/tempo:

Linha Custo Tempo


2 c1 n
3 c2 n−1
4 c3 P−
n
n
1
5 c4 j=2 tj
Pn
6 c5 j=2 tj − 1
Pn
7 c6 j=2 tj − 1

8 c7 n−1

Tabela 1: Custo e Número de Execuções por comando - Insertion Sort

Vamos entender o por que destes números:

• Os comandos de repetição para e enquanto executam para todos os


elementos necessários e mais uma última vez quando a condição se
torna inválida. Por isso ele executa uma vez a mais que o tamanho do
laço. Isto faz com que o comando para na linha 2 execute n vezes (o
loop tem tamanho n − 1 pois inicia na posição 2 do vetor).

• Os comandos dentro do loop executam as n − 1 vezes que esperamos

• No caso do loop da linha 5, o número de execuções, a cada passo,


depende da posição dos números no vetor. Como isto é totalmente
dependente do vetor de entrada, vamos definir um número tj que indica
quantas vezes o comando enquanto será executado para aquele elemento
especı́fico no vetor. Para obtermos o número total de vezes que esse
comando é executado somamos o número de execuções feitas quanto o
elemento 2 foi tratado (t2 ) com o número de vezes quando o elemento
3 foi tratado (t3 ) e assim por diante até o elemento n (tn ). Isto nos dá
a somatória que vemos na tabela acima.

2
• Os comandos dentro deste laço executam uma vez a menos que o pró-
prio laço (lembrando que contamos o caso em que a condição se torna
inválida). Isto nos dá a somatória onde subtraimos 1 de cada elemento
tj para representar este fato.

Se somarmos todos os termos acima teremos:

n
X n
X n
X
T (n) = c1 n+c2 (n−1)+c3 (n−1)+c4 tj +c5 (tj −1)+c6 (tj −1)+c7 (n−1)
j=2 j=2 j=2

Para calcularmos este valor vamos pensar inicialmente no melhor caso,


que será o vetor ordenado de forma crescente. Neste caso o laço interno
não precisará ser executado para empurrar os elementos. Sendo assim temos
t2 = t3 = ... = tn = 1 (apenas Pa primeira verificação é feita, e o laço não é
executado). Isto faz com que nj=2 tj = n − 1 e nj=2 (tj − 1) = 0. O que nos
P
leva a:

T (n) = c1 n + c2 (n − 1) + c3 (n − 1) + c4 (n − 1) + c7 (n − 1)

T (n) = c1 n + c2 n − c2 + c3 n − c3 + c4 n − c4 + c7 n − c7

T (n) = (c1 + c2 + c3 + c4 + c7 )n − (c2 + c3 + c4 + c7 )

T (n) = an − b
Como todos os custos são constantes, sua soma foi substituida pelas cons-
tantes a e b e vemos que este algoritmo executa, no melhor caso, de acordo
com uma função linear ao tamanho de sua entrada.
Por outro lado, o pior caso seria aquele em que mais trocas são feitas para
posicionar corretamente cada elemento. Isto ocorre no caso do vetor estar
inicialmente ordenado de forma decrescente.
Neste caso, tj = j, pois temos que realizar j −1 trocas para cada elemento
e mais uma execução quando a condição se torna inválida. Assim sendo
t2 = 2, t3 = 3, ... tn = n. Considerando isto temos que as somatórias
assumirão os seguintes valores:
n
X n(n − 1)
j= −1
j=2
2

3
n
X n(n − 1)
(j − 1) =
j=2
2

O que nos leva a:

n(n − 1) n(n − 1) n(n − 1)


T (n) = c1 n+c2 (n−1)+c3 (n−1)+c4 ( −1)+c5 ( )+c6 ( )+c7 (n−1)
2 2 2

c4 c5 c6 2 c4 c5 c6
T (n) = ( + + )n + (c1 + c2 + c3 − − − + c7 )n − (c2 + c3 + c4 + c7 )
2 2 2 2 2 2

T (n) = an2 + bn + c
No pior caso, então, este algoritmo roda em um tempo quadrático em
relação ao tamanho do vetor de entrada.
Como falamos de algoritmos esta função tem como domı́nio os números
inteiros (pois não existe entrada não inteira para o algoritmo), porém pode-
mos estender este conceito (mantendo alguma atenção em situações onde isto
pode gerar problemas) para que o domı́nio da função seja os números reais.
Com isto podemos utilizar as notações abaixo.

2 Notação Assintótica
Se olharmos a função an2 + bn + c vemos que quanto maior n se torna, menor
será a parcela no valor total representada pelos valores de bn e c, pois o termo
quadrático cresce de forma muito mais rápida.
Como em geral estamos interessados em uma ordem de grandeza de uso
de um recurso (em geral tempo de execução) de um algoritmo, uma análise
detalhada como a que fizemos acima acaba sendo muito trabalhosa e pouco
prática. Queremos um método mais rápido para achar um termo ”domi-
nante”da função, quando temos um valor de entrada suficientemente grande.
A notação assintótica nos fornece exatamente isto: um ferramental mate-
mático para considerar apenas o ”termo dominante”de uma função, descon-
siderando constantes que o multiplicam e termos menos significativos.
Assintótico significa ”linha que se aproxima indefinidamente de uma curva
sem jamais cortá-la”.

4
2.1 Notação Θ
Dada uma função f (n), denotamos por Θ(g(n)) o conjunto de funções que
satisfaz o seguinte critério:

Θ(g(n)) = {f (n) : existem constantes c1 , c2 e n0 de tal f orma que


c1 g(n) ≤ f (n) ≤ c2 g(n) para todo n ≥ n0 }

Isto significa que, conseguimos, dada uma função em Θ(g(n)), multiplicá-


la por duas contantes diferentes de forma a ”cercarmos”por cima e por baixo
um conjunto de funções f (n) para qualquer número suficientemente grande
(ou seja, maior que n0 ).
Considere que temos a função f (n) = 3n2 + 2n. Considerando g(n) = n2 ,
temos que:

c1 n2 ≤ 3n2 + 2n ≤ c2 n2 =
2
c1 ≤ 3 + ≤ c2
n
O termo n2 é menor ou igual a um para n ≥ 2 e diminui quanto mais o
valor de n aumenta. Logo se assumirmos c1 = 2 e c2 = 4 a expressão acima
é válida para todo n ≥ n0 = 2 . Como isto é verdade, podemos dizer que
f (n) ∈ Θ(n2 ()). Em geral usamos a notação f (n) = Θ(n2 ()). Veremos que
esta notação pode ser útil em algumas situações.

2.2 Notação O
A notação Θ(g(n)) denota um limite extrito. Podemos aplicar este conceito
também para um limite superior. De forma similar, existe a notação O(g(n))
(em inglês Big-Oh):

O(g(n)) = {f (n) : existem constantes c e n0 de tal f orma que


0 ≤ f (n) ≤ cg(n) para todo n ≥ n0 }

Esta notação é muito útil, pois muita vezes permite definirmos um limite
superior para o tempo de execução de um algoritmo meramente olhando para
sua estrutura. No caso do Insertion Sort, como temos dois laços aninhados
que podem no pior caso percorrer quase todo o vetor, temos que o tempo de
execução é limitado por O(n2 ).

5
2.3 Notação Ω
A notação Ω(g(n)) indica um limite inferior para um conjunto de funções.
Podemos defini-la como:

Ω(g(n)) = {f (n) : existem constantes c e n0 de tal f orma que


0 ≤ cg(n) ≤ f (n) para todo n ≥ n0 }

Existe uma analogia entre os conjuntos Ω(g(n)), Θ(g(n)) e O(g(n)) e as


operações de comparação ≤, = e ≥ respectivamente.

2.4 Notação Assintótica em Equações e Inequações


Conforme dito anteriormente podemops usar a notação mais ”relaxada”f (n) =
Θ(n2 ()) para as notações Ω(g(n)), Θ(g(n)) e O(g(n)). De mesma forma po-
demos utilizar algo como 5n2 + 3n + 2 = 5n2 + Θ(n). Como isto pode ser
interpretado?
Quando utilizamos as notações assintóticas em equações e inequações per-
demos um grau de precisão (pois a notação assintótica ignora constantes mul-
tiplicativas e termos menos significativos). Com isto, quando usamos o Θ(n)
na equação 5n2 + Θ(n) ele representa uma ”função anônima”que tem ordem
Θ(n). Não nos interessa saber exatamente qual função ela é (e no caso seria
3n + 2), apenas sua ordem de crescimento.
Tal notação será muito útil para eliminarmos termos não essenciais de
nossa análise e, com isto, simplificá-la.
Muitas vezes podemos ver a notação assintótica também do lado esquerdo
da equação. Um exemplo seria:

2n2 + Θ(n) = Θ(n2 )


Esta equação pode ser lida como: ”independente de qual função anônima
de ordem n seja escolhida do lado esquerdo da equação, sempre será possı́vel
encontrar uma outra função anônima de ordem n2 para o lado direito que
satisfaça a igualdade”.
Isto nos indica que poderiamos encadear as equações que vimos e termos
algo como:

5n2 + 3n + 2 = 5n2 + Θ(n) = Θ(n2 )

6
3 Lista de Exercı́cios
1. Após analisar um algoritmo identificou-se que seu tempo de execução
respeita a função 7n + 4, onde n é o tamanho da entrada. Podemos
dizer que este algoritmo tem tempo de execução O(n2 )? Explique.

2. Ainda de acordo com o exercı́cio anterior, podemos dizer que o tempo


de execução é Θ(n2 )? Explique.

3. Sejam f (n) e g(n) duas funções assintoticamente não-negativas. Usando


a definição da notação Θ, prove que max(f (n), g(n)) = Θ(f (n)+g(n)).

4. É verdade que 2n+1 = O(2n )? E que 22n = O(2n ) ?

5. Explique com suas palavras porque a frase “O tempo de execução do


algoritmo X é , no mı́nimo, O(n2 )” não faz sentido.

6. Prove que o tempo de execução de um algoritmo é f (n) = Θ(g(n)) se


e somente se f (n) = O(g(n)) e f (n) = Ω(g(n)).

7. Considere que você possa utilizar os algoritmos A e B, com tempo de


execução dado pelas funções n5 e 2n respectivamente. Você utilizaria o
algoritmo B ao invés do A em algum caso? Explique e exemplifique.

8. Diga se as expressões abaixo são verdadeiras ou falsas:

(a) 7 = O(n)
(b) 7n = Θ(n)
(c) 7 = Θ(n)
(d) 3x + 2 = Ω(x)
(e) n2 = O(2n )
(f) 2n = O(n2 )
n(n+1)
(g) 2
= Ω(n)

(h) n = O(n)

Você também pode gostar