2. Notação O, Ω e Θ
●
São utilizadas para denotar o
comportamento assintótico de uma função
3. Notação O, Ω e Θ
●
Notação O
– É usada para determinar um limite superior
assintótico.
●
f(n) O(g(n)) →
∈
●
f(n)∈{h(n) | c,N
∃ ∈ℝ: ∀n≥N: h(n) ≤
cg(n)}
●
f(n) é limitada superiormente por g(n)
4. Notação O, Ω e Θ
●
Também usa-se f(x) = O(g(x))
●
Assim, também podemos afirmar que f(x)
= O(g(x)) se e somente se a
∃ ∈ℝ, a ≥ 0:
lim
x→∞
f ( x)
g(x)
≥a
5. Notação O, Ω e Θ
●
Observe que este limite pode ser igual a 0.
6. Notação O, Ω e Θ
●
Exemplos:
– 5n2
+15∈O(n2
) → n
∀ ≥4: 5n2
+15 ≤ 6n2
(c=6, N=4)
– 5n2
+15∈O(n3
) → n
∀ ≥6: 5n2
+15 ≤ n3
(c=1, N=6)
– n2
∉ O(n) → c,N
∄ ∈ℝ: n
∀ ≥N n2
≤ cn
●
Suponha c,N, tais que n
∃ ∀ ≥N n2
≤ cn. Então n
∀ ≥N n2
-
cn ≤ 0. As soluções para n são 0 ≤ n ≤ c. Como n está
limitado entre 0 e c, ele não pode ser maior que
qualquer N. Contradição!
8. Notação O, Ω e Θ
●
Notação Ω
– É usada para determinar um limite inferior
assintótico.
●
f(n) Ω(g(n)) →
∈
●
f(n)∈{h(n) | c,N
∃ ∈ℝ: n
∀ ≥N: h(n) ≥ cg(n)}
●
f(n) é limitada inferiormente por g(n)
9. Notação O, Ω e Θ
●
Também usa-se f(x) = Ω(g(x))
●
Assim, também podemos afirmar que f(x)
= Ω(g(x)) se e somente se a
∃ ∈ℝ, 0 < a
≤ ∞:
lim
x→∞
f ( x)
g(x)
=a
10. Notação O, Ω e Θ
●
Observe que este limite não pode ser igual
a 0.
11. Notação O, Ω e Θ
●
Exemplos:
– 5n2
+15∈Ω(n) → n
∀ ≥4: 5n2
+15 ≥ n (c=1, N=4)
– 5n2
+15∉Ω(n3
) → c,N
∄ ∈ℝ: n
∀ ≥N 5n2
+15 ≥
cn3
13. Notação O, Ω e Θ
●
Notação Θ
– É usada para determinar uma igualdade
assintótica.
●
f(n)∈Θ(g(n)) s.s.s. f(n)∈O(g(n)) e
f(n)∈Ω(g(n))
●
f(n) é assintoticamente equivalente a g(n)
14. Notação O, Ω e Θ
●
Também usa-se g(n) = Θ(f(n))
●
Assim, também podemos afirmar que f(x)
= Ω(g(x)) se e somente se a
∃ ∈ℝ, 0 < a
< ∞:
lim
x→∞
f ( x)
g(x)
=a
15. Notação O, Ω e Θ
●
Exemplos:
– 5n2
+15∈Θ(n2
)
– 4n+1∈Θ(n)
17. Propriedades
●
Seja k e p constantes e n variável.
– k O(np
) = O(np
)
– n O(nk
) = O(nk+1
)
– O(nk
) + O(np
) = O(nmax(k,p)
)
●
Ex.: O(n) + O(n4
) = O(n4
)
– O(f(n)) + O(g(n)) = O(max(f(n),g(n)))
●
Ex.: O(log(n)) + O(n) = O(n)
●
Similar para Θ.
●
Para Ω troca-se max por min.
19. Referência
●
HOPCROFT, John E.; ULLMAN, Jeffrey
D.; MOTWANI, Rajeev. Introdução à teoria
de autômatos, linguagens e computação.
[Rio de Janeiro]: Campus, 2003.
●
Imagens da versão em inglês
21. Motivação
●
Computadores usam recursos como tempo e espaço.
●
Diferentes algoritmos podem resolver um mesmo
problema, mas é preferível aquele que use menos
recursos.
●
A análise deve ser independente da máquina, pois
operações podem levar tempos diferentes em diferentes
máquinas.
– CISC: uma soma com três operandos pode custar apenas um
ciclo.
– RISC: precisa-se mover os valores da memória para
registrador antes de somar.
22. Tipos de análise
●
Melhor caso
– Considera apenas o caso com menor custo.
●
Pior caso
– Similar, mas para o maior custo.
●
Caso médio
– Considera a média de todos os casos
considerando a probabilidade de cada um
deles ocorrer.
E(X)=∑
i=1
n
P( X=xi) xi
24. Exemplo 1
SWAP(x,y)
1 t = x
2 x = y
3 y = t
custo
c1
c2
c3
vezes
1
1
1
ci é constante (possivelmente diferente para cada i).
25. Exemplo 1
●
As constantes poderiam ser diferentes a
depender da máquina ou implementação.
– Se x, y e t são registradores, c1 = c2 = c3 = 1.
– Caso x, y e t estejam na memória RAM, c1 = c2
= c3 = 2.
●
Custo total é
c1 + c2 + c3 = Θ(1).
26. Exemplo 2
MIN(A)
1 min = A[1]
2 for i = 2 to A.length
3 if min > A[i]
4 min = A[i]
5 return min
●
Seja n o tamanho do vetor A.
27. Exemplo 2
MIN(A)
1 min = A[1]
2 for i = 2 to A.length
3 if min > A[i]
4 min = A[i]
5 return min
custo
c1
c2
c3
c4
c5
vezes
1
n
n-1
tA
1
tA é o número de vezes que a linha 4 é
executada para um dado vetor A.
28. Exemplo 2
●
Os testes dos comandos for ou while
rodam uma vez a mais antes de encerrar.
●
O custo total é
c1 + c2n + c3(n-1) + c4tA + c5
●
O pior caso ocorre quando tA = n-1.
– Ex.: A = [4 3 2 1]
●
O custo de pior caso é
●
c1 + c2n + c3(n-1) + c4(n-1) + c5 = Θ(n)
29. Exemplo 2
●
O melhor caso ocorre quando tA = 0.
– Ex.: A = [1 2 3 4]
●
O custo de melhor caso é
c1 + c2n + c3(n-1) + c5 = Θ(n)
●
O custo de caso médio é obtido pelo
valor esperado de tA, E[tA].
30. Exemplo 2
●
Considere Xi o evento que é 1 caso na
iteração i do algoritmo, o valor de min seja
modificado:
Xi=
{1,se min> A[i],
0, se min≤A[i].
36. tj é o número de vezes que o teste do while na
linha 5 é executado para um dado valor j do for
na linha 1.
37. Exemplo 3
●
O custo total é
c1 n+c2(n−1)+c4(n−1)+c5∑
j=2
n
t j+c6 ∑
j=2
n
(t j−1)
+c7∑
j=2
n
(t j−1)+c8(n−1)
●
Melhor caso para tj = 1 com A ordenado:
c1 n+c2(n−1)+c4 (n−1)+c5(n−1)+c8(n−1)=Θ(n)
38. Exemplo 3
●
O pior caso com tj = j para A em ordem decrescente:
c1 n+c2(n−1)+c4(n−1)+c5 ∑
j=2
n
j+c6 ∑
j=2
n
( j−1)
+c7 ∑
j=2
n
( j−1)+c8(n−1)=
c1 n+c2(n−1)+c4(n−1)+c5(n(n+1)
2
−1)+c6(n(n−1)
2 )
+c7(n(n−1)
2 )+c8(n−1)=Θ(n2
)
39. Exemplo 3
●
O caso médio utiliza-se E[tj]. Para cada j, tj
pode ser um valor de 1 a j, com igual
probabilidade, 1/j.
1 2 j-1 j
... key
E[t j]=1
1
j
+2
1
j
+...+ j
1
j
=∑
t=1
j
1
j
t=
j+1
2
40. Exemplo 3
●
Usando E[tj] no custo total, temos:
c1 n+c2(n−1)+c4(n−1)+c5∑
j=2
n
E[t j ]+c6 ∑
j=2
n
(E[t j]−1)
+c7 ∑
j=2
n
(E[t j ]−1)+c8(n−1)=
c1 n+c2(n−1)+c4(n−1)+c5(n(n−1)
4
+n−1)+c6(n(n−1)
4 )
+c7(n(n−1)
4 )+c8(n−1)=Θ(n
2
)
41. Análise simplificada
●
Melhor e pior casos podem ser analisados
de forma simplificada em alguns casos
como no exemplo a seguir.
42. Exemplo 1 (melhor caso)
MIN(A)
1 min = A[1]
2 for i = 2 to A.length
3 if min > A[i]
4 min = A[i]
5 return min
tempo
Θ(1)
Θ(n)
Θ(n)
0
Θ(1)
Total é 2Θ(n)+2Θ(1) = Θ(n)
43. Exemplo 1 (pior caso)
MIN(A)
1 min = A[1]
2 for i = 2 to A.length
3 if min > A[i]
4 min = A[i]
5 return min
tempo
Θ(1)
Θ(n)
Θ(n)
Θ(n)
Θ(1)
Total é 3Θ(n)+2Θ(1) = Θ(n)
44. Exemplo 2 (melhor caso)
tempo
Θ(n)
Θ(n)
0
Θ(n)
Θ(n)
0
0
Θ(n)
Total é 5Θ(n) = Θ(n)
45. Exemplo 2 (pior caso)
tempo
Θ(n)
Θ(n)
0
Θ(n)
Θ(n2
)
Θ(n2
)
Θ(n2
)
Θ(n)
Total é 3Θ(n2
)+4Θ(n) = Θ(n2
)
46. Custos escondidos
●
As notações O, Ω e Θ podem esconder
custo úteis para comparação entre
algortimos de mesma ordem de
complexidade.
●
No exemplo a seguir, ambos os algoritmos
são Θ(n), mas um é Θ(2n) enquanto que o
outro é Θ(1,5n) (ou seja, mais eficiente).
47. Exemplo
MINMAX1(A)
1 min = max = A[1]
2 for i = 2 to A.length
3 if min > A[i]
4 min = A[i]
5 else if max < A[i]
6 max = A[i]
7 return (min, max)
48. MINMAX2(A)
1 if A.length mod 2 ≠ 0
2 min = max = A[1]
3 i = 2
4 else
5 if A[1] < A[2]
6 min = A[1]
7 max = A[2]
8 else
9 min = A[2]
10 max = A[1]
11 i = 3
12 while i ≤ A.length
13 if A[i] < A[i+1]
14 if A[i] < min
15 min = A[i]
16 if A[i+1] > max
17 max = A[i+1]
18 else
19 if A[i+1] < min
20 min = A[i]
21 if A[i] > max
22 max = A[i+1]
23 i = i + 2
24 return (min, max)
49. Exercício
●
Calcule o custo de tempo total de
execução dos algoritmos MINMAX1 e
MINMAX2. Qual é o custo no melhor e no
pior caso?
50. Complexidade de espaço
●
É similar a de tempo, mas as colunas
custo e vezes são substituídas pelo
consumo de memória.
●
Criação de novas variáveis tem custo
constante, mas utilização tem custo zero.
51. Exemplo
MIN(A)
1 min = A[1]
2 for i = 2 to A.length
3 if min > A[i]
4 min = A[i]
5 return min
custo
c1
c2
0
0
0
vezes
1
1
0
0
0
52. Exemplo 1
●
Custo de espaço é c1+c2=Θ(1).
●
Se min e i são inteiros de 8 bit, então o
custo de espaço da função MIN é de
apenas 16 bits.
●
Observe que foi assumido que A foi
passado previamente sem necessitar
armazenar novamente.
64. Mergesort
●
tL é a quantidade de vezes que um
elemento de L foi selecionado.
●
Custo total do MERGE é
7Θ(1) + Θ(n1+1) + Θ(n1) + Θ(n2+1) + Θ(n2) +
Θ(r-p+2) + Θ(r-p+1) + 2Θ(tL) + 2Θ(r-p+1-tL) =
Θ(1) + Θ(n1) + Θ(n2) + Θ(r-p+2) + Θ(r-p+1) +
2Θ(r-p+1) =
Θ(1)+Θ(r-p+1)+Θ(r-p+1) = Θ(r-p+1),
visto que n1 + n2 = q – p + 1 + r – q = r – p + 1.
66. Mergesort
●
Se p ≥ r, temos o caso base T(i) = Θ(1)
para qualquer i ≤ 1.
●
Tipicamente, p = 1 e r = n = |A|, assim:
T(n) = T(r-p+1)
T(n) = 2Θ(1) + T(q-p+1) + T(r-(q+1)+1) + Θ(n)
T(n) = T( (n+1)/2 )+T(n-
⌊ ⌋ (n+1)/2 )+Θ(n)+Θ(1)
⌊ ⌋
T(n) = T( n/2 )+T(
⌈ ⌉ n/2 )+Θ(n),
⌊ ⌋
●
Abusando a notação, para simplificar:
T(n) = 2 T(n/2) + Θ(n), se n > 1.
67. Teorema mestre
Seja a ≥ 1 e b > 1, f(n) uma função e T(n)
definido sobre inteiros não negativos pela
equação recursiva
T(1) = Θ(1) e
T(n) = aT(n/b) + f(n) se n > 1.
onde n/b pode ser interpretado como n/b
⌊ ⌋
ou n/b . Então,
⌈ ⌉
68. Teorema mestre
1) Se f(n) = O(nlog
b
a-ε
) para ε > 0, então
T(n) = Θ(nlog
b
a
).
2) Se f(n) = Θ(nlog
b
a
), então
T(n) = Θ(nlog
b
a
log2n).
3) Se f(n) = Ω(nlog
b
a+ε
) para ε > 0 e se af(n/b) ≤
cf(n) para alguma constante c < 1 e n
suficientemente grande,
T(n) = Θ(f(n)).
69. Interpretação
●
Comparamos f(n) com nlog
b
a
.
– A maior delas determina a solução da
recursão.
●
Se elas são “do mesmo tamanho”,
adicione um fator logaritmo na solução.
70. Interpretação
●
Maior (menor) no sentido polinomial: maior
(menor) que nlog
b
a
por um fator nε
.
– Existem possibilidades entre os casos 1 e 2 e
os casos 2 e 3 que não são tratados no
teorema e não podem ser aplicados.
71. Exemplo 1
T(n) = 9T(n/3) + n.
●
a = 9, b = 3, f(n) = n, nlog
b
a
= nlog
3
9
= n2
.
●
f(n) = n = O(n2-1
) = O(nlog
3
9-1
) = O(nlog
a
b-ε
)
com ε = 1.
●
Pelo caso 1:
T(n) = Θ(nlog
b
a
) = Θ(n2
).
72. Exemplo 2
T(n) = T(2n/3) + 1.
●
a = 1, b = 3/2, f(n) = 1, nlog
b
a
= nlog
(3/2)
1
=
n0
=1.
●
f(n) = 1 = Θ(1) = Θ(n0
) = Θ(nlog
(3/2)
1
).
●
Pelo caso 2:
T(n) = Θ(nlog
b
a
log2n) = Θ(log2n).
73. Exemplo 3
T(n) = 3T(n/4) + n log2n.
●
a = 3, b = 4, f(n) = n log2n, nlog
b
a
= nlog
4
3
= O(n0,793
).
●
f(n) = n log2n = Ω(n) e n = Ω(n0,993
) =
Ω(n0,793+0.2
)=Ω(nlog
4
3+0.2
)=Ω(nlog
a
b+ε
) com ε=0,2.
●
Para n grande, a f(n/b) = 3(n/4)log2(n/4) ≤ (3/4)n
log2n = c f(n) para c = 3/4.
●
Pelo caso 3:
T(n) = Θ(f(n)) = Θ(n log2n).
74. Exemplo 4
T(n) = 2T(n/2) + n log2n.
●
a = 2, b = 2, f(n) = n log2n, nlog
b
a
= nlog
2
2
= n.
●
f(n) = n log2n = Ω(n) porém não existe ε >
0 tal que
f(n) = n log2n = Ω(nlog
b
a+ε
) = Ω(n1+ε
)
●
pois log2n = O(nε
) para qualquer ε > 0.
●
Este exemplo está entre os casos 2 e 3.
76. Exercícios
1) Use o teorema mestre para obter o custo
de tempo e espaço do mergesort.
2) Calcule a complexidade de tempo e
espaço do quicksort no melhor e pior caso.
81. Pré-condição e pós-
condição
●
São proposições lógicas que devem ser
satisfeitas antes e depois da execução de
um comando.
●
Notação:
{P} Pré-condição
S Comando
{Q} Pós-condição
82. Exemplos
{0≤a∧0≤b}
z = a*b
{z = ab}
{x = X y = Y
∧ }
SWAP(x,y)
{x = Y y = X
∧ }
{V}
i = 1
{i = 1}
Verdadeiro
i = 1
{i = 1}
Omissão
significa
hipótese
verdadeira
83. Provas usando pré- e
pós-condições
●
Assuma que P P
⇒ 1 e Q Q
⇒ 1. Então
também temos:
Justificativa
{P}
{P1} [P P
⇒ 1]
S
{Q}
{Q1} [Q Q
⇒ 1]
84. Exemplo
●
Provar que SWAP troca os elementos.
{x = X y = Y
∧ }
SWAP(x,y)
1 t = x
2 x = y
3 y = t
{x = Y y = X
∧ }
85. Exemplo
{x = X y = Y
∧ }
SWAP(x,y)
{x = X y = Y
∧ }
1 t = x
{t = X∧x = X y = Y
∧ }
2 x = y
{t = X∧x = Y y = Y
∧ }
3 y = t
{t = X∧x = Y y = X
∧ }
{x = Y y = X} [P Q Q
∧ ∧ ⇒ ]
86. Atribuições
{P}
i = 1
{i = 1 P
∧ }
●
A atribuição de uma constante a uma
variável adiciona uma igualdade a pré-
condição na pós-condição:
89. Seleção
●
Nos comandos if … else, a condição do if
é adicionada com e lógico a pré-condição
na pós-condição dentro do if. No else, a
negação da condição é adicionada dentro
do else. {P}
if cond
{cond P
∧ }
S1
{cond P Q
∧ ∧ }
else
{¬cond P
∧ }
S2
{¬cond P Q
∧ ∧ }
90. Seleção
●
Se o comando if não possuir um else
associado, deve-se analisar como se
existisse um else seguido de nenhum
comando (ou skip).
{P}
if cond
{cond P
∧ }
S1
{cond P Q
∧ ∧ }
//else
// skip
{¬cond P
∧ }
91. Exemplo
●
Provar que o algoritmo a seguir calcula |x|.
{x=X}
if x<0
x=-x
{x=|X|} |x|={ x ,x≥0
−x , x<0
92. Exemplo
●
Assuma que há um else skip.
{x=X}
if x<0
x=-x
else
skip
{x=|X|}
|x|={ x ,x≥0
−x , x<0
93. Exemplo
{x=X}
if x<0
{x=X x<0}
∧
{-x=-X X<0} [(-1)x=(-1)X e x=X em x<0]
∧
x=-x
{x=-X X<0}
∧
{x=|X|} [def. |X|]
else
{x=X x
∧ ≥0}
skip
{x=X x
∧ ≥0}
{x=X X
∧ ≥0} [x=X em x≥0]
{x=|X|} [def. |X|]
{x=|X|}
94. Laços
●
Para o comando while, a condição do
comando é adicionada a pós-condição
depois da condição. No fim do while, a
negação da condição é adicionada a pós-
condição do último commando no while.
●
Para comando for é similar:
for i = s to t
S
i = s
while (i≤t)
S
i = i + 1
⇔
95. Exemplo 1
●
Mostre que o seguinte algoritmo soma todos os
elementos de um vetor de tamanho n≥0 antes de
retornar.
{n≥0}
SUM(A)
1 i = 1
2 s = 0
3 while i ≤ n
4 s = s+A[i]
5 i = i+1
6 return s
{s=∑
k=1
n
A[k]
}
96. Exemplo 1
{n≥0}
SUM(A)
{n≥0}
1 i = 1
2 s = 0
{n+1≥1 i=1 s = 0} [+1 em n
∧ ∧ ≥0]
[i em n+1≥1, def. ∑]
3 while i ≤ n
{i≤n∧i≤n+1∧ s=∑
k=1
i−1
A[k]}
{i≤n+1∧ s=∑
k=1
i−1
A[k]}
97. Exemplo 1
{i≤n+1∧ s=∑
k=1
i−1
A[k]}
[SIMP ]
∧
4 s = s+A[i]
5 i = i+1
// fim do while
{i+1≤n+1∧s+A[i]=∑
k=1
i
A[k]}
{i>n∧i≤n+1∧s=∑
k=1
i−1
A[k]}
{i≤n∧ s=∑
k=1
i−1
A[k]} {i=n+1∧ s=∑
k=1
i−1
A[k]}
6 return s
{s=∑
k=1
n
A[k]}
98. Exemplo 2
●
Mostre que o seguinte algoritmo calcula o
menor elemento num vetor de tamanho
n≥1 antes de retornar.
{n≥1}
MIN(A)
1 min = A[1]
2 for i = 2 to n
3 if min > A[i]
4 min = A[i]
{ k : 0<k
∀ ∈ℕ ≤n⇒min≤A[k]}
5 return min
99. Exemplo 2
MIN(A)
1 min = A[1]
2 for i = 2 to n
3 if min > A[i]
4 min = A[i]
5 return min
MIN(A)
1 min = A[1]
2 i = 2
2 while i ≤ n
3 if min > A[i]
4 min = A[i]
5 i = i + 1
6 return min
100. Exemplo 2
{n≥1}
MIN(A)
{n≥1}
{n+1≥2}
1 min = A[1]
2 i = 2
{n+1≥2∧i = 2 min = A[1]}
∧
{i≤n+1 i=2 k : 0<k
∧ ∧∀ ∈ℕ ≤1 min
⇒ ≤A[k]}
{i≤n+1∧ k : 0<k
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
2 while i ≤ n
{i≤n i
∧ ≤n+1∧∀k : 0<k
∈ℕ ≤i-1 min
⇒ ≤A[k]}
{i≤n k : 0<k
∧∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
3 if min > A[i]
101. Exemplo 2
3 if min > A[i]
{min>A[i] i
∧ ≤n∧∀k : 0<k
∈ℕ ≤i-1 min
⇒ ≤A[k]}
{i≤n∧ k : 0<k
∀ ∈ℕ ≤i-1 A[i]<min
⇒ ≤A[k]}
{i≤n∧ k : 0<k
∀ ∈ℕ ≤i-1 A[i]
⇒ ≤A[k]}
{i≤n A[i]
∧ ≤A[i] k : 0<k
∧∀ ∈ℕ ≤i-1 A[i]
⇒ ≤A[k]} [A[i]≤A[i]]
{i≤n k : 0<k
∧∀ ∈ℕ ≤i A[i]
⇒ ≤A[k]}
4 min = A[i]
{i≤n k : 0<k
∧∀ ∈ℕ ≤i min
⇒ ≤A[k]}
//else skip
{min≤A[i] i
∧ ≤n∧ k : 0<k
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
{i≤n k : 0<k
∧∀ ∈ℕ ≤i min
⇒ ≤A[k]}
{i+1≤n+1 k : 0<k
∧∀ ∈ℕ ≤i+1-1 min
⇒ ≤A[k]}
5 i = i + 1
{i≤n+1 k : 0<k
∧∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
//fim do while
{i>n i
∧ ≤n+1 k : 0<k
∧∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
102. Exemplo 2
{i>n i
∧ ≤n+1 k : 0<k
∧∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
{i=n+1 k : 0<k
∧∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
{ k : 0<k
∀ ∈ℕ ≤n min
⇒ ≤A[k]}
6 return min
103. Invariante de laço
●
É uma condição que deve existir antes de um
laço começar e no fim dele, antes de retornar
ao teste do laço.
●
O objetivo dele é se tornar o resultado
desejado quando alcançar o final do
algoritmo.
●
A invariante de laço também deve ser
verdadeira após os comandos de
inicializaçao antes do laço.
104. Exemplo 1
SUM(A)
1 i = 1
2 s = 0
3 while i ≤ n
4 s = s+A[i]
5 i = i+1
{i≤n+1∧ s=∑
k=1
i−1
A[k]}
{i≤n+1∧ s=∑
k=1
i−1
A[k]}
6 return s
{i>n∧i≤n+1∧s=∑
k=1
i−1
A[k]}
{s=∑
k=1
n
A[k]}
105. Exemplo 2
MIN(A)
1 min = A[1]
2 for i = 2
{i≤n+1∧ 0<k
∀ ≤i-1: min≤A[k]}
to n
3 if min > A[i]
4 min = A[i]
// depois de i = i + 1
{i≤n+1 0<k
∧∀ ≤i-1: min≤A[k]}
//fim do for
{i>n i
∧ ≤n+1 0<k
∧∀ ≤i-1: min≤A[k]}
{ 0<k
∀ ≤n: min≤A[k]}
5 return min
106. Exemplo 3
MULTIPLICAR(a,b)
1 x, y, z = a, b, 0
2 while y > 0
3 if y mod 2 = 0
4 y, x = y div 2, x+x
5 else
6 y, z = y-1, z+x
7 return z
107. Exemplo 3
{0≤b}
MULTIPLICAR(a,b)
1 x, y, z = a, b, 0
{0≤y z+xy = ab
∧ }
2 while y > 0
3 if y mod 2 = 0
4 y, x = y div 2, x+x
{0≤y z+xy = ab
∧ }
5 else
6 y, z = y-1, z+x
{0≤y z+xy = ab
∧ }
// fim do while
{y=0 0
∧ ≤y z+xy = ab}
∧
{z = ab}
7 return z
108. Exercício
●
Apresente as demais pré- e pós condições
ao longo do algoritmo anterior de forma a
provar as invariantes de laço após as
linhas 1, 4, 6 e 7.
109. Prova de corretude em
linguagem natural
●
A prova de corretude em linguagem
natural é escrita em cima das pré- e pós-
condições verificadas pelos comandos do
algoritmo junto com a invariante de laço.
●
Deve-se enunciar a invariante de laço e
provar as seguintes três partes:
110. Prova de corretude em
linguagem natural
●
Inicialização: consiste em demostrar que a
invariante de laço é verdadeira antes de iniciar
o laço e após os comandos anteriores ao laço.
●
Manuntenção: mostra que a invariante se
mantém no início e no fim do laço antes de
retornar a condição de teste do laço.
●
Término: prova que a invariante junto com a
negação da condição do laço estabelece o
objetivo final do algoritmo.
111. Exemplo 1
{n≥0}
SUM(A)
1 i = 1
2 s = 0
3 while i ≤ n
4 s = s+A[i]
5 i = i+1
6 return s
{i≤n+1∧s=∑
k=1
i−1
A[k ]}
{i≤n+1∧s=∑
k=1
i−1
A[k ]}
{i>n∧i≤n+1∧s=∑
k=1
i−1
A[k ]}
{s=∑
k=1
n
A[k]}
Prova:
A invariante de laço é a seguinte:
“para cada laço, indexado por i, i ≤ n+1 e .”
Inicialização: Antes da linha 3,
e i ≤ n+1, pois i = 1 e n ≥ 0⇔n+1 ≥ 1 = i.
Manuntenção: Após a linha 4, temos Depois
da linha 5, i torna-se i+1, assim s passa a ser e i ≤
n+1, mantendo a invariante de laço no fim do laço.
Término: Após o comando enquanto, antes da linha 6, i > n
e i ≤ n+1. Logo, i = n+1 e
s=∑
k=1
i−1
A[k]
s=0=∑
k=1
0
A[k]=∑
k=1
i−1
A[k]
s=∑
k=1
i
A[k].
∑
k=1
i−1
A[k]
s=∑
k=1
n
A[k].
112. Exemplo 2
MIN(A)
1 min = A[1]
2 for i = 2 to n
3 if min > A[i]
4 min = A[i]
5 return min
MIN(A)
1 min = A[1]
2 i = 2
{invariante de laço}
3 while i ≤ n
4 if min > A[i]
5 min = A[i]
6 i = i + 1
{invariante de laço}
7 return min
113. Exemplo 2
●
A invariante de laço é
i≤n+1 k : 0<k≤i-1 min≤A[k]
∧
∀ ∈ ⇒
‘
no final, mas depois do incremento de i.
MIN(A)
1 min = A[1]
2 for i = 2 to n
3 if min > A[i]
4 min = A[i]
5 return min
114. Exemplo 2
{n≥1}
MIN(A)
1 min = A[1]
2 for i = 2
{i≤n+1∧ k : 0<k
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
to n
3 if min > A[i]
4 min = A[i]
{i≤n+1 k : 0<k
∧
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
// depois de i = i + 1
{i≤n+1 k : 0<k
∧
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
//fim do for
{i>n i
∧≤n+1 k : 0<k
∧
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
{ k : 0<k≤n min≤A[k]}
∀ ∈ℕ ⇒
5 return min
Prova:
A invariante de laço é para cada
iteração de número i, i ≤ n+1 e, para
todo k natural com 0<k≤i-1, min≤A[k].
Inicialização: Depois de inicializar i
com 2 na primeira iteração do for na
linha 2, como n≥1, então i≤n+1. Como
min=A[1] da linha 1, então para todo k
satisfazendo 0<k≤i-1=1, min≤A[k].
Manuntenção: Após o if na linha 3,
min>A[i]. Assim, pela invariante de
laço, para todo k natural com 0<k≤i-1,
A[i]<min≤A[k]. Como A[i]≤A[i], têm-se,
portanto, que para todo k natural com
0<k≤i, A[i]≤A[k]. Após a linha 4,
min=A[i]. Então, para todo k natural
com 0<k≤i, min≤A[k] após a linha 4.
115. Exemplo 2
Caso a condição no if da linha 4 não
seja verdade, então min≤A[i]. Logo,
para todo k natural com 0<k≤i,
min≤A[k] antes da linha 5. Após a
linha 5, a invariante se mantém,
visto que i = i+1-1 e substituindo
nesta expressão i+1 por i, têm-se
que para todo k natural com 0<k≤i-1,
min≤A[k].
Término: ao final do comando
while, antes da linha 6, têm-se que
i>n. Como a invariante afirma que
i≤n+1, então i = n+1. Assim, como a
invariante também afirma que para
todo k natural com 0<k≤i-1,
min≤A[k], portanto, para todo k
natural com 0<k≤n, min≤A[k].
{n≥1}
MIN(A)
1 min = A[1]
2 for i = 2
{i≤n+1∧ k : 0<k
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
to n
3 if min > A[i]
4 min = A[i]
{i≤n+1 k : 0<k
∧
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
// depois de i = i + 1
{i≤n+1 k : 0<k
∧
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
//fim do for
{i>n i
∧≤n+1 k : 0<k
∧
∀ ∈ℕ ≤i-1 min
⇒ ≤A[k]}
{ k : 0<k≤n min≤A[k]}
∀ ∈ℕ ⇒
5 return min
116. Exercício
{0≤b}
MULTIPLICAR(a,b)
1 x, y, z = a, b, 0
{0≤y z+xy = ab
∧ }
2 while y > 0
3 if y mod 2 = 0
4 y, x = y div 2, x+x
{0≤y z+xy = ab
∧ }
5 else
6 y, z = y-1, z+x
{0≤y z+xy = ab
∧ }
{y=0 0
∧ ≤y z+xy = ab}
∧
{z = ab}
7 return z
Faça a prova em linguagem natural.
118. Exemplo 1
●
Prove que o algoritmo a seguir soma duas
matrizes A e B com resultado na matriz C.
{0≤n 0≤m
∧ }
1 for i=1 to n
2 for j=1 to m
3 C[i,j] = A[i,j] + B[i,j]
{ k,l : 1≤k≤n 1≤l≤m C[k,l]=A[k,l]+B[k,l]
∀ ∈ℕ ∧ ⇒ }
119. Exemplo 1
1 i = 1
2 while i≤n
3 j = 1
4 while j≤m
5 C[i,j] = A[i,j] + B[i,j]
6 j = j+1
7 i = i + 1
1 for i=1 to n
2 for j=1 to m
3 C[i,j] = A[i,j]
+ B[i,j]
124. Exemplo 1
●
A invariante de laço principal é
i ≤ n+1 k,l : 1≤k≤i-1 1≤l≤m C[k,l]=A[k,l]
∧∀ ∈ℕ ∧ ⇒
+B[k,l]
1 for i=1 to n
2 for j=1 to m
3 C[i,j] = A[i,j] + B[i,j]
‘
no final, mas depois do incremento de i.
125. Exemplo 1
●
A invariante do laço interno é
j ≤ m+1 k,l : 1≤k≤i-1 1≤l≤m C[k,l]=A[k,l]
∧∀ ∈ℕ ∧ ⇒
+B[k,l] l : 1≤l≤j-1 C[i,l]=A[i,l]+B[i,l]
∧∀ ∈ℕ ⇒
1 for i=1 to n
2 for j=1 to m
3 C[i,j] = A[i,j] + B[i,j]
‘
no final, mas depois do incremento de j e
antes do incremento de i.
126. Exemplo 1
Prova:
A invariante do laço principal é
“para i ≤ n+1, para todo k, l , com 1≤k≤i-1 e 1≤l≤m, C[k,l]=A[k,l]+B[k,l].”
∈ℕ
Inicialização: Após i=1 na linha 1, a invariante se mantém, pois nenhum
elemento foi adicionado e o intervalo 1≤k≤i-1 é vazio. Além disso, como 0≤n,
i=1 implica que i≤n+1.
Manutenção: Após o teste do for na linha 1, i≤n. Para o while com j, temos a
seguinte invariante de laço:
"j≤m+1 e para todo k,l com 1≤k≤i-1 e 1≤l≤m, C[k,l]=A[k,l]+B[k,l] e para todo
∈ℕ
l com 1≤l≤j-1, C[i,l]=A[i,l]+B[i,l]."
∈ℕ
Inicialização do laço interno: Após j=1 da linha 2, como 0≤m, temos que
j≤m+1. Como j=1, por vacuidade, temos que para todo
para todo l com 1≤l≤j-1=0, C[i,l]=A[i,l]+B[i,l].
∈ℕ
127. Exemplo 1
Manuntenção do laço interno: No teste do for na linha 2, temos j≤m. Após a
linha 3, C[i,j] = A[i,j] + B[i,j] pode ser incluso na proposição para todo l com
∈ℕ
1≤l≤j-1, C[i,l]=A[i,l]+B[i,l], resultando em para todo l com 1≤l≤j, C[i,l]=A[i,l]
∈ℕ
+B[i,l]. Após o incremento de j no for para j, a invariante do laço interno se
mantém.
Término do laço interno: Após o for da linha 2, antes do incremento do for para
i, j=m+1. Assim, a invariante do laço interno torna-se para todo k,l com 1≤k≤i-
∈ℕ
1 e 1≤l≤m, C[k,l]=A[k,l]+B[k,l] e para todo l com 1≤l≤m, C[i,l]=A[i,l]+B[i,l]. Esta
∈ℕ
é equivalente a para todo k,l com 1≤k≤i e 1≤l≤m, C[k,l]=A[k,l]+B[k,l], pois o
∈ℕ
resultado para a linha i da matriz C é incluído no primeiro para todo que variava
de 1 a i-1.
Continuando o a manuntenção da invariante de laço principal, o resultado acima
torna-se a invariante de laço principal após o incremento de i no for mais
externo.
128. Exemplo 1
Término: Após o fim do for mais externo, i = n + 1 e a invariante de laço
principal torna-se o resultado desejado, isto é, para todo k,l com 1≤k≤n e
∈ℕ
1≤l≤m, C[k,l]=A[k,l]+B[k,l].
129. Exemplo 2
●
Demonstre que o INSERTION-SORT
ordena um vetor de tamanho n≥1.
{n≥1}
INSERTION-SORT(A)
1 for j = 2 to n
2 key = A[j]
3 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
{ k : 0<k<n A[k] ≤ A[k+1]}
∀ ∈ℕ ⇒
130. Exemplo 2
INSERTION-SORT(A)
1 for j = 2 to A.length
2 key = A[j]
3 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
INSERTION-SORT(A)
0 j = 2
1 while j ≤ A.length
2 key = A[j]
3 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
9 j = j + 1
Notação simplificada:
∀k : 0<k<n P(k)
∈ℕ ⇒ ⇔ 0<k<n P(k)
∀ ⇒
135. Exemplo 2
INSERTION-SORT(A)
1 for j = 2 to A.length
2 key = A[j]
3 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
INSERTION-SORT(A)
0 j = 2
{invariante de laço}
1 while j ≤ A.length
2 key = A[j]
3 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
9 j = j + 1
{invariante de laço}
136. Exemplo 2
●
A invariante de laço principal é
j ≤ n+1 0<k<j-1 A[k] ≤ A[k+1]
∧ ∀ ⇒
INSERTION-SORT(A)
1 for j = 2 to A.length
2 key = A[j]
3 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
‘
no final, mas depois do incremento de j.
137. Exemplo 2
●
A invariante do laço interno é
0≤i 1≤k≤i-1: A[k]≤A[k+1]
∧∀ ∧
key≤A[i+1] i+1≤k≤j-1: A[k]=A[k+1]
∀
INSERTION-SORT(A)
1 for j = 2 to A.length
2 key = A[j]
3 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
‘
depois do decremento de i
138. Exemplo 2
Prova:
A invariante do laço principal é
“para uma iteração j, o vetor A[1..j-1] está ordenado com j≤n+1.”
Inicialização: Após j=2 na linha 1, a invariante se mantém, pois o único
elemento é A[1] e j=2 implica que 0<j≤n+1, visto que é assumido que n≥1.
Manutenção: Após a linha 1, 0<j≤n. Para o while da variável i, temos a
seguinte invariante de laço:
“0≤i, o vetor A[1..i] está ordenado, key≤A[i+1] e os elementos de A[i+1..j-1]
foram copiados para A[i+2..j].”
Inicialização do laço interno: Após a linha 4, a invariante de laço para i é
verdadeira, pois como i=j-1 e 0<j≤n, então 0≤i. Além disso, o vetor A[1..j-1] já
estava ordenado da invariante de laço principal, logo A[1..i] está ordenado.
Temos que key=A[j] e A[j]≤A[j], então key≤A[j]=A[i+1]. Podemos dizer que
A[i+1..j-1] foi copiado para A[i+2..j], visto que esses vetores são vazios, pois
i+1>j-1.
139. Exemplo 2
Manutenção do laço interno: Após a linha 5, j≤n, 0<i e A[i]>key implica que
key≤A[i]. Após a linha 6, key≤A[i+1] e A[i]=A[i+1]. Logo, com a invariante do
while com i, temos agora que A[i..j-1] foi copiado para A[i+1..j]. Após a linha 7,
0≤i, key≤A[i+2] e A[i+1..j-1] foi copiado para A[i+2..j]. Como A[i+1..j-1] foi
copiado para A[i+2..j], A[i+1]=A[i+2]. Logo, key≤A[i+1], concluindo a
manuntenção do laço interno.
Finalização do laço interno: Após o laço interno, i≤0 ou A[i]≤key. Para a
primeira alternativa, se i≤0 e a invariante do while para i se mantém, então i=0.
Logo, key≤A[1] e A[1..j-1] é igual a A[2..j]. Para a segunda alternativa, se
A[i]≤key e a invariante do laço interno é verdadeira, A[i]≤key e key≤A[i+1]
implicam que A[i]≤A[i+1]. Com isto e que A[1..i] está ordenado e A[i+1..j-1] foi
copiado para A[i+2..j], temos que A[1..j] está ordenado. Após a linha 8, na
primeira alternativa, key≤A[1] torna-se A[i+1]≤A[1]. Como i=0, esta
desigualdade é sempre verdadeira. Como A[1..j-1] é igual a A[2..j] e key é A[1],
então para a primeira alternativa também resulta em A[1..j] ordenado.
140. Exemplo 2
Continuando a manuntenção do laço principal, após a linha 9, como A[1..j]
ordenado e j≤n, têm-se que o vetor A[1..j-1] está ordenado com j≤n+1.
Término: Quando o laço principal encerra, j>n. Pela invariante de laço, j>n e
j≤n+1 implicam que j=n+1. Logo, A[1..n] está ordenado.
141. Exercícios
1) Provar corretude dos algoritmos deste
slide que não possuem pré- e pós-
condições (MINMAX1, MINMAX2, ...).
Observe que em algoritmos recursivos
deve-se utilizar indução. Apresente
também a prova em linguagem natural.
2) Faça o mesmo para os algoritmos a
seguir:
142. Exercícios
{n>0}
Fib(n)
i, a, b = 1, 1, 0
while i < n
i, a, b = i+1, a+b, a
{a = fn}
return a
{n>0}
maiorPotenciaDe2(n)
i = 1
while 2i < n
i = 2i
{0 < i ≤ n< 2i p : i = 2
∧∃ ∈ℕ p
}
return i
f0 = 0
f1 = 1
fn = fn-1 + fn-2
144. Exercícios
3) Mostre que o seguinte algoritmo realiza a
multiplicação de dois números binários de
tamanho n. Mostre também que o tempo no
pior caso é O(n1,59
).
Dicas:
– A prova não será por invariante de laço, pois o
algoritmo é recursivo.
– Seja x = 101101102. Então, n = 8, xL = 1011, xR =
0110 e x = 101101102 = 1011∙24
+ 0110 = xL∙2n/2
+xR.
●
Multiplicar por 2i
pode ser feito com i
deslocamentos à esquerda.
145. Exercícios
GAUSSMULT(x,y)
1 if n = 1 return xy
2 xL, xR = n/2
⌈ ⌉ bits à esquerda de x, n/2
⌊ ⌋
bits a direita de x
3 yL, yR = n/2
⌈ ⌉ bits à esquerda de y, n/2
⌊ ⌋
bits a direita de y
4 P1 = GAUSSMULT(xL,yL)
5 P2 = GAUSSMULT(xR,yR)
6 P3 = GAUSSMULT(xL+xR,yL+yR)
7 return P12n
+ (P3-P1-P2)2n/2
+ P2
146. Referência
●
Gries, David. The science of programming.
1981. Springer-Verlag.
●
Cormen, T.H., Leiserson, C.E., Rivest, R.L.
Stein, C. Introduction to Algorithms. Third
edition. 2009. MIT Press.