Che cos'è un invariante loop?


268

Sto leggendo "Introduzione all'algoritmo" di CLRS. Nel capitolo 2, gli autori menzionano "invarianti ad anello". Che cos'è un invariante loop?


4
Questo sembra abbastanza buono nello spiegare: cs.miami.edu/~burt/learning/Math120.1/Notes/LoopInvar.html
Tom Gullen,


Nel caso in cui qualcuno volesse risolvere un vero problema di codifica algoritmica basato sul concetto di invariante di loop, si prega di fare riferimento a questo problema su HackerRank. Hanno anche riferito il problema del tipo di inserzione solo per dettagliare il concetto.
RBT

Si può anche fare riferimento alle note qui per la comprensione teorica.
RBT

Risposte:


345

In parole semplici, un invariante del ciclo è un predicato (condizione) che vale per ogni iterazione del ciclo. Ad esempio, diamo un'occhiata a un semplice forciclo simile al seguente:

int j = 9;
for(int i=0; i<10; i++)  
  j--;

In questo esempio è vero (per ogni iterazione) quello i + j == 9. Un invariante più debole che è anche vero è quello i >= 0 && i <= 10.


29
Questo è un esempio eccellente. Molte volte, quando ho sentito un istruttore descrivere l'invariante loop, è stata semplicemente "la condizione loop", o qualcosa di simile. Il tuo esempio mostra che l'invariante può essere molto di più.
Brian S,

77
Non vedo questo un buon esempio perché l'invariante del loop dovrebbe essere in qualche modo l'obiettivo del loop ... CLRS lo usa per dimostrare la correttezza di un algoritmo di ordinamento. Per l'ordinamento per inserzione, supponendo che il ciclo stia itando con i, alla fine di ogni ciclo, l'array viene ordinato fino all'i-esimo elemento.
Scontro il

5
Sì, questo esempio non è sbagliato, ma non abbastanza. Eseguo il backup di @Clash up, poiché l'invariante loop dovrebbe presentare l'obiettivo, non solo per se stesso.
Jack,

7
@Tomas Petricek - quando termina il ciclo, i = 10 e j = -1; quindi l'esempio invariante più debole che hai dato potrebbe non essere corretto (?)
Raja

7
Anche se sono d'accordo con i commenti sopra, ho votato questa risposta perché ... l'obiettivo non è definito qui. Definisci qualsiasi obiettivo adatto e l'esempio è eccezionale.
Flavio

119

Mi piace questa definizione molto semplice: ( fonte )

