Um algoritmo (ou programa) está correto se para cada instância do problema que ele resolve, a resposta dada está correta. A prova da correção tem duas partes: provar que a resposta está correta se o programa terminar ( correção parcial ), provar que o programa termina (a complexidade de tempo é um refinamento de uma prova de término). Vejamos uma técnica desenvolvida por Hoare (o mesmo que inventou o Quicksort) para correção parcial de algoritmos.
A seguir, e são proposições.
Definição 1 Um algoritmo, ou trecho de algoritmo, está parcialmente correto com respeito a pré-condição e a pós-condição se sempre que for verdadeiro para os valores de entrada de e terminar, então é verdadeiro para os valores de saída de .
Notação significa está parcialmente correto com respeito a pré-condição e a pós-condição .
Exemplo Por exemplo, se é e é e é o trecho de programa
y:=2; z:= x + y;
então, . De fato, suponha . Após a execução de , e pois de temos . Portanto, .
Regras de Inferência para correção parcial de programas:
1. Composição
2. Condicional
2.1. No caso
Se (condição) então S;
o argumento é
2.2. No caso
Se (condição) então S1; senão S2
o argumento é
3. Laços No caso
Enquanto (condição) S;
Nesse caso, é executado entuanto a condição for verdadeira; devemos escolher uma proposição que permaneça válida durante as execuçãoes de . O argumento é
A proposição é o invariante do laço. Provar que uma proposição é invariante de laço precisa de indução.
Exemplo. Seja o seguinte algoritmo que recebe dois inteiros e devolve o valor da variável produto. Vamos provar que
multiplica(m,n:inteiros) /* Dados : m e n inteiros */ /* Devolve: m n */ /* p: m e n inteiros */ se (n<0) então a:=-n; senão a:=n; /* q: p e a=|n| */ k:=0; x:=0; /* r: q e k=0 e x=0 */ enquanto (k <= a) */ { x:=x+m; k:=k+1; } /* s: x=ma e a=|n| */ se (n<0) então produto:=-x; senão produto:=x. /* t: produto=mn */
A estratégia para provar a correção desse algoritmo é:
- ;
- ;
- ;
- ;
pela regra da composição. De 3 e 4 temos, por composição novamente
que junto com (56), mais a regra da composição resulta que
Agora, vamos provar 1,2,3,4 listados acima.
Sejam e as proposições dadas no algoritmo, seja o trecho de programa
Assuma e .
Se e e então após terminar temos . Se então portanto , portanto
Agora, se e e então após terminar temos . Se então portanto . Portanto,
Portanto, de (53), (54) e do argumento 2.2 temos (52), e, portanto, . Agora, considere o trecho
e vamos provar .
para qualquer proposição verdadeira , em particular quando
é verdadeira.
Assuma . Se (condição do laço) então o invariante
é verdadeiro antes da primeira rodada do laço pois . Assuma antes de uma execução do trecho
Considere uma execução de e denote por e os valores das variáveis e antes dessa execução. Então, por hipótese
e (condição) ; após a execução temos e .
Se e , então .
Se então .
Se então .
Portanto, após a execução de o invariante é verdadeiro, ou seja, “provamos”
e da regra de inferência para laços, temos
ainda, . Portanto,
logo , onde .
Finalmente, assuma , denote por o trecho de programa e vamos provar .
Se , então .
Se e então após , .
Se e e então . Portanto .
Agora, se e , então .
Se e então após , .
Se e e então . Portanto, pelo argumento 2.2 para condicionais
Mais invariante de laço.
Consideremos o seguinte algoritmo:
fat(n) /* Dados : n natural */ /* Devolve: n !*/ i:=1; f:=1; enquanto (i < n) { f := f*(i+1); i := i+1;} devolva f.
Vamos provar que a seguinte proposição é um invariante do laço na terceira linha do algoritmo
Antes, porém, notemos que o invariante e a negação da condição do laço resulta que quando o laço termina temos e e e , ou seja, e , provando a correção parcial.
Vamos provar que a proposição (58) é verdadeira independente do número de vezes que o laço é executado. A prova é por indução no número de iterações do .
Suponhamos que a condição do laço vale, ou seja, . Antes da primeira iteração (58) é verdadeiro pois .
Suponhamos que e que . Após a execução de e após a execução de temos que para o novo valor de , e e .
Exemplo.
Prove que o seguinte programa está parcialmente correto.
Divisão_inteira(a,d) /* Dados : a natural e d natural positivo */ /* Devove : inteiros q e r tais que a=dq+r e 0 r < d */ 1. r := a; 2. q := 0; 3. enquanto (r d) 4. { r := r-d; 5. q := q+1; } 6. devolva q,r.
Prova: Sejam o trecho de programa definido pelas linhas 1 e 2, o trecho de programa definido pelas linhas 3 — 5, todo o programa, e e as proposições:
Assumindo
como invariante do laço de , temos, já que é a condição de parada do laço, que
Logo,
e, dessarte, .
Resta-nos ainda mostrar serve como invariante do laço definido por . Como se e , temos, da asserção 59, que . Analisemos agora o que acontece quando executamos assumindo as 4 e 5: sendo e os novos valores de, respectivamente, e , verificamos que e . Consequentemente, já que , ,
e, finalmente,
o que prova que é de fato um invariante para o laço definido por .
Algoritmos recursivos.
Exemplo
fat(n) /* Dados : n natural */ /* Devolve: n !*/ se (n=0) fat:=1; senão fat:= n*fat(n-1); devolva fat.
A prova da correção é fácil usando indução que .
Assuma
- se então após temos , portanto se então ; assuma que se então
- se então pois , como não vale a condição temos , portanto, pela hipótese da indução, após a execução do código temos
pela regra de inferência 2.2, .
Exercício. Prove a correção de
pot(a,n) /* Dados : a real positivo e n natural */ /* Devolve: a^n */ se (n=0) pot:=1; senão pot:= a*pot(a,n-1); devolva pot.