Eu desenvolvi o seguinte pseudocódigo para o problema da soma dos pares:
Dada uma matriz de números inteiros e um número inteiro , retorne YES se houver posições i, j em A com A [i] + A [j] = b , NO caso contrário.
Agora devo declarar um loop invariável que mostra que meu algoritmo está correto. Alguém pode me dar uma dica de um loop válido invariável?
PAIRSUM(A,b):
YES := true;
NO := false;
n := length(A);
if n<2 then
return NO;
SORT(A);
i := 1;
j := n;
while i < j do // Here I should state my invariant
currentSum := A[i] + A[j];
if currentSum = b then
return YES;
else
if currentSum < b then
i := i + 1;
else
j := j – 1;
return NO;
algorithms
proof-techniques
loop-invariants
Forrest Gump
fonte
fonte
A
classificação. 2)i
ej
alterne as funções no final do loop, dependendo da entrada. 3) O número de execuções de loop depende da entrada.Respostas:
Primeira simplificação: esse algoritmo, por inspeção, não pode dizer SIM quando não deveria, pois dizer SIM é imediatamente após um teste para verificar se a soma nas posições atuais é .b
Segunda simplificação: sempre termina, pois e movem de maneira monótona para cima e para baixo, respectivamente, e sempre se move. Assim, eventualmente, eles se tornam iguais e o loop termina.i j
Então, vamos supor que essa resposta seja SIM, e e são os índices nos quais estamos interessados, após a classificação, com o menor possível e, em seguida, o maior possível (podemos ter vários pares possíveis com a soma correta). Aqui está o invariável:x y x y
Se e , o algoritmo irá parar e dizer SIM. O loop invariável é trivial no início e também trivial quando e . Mas se e , então, como a matriz é classificada , devido à forma como foi selecionado. Portanto, diminui em todas as iterações futuras até atingir . O outro caso, onde chega a primeiro, é simétrico.i=x j=y i<x j>y i=x j>y A[i]+A[j]>b y j y j y
Como observado acima, o algoritmo sempre termina, o que implica que o algoritmo realmente dirá SIM, então terminamos.
Edit: Uma vez que esta pergunta é sobre prova escrita, aqui está outra opção, que eu acho que é mais confusa e mais corajosa. Não sei se Tony Hoare aprovaria ou não.
Aqui está o invariante: À parte superior do loop,
Novamente, isso ocorre de forma vaga no começo. Agora vamos verificar alguns casos:
Finalmente, observamos que a diferença entre e sempre fica menor, então eles devem eventualmente tornar-se igual, e o algoritmo irá parar.i j
Observações: Esse é exatamente o mesmo argumento, mas mais difícil de seguir. Você também pode escrever provas como essa usando algumas técnicas especializadas:
fonte
Talvez algo assim?
Talvez uma maneira mais clara de afirmar isso seja simplesmente:
Esse invariante de loop é útil porque, se o loop termina (ou seja, atinge a condição de terminação), então
A
a matriz é classificada com um elemento, e podemos concluir que não há um par de elementosA
.Aqui está um esboço / ideia de prova para esse invariante:
A[1...n]
é uma matriz classificada ei = 1
,j = n
. Portanto, a invariante do loop é garantida após ask = 0
iterações do corpo do loop.k
até e inclusivek'
.k' + 1
. Existem três casos para o comportamento do corpo do loop durante a iteração correspondente ak = k' + 1
:A[i] + A[j] = b
, nesse caso, o loop retorna corretamenteYES
e não há iteração após essa iteração do loop;A[i] + A[j] < b
, Caso em que podemos seguramente descartar o menor elemento deA
,A[i]
. Para ver isso, observe que uma vez queA[j]
é o maior elemento emA
, se a soma dele e outro elemento emA
for menor queb
, então a soma desse outro elemento e de qualquer elemento deA
também é garantida como menor queb
.A[i] + A[j] > b
, Caso em que podemos seguramente descartar o maior elemento deA
,A[j]
. O raciocínio é semelhante ao do casoA[i] + A[j] < b
.i = j
e assimA
contém um único elemento. ComoA
agora contém apenas um elemento, é trivialmente o caso queA
não contém dois elementos no totalb
.Demonstrar a suficiência do loop invariável para mostrar que não há solução após o término do loop
Isso aborda uma questão levantada nos comentários de Raphael, aqui estão alguns esclarecimentos sobre por que esse loop invariável deve ser suficiente.
Para que o loop termine, precisamos ter
i = j
. Observe também que esse loop sempre termina, emitindo corretamenteYES
se uma correspondência for encontrada ou removendo elementos da matriz até que apenas um permaneça (aumentandoi
ou diminuindoj
). Esse argumento de finalização não é fornecido no esboço acima, mas deve ser direto.Desdex,y x,y A[x]+A[y]=b
i = j
,A
é uma sub-matriz classificada de tamanho um. Como nenhuma matriz de tamanho 1 atende ao requisito de que existe (observe que estou interpretando isso como único , pois, caso contrário, o algoritmo fornecido está incorreto; considere e ) onde , e como, pelo invariante do loop, sabemos que nenhum elemento , onde , poderia ser adicionado a qualquer elemento , onde , para atingir a soma , isso completa a ideia da prova.A = [1]
b = 2
A[k]
k != i = j
A[k']
k' != k
b
Pressupostos / fatos utilizados para a condição de rescisão:
i = j
após o término (normal) do corpo do loop, e o loop sempre termina;i
j
i = j
k != i = j
, não há nenhuma maneira de adicionarA[k]
a qualquerA[k']
,k != k'
, para obterb
; nós apenas descartamos, incrementandoi
e diminuindoj
, elementos que não poderiam ser somados ab
nenhum elemento da matriz original.Sinta-se à vontade para ajudar se eu continuar perdendo algo sutil.
fonte
Sempre existem infinitamente muitos invariantes de loop válidos. O truque é encontrar um que seja suficiente para provar o que você quer provar sobre o algoritmo e que você pode provar (geralmente por indução sobre o número de iterações de loop).
Existem três partes para provar a correção desse algoritmo:
YES
ouNO
, esta saída está correta.Para correção, você precisa provar que e . É melhor fazer parte do seu invariante. Dada a condição do loop , você pode condensar isso em na entrada no corpo do loop. Esta condição não é verdadeiro quando o teste de circuito é atingido no final, mas que pode vir a ser útil para aviso de que (porque no interior do corpo do ciclo, com , e alterar apenas por , que pode na pior das hipóteses, transformar essa desigualdade estrita em igualdade).1≤i≤n 1≤j≤n i<j 1≤i<j≤n i≤j i<j i j 1
QuandoA[i]+A[j]=b
return YES
é executado, é aparente. Portanto, esta parte não precisa de nada em particular para ser provado.Quando a últimai<j ∀i,∀j,A[i]+A[j]≠b (x,y) (i,j) 1≤x≤i j≤y≤n (x,y)≠(i,j) A[x]+A[y]≠b . É melhor que isso seja expresso no loop invariável.
return NO
instrução é executada, o que significa que o loop terminou normalmente (e, portanto, é falso), você precisa provar que . Esta propriedade obviamente não é verdadeira em geral: não se aplica se a resposta for . Você precisa fortalecer essa propriedade: você tem um caso especial, que precisa ser generalizado. Este é um caso típico de uma propriedade que se aplica apenas à parte da matriz que já foi percorrida: o loop é construído para que se seja um valor anterior de (ou seja, e e ) em seguidaYES
Nós terminamos lá? Não é bem assim; tudo o que sabemos sobre a terminação normal do loop é que . E se tivéssemos e , ou e : poderíamos ter ? É difícil dizer sem mais informações. De fato, é melhor distinguir alguns casos em que e os casos em que . Com essas propriedades, podemos usar o fato de que a matriz é classificada para deduzir fatos sobre outras posições na matriz; com apenas , não temos nada para trabalhar. Não sabemos para que lado∀x≤i,∀y≤j,A[x]+A[y]≠b x>i y≤j x≤i y>j A[x]+A[y]≠b A[x]+A[y]>b A[x]+A[y]<b ≠b A[x]+A[y] mentiras para alguns e aleatórios ; mas sabemos o que acontece no limite: se é incrementado, é porque é muito pequeno e se é diminuído, é porque é muito grande . Pense em qual laço invariável poderia expressar isso; Vou dar uma possibilidade abaixo.x<i y>j i A[i]+A[j] j A[i]+A[j]
Observe que essa propriedade não fornece diretamente a condição desejada para aA[x]+A[y]<b A[x]+A[y]>b
return NO
instrução; você ainda precisará observar o que aconteceu na última execução do loop ou, alternativamente, para provar um invariante de loop mais forte que analisa mais de perto quando quando .Finalmente, para a terminação, é necessário relacionar e com o número de iterações através do laço. Aqui, isto é simples: ou se move a cada turno, então diminui em cada iteração do loop; você não precisa usar um loop invariável para provar isso.i j i j j−i 1
Obtivemos o seguinte invariante:
fonte
Não há maneira infalível de derivar um invariante útil; note que o problema não é computável (caso contrário, poderíamos resolver o problema da parada). Portanto, você está de volta à tentativa e erro com heurísticas treinadas pela experiência. Veja a resposta de Gilles para um processo de pensamento exemplar. Em geral, você precisa primeiro da condição de postagem desejada (ou seja, especificação de saída) e encontra uma invariante que a implique (junto com a condição de loop negada).
Aqui está o que eu acho que funciona:
Que as três últimas cláusulas sejam realmente mantidas pelo loop - supondo que você não termine com YES, o que é trivialmente correto - é tedioso para mostrar, porque elas são altamente interdependentes e podem mudar de função.i,j
No final, você obtém que permite combinar as duas últimas cláusulas ao fato de que o último elemento não se resume a com nenhum outro elemento. A combinação entre elementos da esquerda e da direita é excluída pela cláusula três. Finalmente, dois elementos restantes de não podem somar por causa da cláusula quatro e um, e da mesma forma para dois elementos à direita de .i=j A[i] b i b i
Se você fizer as provas, siga as regras da lógica Hoare de perto.
fonte