Un invariante di loop è una condizione [tra le variabili del programma] che è necessariamente vera immediatamente prima e immediatamente dopo ogni iterazione di un loop. (Nota che questo non dice nulla sulla sua verità o falsità a metà di un'iterazione.)

Di per sé, un invariante di loop non fa molto. Tuttavia, dato un invariante appropriato, può essere utilizzato per dimostrare la correttezza di un algoritmo. Il semplice esempio in CLRS ha probabilmente a che fare con l'ordinamento. Ad esempio, lascia che il tuo invariante del ciclo sia qualcosa di simile, all'inizio del ciclo, le prime ivoci di questo array sono ordinate. Se puoi provare che si tratta effettivamente di un invariante di loop (ovvero che contiene prima e dopo ogni iterazione di loop), puoi usarlo per dimostrare la correttezza di un algoritmo di ordinamento: al termine del loop, l'invariante di loop è ancora soddisfatto e il contatore iè la lunghezza dell'array. Pertanto, le prime ivoci vengono ordinate significa che l'intero array viene ordinato.

Un esempio ancora più semplice: loop invarianti, correttezza e derivazione del programma .

Il modo in cui capisco un invariante di loop è come uno strumento sistematico e formale per ragionare sui programmi. Facciamo una singola affermazione che ci concentriamo sul dimostrare che è vero e lo chiamiamo il ciclo invariante. Questo organizza la nostra logica. Mentre possiamo anche discutere in modo informale della correttezza di alcuni algoritmi, l'uso di un invariante loop ci costringe a pensare con molta attenzione e assicura che il nostro ragionamento sia ermetico.


10
Va sottolineato che "immediatamente dopo ogni iterazione" include dopo la fine del ciclo, indipendentemente da come è terminato.
Robert S. Barnes,

Grazie mille per questa risposta! Il più grande risultato da esso è lo scopo di avere questo invariante di loop è di aiutare a dimostrare la correttezza dell'algoritmo. Le altre risposte si concentrano solo su ciò che è un invariante loop!
Neekey,

39

C'è una cosa che molte persone non capiscono subito quando hanno a che fare con loop e invarianti. Si confondono tra l'invariante del loop e il condizionale del loop (la condizione che controlla la terminazione del loop).

Come sottolinea la gente, l'invariante del ciclo deve essere vero

  1. prima che inizi il ciclo
  2. prima di ogni iterazione del ciclo
  3. dopo che il ciclo termina

(anche se può essere temporaneamente falso durante il corpo del loop). D'altra parte il condizionale del ciclo deve essere falso dopo che il ciclo termina, altrimenti il ​​ciclo non terminerebbe mai.

Pertanto l'invariante del ciclo e il condizionale del ciclo devono essere condizioni diverse.

Un buon esempio di invariante a ciclo complesso è per la ricerca binaria.

bsearch(type A[], type a) {
start = 1, end = length(A)

    while ( start <= end ) {
        mid = floor(start + end / 2)

        if ( A[mid] == a ) return mid
        if ( A[mid] > a ) end = mid - 1
        if ( A[mid] < a ) start = mid + 1

    }
    return -1

}

Quindi il ciclo condizionale sembra piuttosto semplice: quando inizio> fine il ciclo termina. Ma perché il ciclo è corretto? Qual è l'invariante loop che dimostra la sua correttezza?

L'invariante è l'istruzione logica:

if ( A[mid] == a ) then ( start <= mid <= end )

Questa affermazione è una tautologia logica - è sempre vera nel contesto del ciclo / algoritmo specifico che stiamo cercando di dimostrare . E fornisce informazioni utili sulla correttezza del loop dopo la sua conclusione.

Se torniamo perché abbiamo trovato l'elemento nella matrice, allora l'istruzione è chiaramente vera, poiché se A[mid] == apoi aè nella matrice e middeve essere tra l'inizio e la fine. E se i termina ciclo perché start > endallora non ci può essere numero tale che start <= mid e mid <= end , e quindi sappiamo che la dichiarazione A[mid] == adeve essere falsa. Tuttavia, di conseguenza l'istruzione logica complessiva è ancora vera in senso null. (Nella logica l'istruzione if (false) then (qualcosa) è sempre vera.)

E che dire di ciò che ho detto sul fatto che il ciclo sia necessariamente falso quando il ciclo termina? Sembra che quando l'elemento viene trovato nella matrice, allora il condizionale del ciclo è vero quando il ciclo termina !? In realtà non lo è, perché il condizionale del loop implicito è davvero, while ( A[mid] != a && start <= end )ma abbreviamo il test effettivo poiché la prima parte è implicita. Questo condizionale è chiaramente falso dopo il ciclo indipendentemente da come termina il ciclo.


È strano che usare un'istruzione logica come invariante di loop, perché come tutte le istruzioni logiche possono essere sempre vere, indipendentemente dalle condizioni.
acgtyrant

Non è così strano che dovrei pensare, poiché non v'è alcuna garanzia che aè presente in A. Informalmente sarebbe "se la chiave aè presente nell'array, deve trovarsi tra starte endcompreso". Quindi ne consegue che se A[start..end]è vuoto, ciò anon è presente in A.
scanny

33

Le risposte precedenti hanno definito un invariante di loop in un modo molto buono.

Di seguito è riportato il modo in cui gli autori di CLRS hanno utilizzato il ciclo invariante per dimostrare la correttezza del criterio di inserimento.

Algoritmo di ordinamento inserzione (come indicato nel libro):

INSERTION-SORT(A)
    for j ← 2 to length[A]
        do key ← A[j]
        // Insert A[j] into the sorted sequence A[1..j-1].
        i ← j - 1
        while i > 0 and A[i] > key
            do A[i + 1] ← A[i]
            i ← i - 1
        A[i + 1] ← key

Loop Invariant in questo caso: l'array secondario [da 1 a j-1] viene sempre ordinato.

Ora controlliamo questo e dimostriamo che l'algoritmo è corretto.

Inizializzazione : prima della prima iterazione j = 2. Quindi il sub-array [1: 1] è l'array da testare. Poiché ha un solo elemento, viene ordinato. Quindi l'invariante è soddisfatto.

Manutenzione : questo può essere facilmente verificato controllando l'invariante dopo ogni iterazione. In questo caso è soddisfatto.

Terminazione : questo è il passaggio in cui dimostreremo la correttezza dell'algoritmo.

Quando il ciclo termina, quindi il valore di j = n + 1. Ancora una volta il ciclo invariante è soddisfatto. Ciò significa che l'array secondario [da 1 a n] deve essere ordinato.

Questo è ciò che vogliamo fare con il nostro algoritmo. Quindi il nostro algoritmo è corretto.


1
Accetto ... la dichiarazione di conclusione è così importante qui.
Gaurav Aradhye,

18

Oltre a tutte le buone risposte, immagino che un ottimo esempio tratto da How to Think About Algorithms, di Jeff Edmonds, possa illustrare molto bene il concetto:

ESEMPIO 1.2.1 "L'algoritmo Find-Max a due dita"

1) Specifiche: un'istanza di input è costituita da un elenco L (1..n) di elementi. L'output è costituito da un indice i tale che L (i) ha il valore massimo. Se sono presenti più voci con lo stesso valore, viene restituita una qualsiasi.

2) Passaggi di base: decidi tu con il metodo a due dita. Il dito destro scorre lungo l'elenco.

