Loop invariável para um algoritmo


7

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.Abi,jAA[i]+A[j]=b

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;

3
Bem-vinda! Observe que apenas um invariante de loop não prova que seu algoritmo está correto. Ele precisa ser um invariável correto no sentido de ser adequado para provar a pós-condição desejada. Observe também que essas provas se tornam mais difíceis se você sair do loop prematuramente; pode ser uma boa idéia reescrever o algoritmo de modo que todo o loop seja sempre processado.
Raphael

11
Você vê o preço de ser inteligente: as provas ficam difíceis. 1) A correção depende muito da Aclassificação. 2) ie jalterne as funções no final do loop, dependendo da entrada. 3) O número de execuções de loop depende da entrada.
Raphael

11
Eu acho que você esqueceu de declarar que sua matriz deve ser classificada cada vez mais se você quiser que esse algoritmo esteja correto.
Gopi

(oups, Raphael já disse que :(, embora você ainda não o modificou.)
Gopi

Respostas:


5

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.ij

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:xyxy

  • No início do loop, e .ixjy

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=xj=yi<xj>yi=xj>yA[i]+A[j]>byjyjy

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,

  • Se , então, para qualquer ,s<ix[n]A[s]+A[x]b
  • Se , então, para qualquer ,t>jx[n]A[x]+A[t]b

Novamente, isso ocorre de forma vaga no começo. Agora vamos verificar alguns casos:

  • Se , em qualquer par de índices e pelo menos um de ou é menor que ou maior que . Isso exclui que a entrada seja SIM. O loop termina e diz NÃO, então estamos em boa forma.i=jxyxyij
  • Suponha que o loop não tenha terminado. Se , a resposta é SIM e o algoritmo funciona.A[i]+A[j]=b
  • Assuma .A[i]+A[j]b
  • Subcase: Se houver um , pela hipótese , de modo que , então porque a matriz foi classificada, então , o que faz com que diminua. Isso mostra que a invariante é mantida para . Para vê-lo em , observe que se existe um tal que , então , que é descartado pela hipótese indutiva.jj<jA[i]+A[j]=bA[j]<A[j]A[i]+A[j]>bjijxA[x]+A[j]=bx<i
  • Subcase: Se houver um , pela hipótese , de modo que , então porque a matriz foi classificada, então , o que faz com que aumente. Isso mostra que a invariante é mantida para . Para ver isso em , observe que se existe um tal que , então , que é descartado pela hipótese indutiva.ii>iA[i]+A[j]=bA[i]>A[i]A[i]+A[j]<bijixA[i]+A[x]=bx>j
  • Subcaso: se nenhum dos outros dois casos for válido, o invariante será mantido trivialmente.

Finalmente, observamos que a diferença entre e sempre fica menor, então eles devem eventualmente tornar-se igual, e o algoritmo irá parar.ij

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:

  • Não diga o que o algoritmo faz. Aqui, se a entrada for SIM, o algoritmo SEMPRE fornecerá índices de certificação o mais próximo possível dos fins. A segunda prova esconde isso de maneira inteligente em uma análise de caso.
  • Não dê nomes às coisas. Tendo ocultado o que o algoritmo faz, também não nomeie o ponto de parada.
  • Não considere casos fáceis. Esse algoritmo funciona claramente para NÃO instâncias. Ao não notar isso de antemão, obtemos uma invariante mais complicada.

Essa é uma boa prova de alto nível, mas como você usa essa ideia em uma prova corajosa ao estilo Hoare? Em particular, de onde você obtém (pois eles podem não existir)? x,y
Raphael

@ Louis Também acho que o sarcasmo é uma péssima idéia, pois pode ser facilmente mal interpretado. Além disso, você falha na ignição aqui porque sua segunda versão também é de alto nível. As provas no estilo Hoare usam argumentos locais , movendo-se entre instruções de código individuais. Não sei se o OP realmente queria ir nessa direção; Eu assumi isso.
Raphael

3

Talvez algo assim?

Após as kiterações do loop, A[i...j]contém uma sub-matriz classificada de tamanho n - k, com kos elementos originais, nenhum dos quais somando bcom qualquer outro elemento de A, foi removido.

Talvez uma maneira mais clara de afirmar isso seja simplesmente:

Após as kiterações do corpo do loop, para 0 <= k < n, A[i...j]é uma sub-matriz classificada (contígua) de A[1...n](cujos elementos estão na mesma ordem e têm o mesmo valor que antes da primeira iteração) do corpo do loop contendo kelementos (ou seja, i + j = k + 1), que A[1...n]contém um par de elementos desejado, se e somente se A[i...j], também.

Esse invariante de loop é útil porque, se o loop termina (ou seja, atinge a condição de terminação), então Aa matriz é classificada com um elemento, e podemos concluir que não há um par de elementos A.

Aqui está um esboço / ideia de prova para esse invariante:

  • Antes do laço, A[1...n]é uma matriz classificada e i = 1, j = n. Portanto, a invariante do loop é garantida após as k = 0iterações do corpo do loop.
  • Suponha que o invariante do loop seja verdadeiro katé e inclusive k'.
  • Agora mostramos que o loop invariável é válido k' + 1. Existem três casos para o comportamento do corpo do loop durante a iteração correspondente a k = k' + 1:
    • A[i] + A[j] = b, nesse caso, o loop retorna corretamente YESe 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 de A, A[i]. Para ver isso, observe que uma vez que A[j]é o maior elemento em A, se a soma dele e outro elemento em Afor menor que b, então a soma desse outro elemento e de qualquer elemento de Atambém é garantida como menor que b.
    • A[i] + A[j] > b, Caso em que podemos seguramente descartar o maior elemento de A, A[j]. O raciocínio é semelhante ao do caso A[i] + A[j] < b.
  • Após a cessação do corpo do ciclo, i = je assim Acontém um único elemento. Como Aagora contém apenas um elemento, é trivialmente o caso que Anão contém dois elementos no total b.

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 corretamente YESse uma correspondência for encontrada ou removendo elementos da matriz até que apenas um permaneça (aumentando iou diminuindo j). Esse argumento de finalização não é fornecido no esboço acima, mas deve ser direto.

Desde 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.x,y x,yA = [1]b = 2A[x]+A[y]=bA[k]k != i = jA[k']k' != kb

Pressupostos / fatos utilizados para a condição de rescisão:

  • A questão foi interpretada corretamente, ou seja, e são distintos; você precisa adicionar elementos em posições diferentes na matriz original.xy
  • i = j após o término (normal) do corpo do loop, e o loop sempre termina;
  • Não há duas distintas e entre e se ; não há dois índices em uma sub-matriz de tamanho 1.xyiji = j
  • Até o invariante de laço, para k != i = j, não há nenhuma maneira de adicionar A[k]a qualquer A[k'], k != k', para obter b; nós apenas descartamos, incrementando ie diminuindo j, elementos que não poderiam ser somados a bnenhum elemento da matriz original.

Sinta-se à vontade para ajudar se eu continuar perdendo algo sutil.


Eu acho que sua prova está correta (mas note que eu a procurei apenas). Como você descobriu isso? Este é um exercício de aprendizado, portanto, mostrar como você desenvolveu seu método para resolver o problema seria apreciado.
Gilles 'SO- stop be evil'

3

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:

  • O algoritmo nunca executa uma etapa incorreta. Aqui, as possíveis etapas incorretas são acessar um elemento da matriz fora dos limites da matriz.
  • Quando o algoritmo retorna YESou NO, esta saída está correta.
  • O algoritmo termina para cada entrada.

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).1in1jni<j1i<jniji<jij1

Quando return YESé executado, é aparente. Portanto, esta parte não precisa de nada em particular para ser provado.A[i]+A[j]=b

Quando a última return NOinstruçã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 seguidai<ji,j,A[i]+A[j]bYES(x,y)(i,j)1xijyn(x,y)(i,j)A[x]+A[y]b . É melhor que isso seja expresso no loop invariável.

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 ladoxi,yj,A[x]+A[y]bx>iyjxiy>jA[x]+A[y]bA[x]+A[y]>bA[x]+A[y]<bbA[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<iy>jiA[i]+A[j]jA[i]+A[j]

Observe que essa propriedade não fornece diretamente a condição desejada para a return NOinstruçã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 .A[x]+A[y]<bA[x]+A[y]>b

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.ijijji1

Obtivemos o seguinte invariante:

1ijn(x<i,y<j,A[x]+A[y]b)(x<i,A[x]+A[j]<b)(y>j,A[i]+A[y]>b)

E você está perdendo a parte depois de ter o invariante. E, como você diz, " classificado" deve fazer parte do invariante. A
Raphael

Ah, e um pequeno erro: não é invariável. i<j
Raphael

11
@ Rafael Bom ponto com . Na verdade, não é necessário realizar isso através da indução, mas como ponto metodológico, é importante. Considero o fato de que não muda durante o loop (e, portanto, permanece classificado, mas o fato de não mudar também é crítico) como óbvio aqui (tecnicamente, isso também teria que ser provado, mas eu ' d faça-o separadamente e tire-o do caminho primeiro). O que acontece depois que você tem o invariante não faz parte da questão; é a parte do exercício que pode ser feita mecanicamente. i<jA
Gilles 'SO- stop be evil' '

A parte que vem depois mostra por que a invariante é suficiente; mas então, você (meio que) cobriu isso em sua derivação.
Raphael

2

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:

I= A is sorted and has not been changed 1i,jn ji i<i. j>j. A[i]+A[j]b i<i. b>A[i]+A[j] j>j. b<A[i]+A[j]

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=jA[i]bibi

Se você fizer as provas, siga as regras da lógica Hoare de perto.


O que eu sinto falta na sua resposta é como você decidiu esse invariante. (Isso e você não mostrou que e estamos sempre dentro dos limites da matriz, mas não se importe comigo, sou um especialista em correção de software.)ij
Gilles 'para- parar de ser mau'

Bom ponto sobre os índices. Tais condições geralmente são esquecidas, pois não são necessárias para mostrar a pós-condição, mas "apenas" são necessárias para mostrar que você chega lá. Deve haver uma regra de Hoare para acessos a matrizes que proteja acessos a matrizes, por exemplo, , forçando você a acompanhar os índices. {1iA.lengthP(X)}A[i]=X;{P(A[i])}
Raphael
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.