3) Misura del progresso: la misura del progresso è la lunghezza del dito destro lungo l'elenco.

4) Il loop invariante: il loop invariante afferma che il dito sinistro punta verso una delle voci più grandi incontrate finora dal dito destro.

5) Passaggi principali: a ogni iterazione, si sposta il dito destro in basso di una voce nell'elenco. Se il dito destro ora punta verso una voce più grande della voce del dito sinistro, quindi spostare il dito sinistro per stare con il dito destro.

6) Fai progressi: fai progressi perché il dito destro sposta di una voce.

7) Mantieni invarianza loop: sai che l'invariante loop è stata mantenuta come segue. Per ogni passaggio, il nuovo elemento del dito sinistro è Max (vecchio elemento del dito sinistro, nuovo elemento). Per invariante di loop, si tratta di Max (Max (elenco più breve), nuovo elemento). Matematicamente, questo è Max (elenco più lungo).

8) Stabilire il Loop Invariant: inizialmente si stabilisce il loop Invariant puntando entrambe le dita sul primo elemento.

9) Condizione di uscita: hai finito quando il dito destro ha finito di attraversare l'elenco.

10) Fine: alla fine, sappiamo che il problema è risolto come segue. In base alla condizione di uscita, il dito destro ha rilevato tutte le voci. Dall'invariante loop, il dito sinistro indica il massimo di questi. Ritorna questa voce.

11) Termine e tempo di esecuzione: il tempo richiesto è alcuni volte costanti la lunghezza dell'elenco.

12) Casi speciali: controlla cosa succede quando ci sono più voci con lo stesso valore o quando n = 0 o n = 1.

13) Dettagli di codifica e implementazione: ...

14) Prova formale: la correttezza dell'algoritmo segue dai passaggi precedenti.


Penso che questa risposta "metta davvero il dito" sull'intuizione intuitiva di un invariante :).
Scanny

6

Va notato che un Loop Invariant può aiutare nella progettazione di algoritmi iterativi se considerato un'asserzione che esprime relazioni importanti tra le variabili che devono essere vere all'inizio di ogni iterazione e quando termina il ciclo. Se questo vale, il calcolo è sulla strada per l'efficacia. Se falso, l'algoritmo non è riuscito.


5

Invariante in questo caso significa una condizione che deve essere vera ad un certo punto in ogni iterazione del ciclo.

Nella programmazione del contratto, un invariante è una condizione che deve essere vera (per contratto) prima e dopo che viene chiamato un metodo pubblico.


4

Il significato di invariante non è mai cambiato

Qui l'invariante del ciclo significa "Il cambiamento che accade alla variabile nel ciclo (incremento o decremento) non sta cambiando la condizione del ciclo, cioè la condizione è soddisfacente", così che il concetto invariante del ciclo è arrivato


2

La proprietà Loop Invariant è una condizione che vale per ogni fase dell'esecuzione di un loop (es. Per loop, mentre loop, ecc.)

Questo è essenziale per una prova invariante del ciclo, in cui si è in grado di dimostrare che un algoritmo viene eseguito correttamente se in ogni fase della sua esecuzione è valida questa proprietà invariante del ciclo.

Perché un algoritmo sia corretto, Loop Invariant deve contenere:

Inizializzazione (l'inizio)

Manutenzione (ogni passaggio successivo)

Terminazione (al termine)

Questo è usato per valutare un sacco di cose, ma l'esempio migliore sono gli algoritmi golosi per l'attraversamento di grafici ponderati. Affinché un algoritmo avido fornisca una soluzione ottimale (un percorso attraverso il grafico), deve raggiungere la connessione di tutti i nodi nel percorso di peso più basso possibile.

Pertanto, la proprietà invariante del loop è che il percorso preso ha il minor peso. Al principio non abbiamo aggiunto i bordi, in modo da questa proprietà è true (non è falso, in questo caso). Ad ogni passo , seguiamo il bordo di peso più basso (il passo goloso), quindi di nuovo stiamo prendendo il percorso di peso più basso. Alla fine , abbiamo trovato il percorso più basso ponderato, quindi anche la nostra proprietà è vera.

Se un algoritmo non lo fa, possiamo dimostrare che non è ottimale.


1

È difficile tenere traccia di ciò che sta accadendo con i loop. I cicli che non terminano o terminano senza raggiungere il loro comportamento obiettivo è un problema comune nella programmazione del computer. Gli invarianti di loop aiutano. Un invariante di loop è un'affermazione formale sulla relazione tra le variabili nel tuo programma che è vera appena prima che il loop sia mai eseguito (stabilendo l'invariante) ed è di nuovo vero nella parte inferiore del loop, ogni volta attraverso il loop (mantenendo l'invariante ). Ecco lo schema generale dell'uso di Loop Invariants nel tuo codice:

... // qui il Loop Invariant deve essere vero
mentre (TEST CONDITION) {
// inizio del loop
...
// fondo del loop
// il Loop Invariant deve essere vero qui
}
// Termination + Loop Invariant = Obiettivo
...
Tra la parte superiore e inferiore del circuito, si presume che vengano fatti progressi verso il raggiungimento dell'obiettivo del circuito. Questo potrebbe disturbare (rendere falso) l'invariante. Il punto di Loop Invariants è la promessa che l'invariante verrà ripristinato prima di ripetere ogni volta il corpo del loop. Ci sono due vantaggi in questo:

Il lavoro non viene portato al passaggio successivo in modi complicati e dipendenti dai dati. Ognuno passa attraverso il ciclo in modo indipendente da tutti gli altri, con l'invariante che serve a legare i passaggi insieme in un tutto funzionante. Il ragionamento sul funzionamento del loop è ridotto al ragionamento sul fatto che l'invariante del loop viene ripristinata ad ogni passaggio del loop. Ciò spezza il complicato comportamento generale del loop in piccoli e semplici passaggi, ciascuno dei quali può essere considerato separatamente. Le condizioni di test del loop non fanno parte dell'invariante. È ciò che fa terminare il loop. Consideri separatamente due cose: perché il loop dovrebbe mai terminare e perché il loop raggiunge il suo obiettivo quando termina. Il loop terminerà se ogni volta attraverso il loop ti avvicini al soddisfacimento della condizione di terminazione. Spesso è facile assicurarlo: ad es fare un passo di una variabile contatore di uno fino a raggiungere un limite superiore fisso. A volte il ragionamento alla base della risoluzione è più difficile.

L'invariante di loop deve essere creato in modo tale che quando viene raggiunta la condizione di terminazione e l'invariante è vero, l'obiettivo viene raggiunto:

invariante + terminazione => obiettivo
Ci vuole pratica per creare invarianti che siano semplici e correlati che catturino tutto il raggiungimento dell'obiettivo tranne che per la fine. È meglio usare simboli matematici per esprimere invarianti di loop, ma quando questo porta a situazioni troppo complicate, ci affidiamo a prosa chiara e buon senso.


1

Mi dispiace non ho il permesso di commento.

@Tomas Petricek come hai detto

Un invariante più debole che è anche vero è che i> = 0 && i <10 (perché questa è la condizione di continuazione!) "

Come è un invariante loop?

Spero di non sbagliarmi, per quanto ho capito [1] , l'invariante del ciclo sarà vera all'inizio del ciclo (inizializzazione), sarà vera prima e dopo ogni iterazione (manutenzione) e sarà anche vera dopo la terminazione del loop (terminazione) . Ma dopo l'ultima iterazione divento 10. Quindi, la condizione i> = 0 && i <10 diventa falsa e termina il ciclo. Infrange la terza proprietà (Termination) del loop invariant.

[1] http://www.win.tue.nl/~kbuchin/teaching/JBP030/notebooks/loop-invariants.html


La mia ipotesi è che questo sia vero perché il loop non si esegue effettivamente in quelle condizioni.
muiiu,

0

Il ciclo invariante è una formula matematica come (x=y+1). In questo esempio, xe yrappresentano due variabili in un ciclo. Considerando il comportamento cambiamento di tali variabili durante l'esecuzione del codice, è quasi impossibile testare tutto possibile xe yvalori e vedere se producono alcun errore. Diciamo che xè un numero intero. L'intero può contenere spazio a 32 bit nella memoria. Se quel numero supera, si verifica un overflow del buffer. Quindi dobbiamo essere sicuri che durante l'esecuzione del codice non superi mai quello spazio. per questo, dobbiamo capire una formula generale che mostra la relazione tra le variabili. Dopotutto, proviamo solo a capire il comportamento del programma.


0

In parole semplici, è una condizione LOOP che è vera in ogni iterazione di loop:

for(int i=0; i<10; i++)
{ }

In questo possiamo dire che lo stato di io è i<10 and i>=0


0

Un invariante di loop è un'asserzione vera prima e dopo l'esecuzione di loop.


-1

Nella ricerca lineare (come da esercizio indicato nel libro), dobbiamo trovare il valore V in un determinato array.

È semplice come scansionare l'array da 0 <= k <lunghezza e confrontare ogni elemento. Se V trovato, o se la scansione raggiunge la lunghezza dell'array, basta terminare il ciclo.

Secondo la mia comprensione nel problema sopra-

Loop Invariants (Inizializzazione): V non si trova nell'iterazione k - 1. Prima iterazione, questo sarebbe -1 quindi possiamo dire che V non si trova nella posizione -1

Mantenimento: nella prossima iterazione, V non trovato in k-1 è vero

Terminazione: se V trovato in posizione k o k raggiunge la lunghezza dell'array, terminare il loop.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.