¿Qué es un bucle invariante?


268

Estoy leyendo "Introducción al algoritmo" por CLRS. En el capítulo 2, los autores mencionan "invariantes de bucle". ¿Qué es un bucle invariante?


44
Esto parece bastante bueno para explicar: cs.miami.edu/~burt/learning/Math120.1/Notes/LoopInvar.html
Tom Gullen


En caso de que alguien quiera resolver un problema de codificación algorítmica real basado en el concepto de invariante de bucle, consulte este problema en HackerRank. También se han referido al problema de clasificación de inserción solo para detallar el concepto.
RBT

También se pueden consultar las notas aquí para una comprensión teórica.
RBT

Respuestas:


345

En palabras simples, un bucle invariante es un predicado (condición) que se cumple para cada iteración del bucle. Por ejemplo, veamos un forbucle simple que se ve así:

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

En este ejemplo, es cierto (para cada iteración) que i + j == 9. Un invariante más débil que también es cierto es eso i >= 0 && i <= 10.


29
Este es un excelente ejemplo. Muchas veces cuando escuché a un instructor describir el ciclo invariante, simplemente ha sido "la condición del ciclo", o algo similar. Su ejemplo muestra que el invariante puede ser mucho más.
Brian S

77
No creo que este sea un buen ejemplo porque la invariante del bucle debería ser el objetivo del bucle ... CLRS lo usa para probar la corrección de un algoritmo de clasificación. Para la ordenación por inserción, suponiendo que el ciclo esté iterando con i, al final de cada ciclo, la matriz se ordena hasta el elemento i-ésimo.
Choque el

55
Sí, este ejemplo no está mal, pero no es suficiente. Retrocedo @Clash up, ya que el loop invariant debe presentar el objetivo, no solo por sí mismo.
Jack

77
@Tomas Petricek: cuando el ciclo termina, i = 10 y j = -1; por lo que el ejemplo invariante más débil que diste no sea el correcto (?)
Raja

77
Aunque estoy de acuerdo con los comentarios anteriores, he votado esta respuesta porque ... el objetivo no está definido aquí. Defina cualquier objetivo que encaje, y el ejemplo es excelente.
Flavio el

119

Me gusta esta definición muy simple: ( fuente )

Un bucle invariante es una condición [entre las variables del programa] que es necesariamente cierta inmediatamente antes e inmediatamente después de cada iteración de un bucle. (Tenga en cuenta que esto no dice nada sobre su verdad o falsedad en parte a través de una iteración).

Por sí solo, un bucle invariante no hace mucho. Sin embargo, dado un invariante apropiado, puede usarse para ayudar a probar la exactitud de un algoritmo. El ejemplo simple en CLRS probablemente tiene que ver con la ordenación. Por ejemplo, deje que su bucle invariante sea algo así, al comienzo del bucle, ise ordenan las primeras entradas de esta matriz. Si puede probar que esto es, de hecho, un bucle invariante (es decir, que se mantiene antes y después de cada iteración del bucle), puede usar esto para probar la corrección de un algoritmo de clasificación: al final del bucle, el bucle invariante aún está satisfecho , y el contador ies la longitud de la matriz. Por lo tanto, las primeras ientradas están ordenadas significa que toda la matriz está ordenada.

Un ejemplo aún más simple: Invariantes de bucles, corrección y derivación de programa .

La forma en que entiendo un bucle invariante es como una herramienta sistemática y formal para razonar sobre los programas. Hacemos una sola afirmación en la que nos enfocamos en demostrar que es verdadera, y la llamamos invariante del ciclo. Esto organiza nuestra lógica. Si bien podemos debatir informalmente sobre la corrección de algún algoritmo, el uso de un bucle invariante nos obliga a pensar con mucho cuidado y garantiza que nuestro razonamiento sea hermético.


10
Cabe señalar que "inmediatamente después de cada iteración" incluye una vez que termina el ciclo, independientemente de cómo haya terminado.
Robert S. Barnes

Muchas gracias por esta respuesta! Lo más importante es que el propósito de tener este bucle invariante es ayudar a demostrar la exactitud del algoritmo. ¡Las otras respuestas solo se centran en lo que es un bucle invariante!
Neekey

39

Hay una cosa que muchas personas no se dan cuenta de inmediato cuando se trata de bucles e invariantes. Se confunden entre el bucle invariante y el bucle condicional (la condición que controla la terminación del bucle).

Como la gente señala, el bucle invariante debe ser cierto

  1. antes de que comience el ciclo
  2. antes de cada iteración del bucle
  3. después de que termina el ciclo

(aunque puede ser temporalmente falso durante el cuerpo del bucle). Por otro lado, el bucle condicional debe ser falso después de que el bucle termina; de lo contrario, el bucle nunca terminaría.

Por lo tanto, el bucle invariante y el bucle condicional deben ser condiciones diferentes.

Un buen ejemplo de un bucle complejo invariante es para la búsqueda 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

}

Por lo tanto, el bucle condicional parece bastante sencillo: cuando inicio> fin, el bucle termina. Pero, ¿por qué es correcto el bucle? ¿Cuál es el bucle invariante que demuestra su corrección?

La invariante es la declaración lógica:

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

Esta afirmación es una tautología lógica: siempre es cierta en el contexto del bucle / algoritmo específico que estamos tratando de probar . Y proporciona información útil sobre la corrección del bucle después de que finaliza.

Si volvemos porque encontramos el elemento de la matriz a continuación, la declaración es claramente cierto, ya que si A[mid] == ase aestá en la matriz y middebe estar entre inicio y fin. Y si el bucle termina, porque start > endentonces no puede haber un número tal que start <= mid y mid <= end y por lo tanto sabemos que la declaración A[mid] == adebe ser falsa. Sin embargo, como resultado, la declaración lógica general sigue siendo cierta en sentido nulo. (En lógica, la declaración if (false) then (something) siempre es verdadera).

Ahora, ¿qué pasa con lo que dije sobre el bucle condicional que necesariamente es falso cuando el bucle termina? Parece que cuando el elemento se encuentra en la matriz, entonces el condicional del bucle es verdadero cuando el bucle termina. En realidad no lo es, porque el bucle condicional implícito es realmente while ( A[mid] != a && start <= end )pero acortamos la prueba real ya que la primera parte está implícita. Este condicional es claramente falso después del ciclo, independientemente de cómo termine el ciclo.


Es extraño usar una declaración lógica como invariante de bucle, porque como toda declaración lógica puede ser siempre cierta, sin importar en qué condición se encuentre.
acgtyrant

No es tan extraño, debería pensar, ya que no hay garantía de que aesté presente A. Informalmente sería, "si la clave aestá presente en la matriz, debe ocurrir entre starte endinclusive". Luego se deduce que si A[start..end]está vacío, eso ano está presente en A.
scanny

33

Las respuestas anteriores han definido un bucle invariante de una muy buena manera.

A continuación se muestra cómo los autores de CLRS usaron el bucle invariante para probar la corrección del tipo de inserción.

Algoritmo de clasificación de inserción (como se da en el 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

Invariante de bucle en este caso: la submatriz [1 a j-1] siempre se ordena.

Ahora verifiquemos esto y demostremos que el algoritmo es correcto.

Inicialización : antes de la primera iteración j = 2. Entonces sub-array [1: 1] es el array a ser probado. Como solo tiene un elemento, está ordenado. Así invariante está satisfecho.

Mantenimiento : Esto puede verificarse fácilmente marcando la invariante después de cada iteración. En este caso está satisfecho.

Terminación : este es el paso donde probaremos la corrección del algoritmo.

Cuando el ciclo termina, entonces el valor de j = n + 1. Nuevamente, el bucle invariante está satisfecho. Esto significa que la sub-matriz [1 a n] debe ordenarse.

Esto es lo que queremos hacer con nuestro algoritmo. Por lo tanto, nuestro algoritmo es correcto.


1
De acuerdo ... la declaración de terminación es tan importante aquí.
Gaurav Aradhye

18

Además de todas las buenas respuestas, creo que un gran ejemplo de Cómo pensar en algoritmos, de Jeff Edmonds, puede ilustrar muy bien el concepto:

EJEMPLO 1.2.1 "El algoritmo Find-Max de dos dedos"

1) Especificaciones: una instancia de entrada consiste en una lista L (1..n) de elementos. La salida consiste en un índice i tal que L (i) tiene un valor máximo. Si hay varias entradas con este mismo valor, se devuelve cualquiera de ellas.

2) Pasos básicos: usted decide el método de dos dedos. Su dedo derecho recorre la lista.

3) Medida del progreso: la medida del progreso es qué tan lejos de la lista está su dedo derecho.

4) La invariante del bucle: la invariante del bucle indica que su dedo izquierdo apunta a una de las entradas más grandes encontradas hasta ahora con su dedo derecho.

5) Pasos principales: en cada iteración, mueves el dedo derecho hacia abajo una entrada de la lista. Si su dedo derecho ahora apunta a una entrada que es más grande que la entrada del dedo izquierdo, entonces mueva el dedo izquierdo para estar con el derecho.

6) Progresar: progresa porque su dedo derecho mueve una entrada.

7) Mantener invariante de bucle: sabe que la invariante de bucle se ha mantenido de la siguiente manera. Para cada paso, el nuevo elemento del dedo izquierdo es Max (antiguo elemento del dedo izquierdo, nuevo elemento). Por el bucle invariante, este es Max (Max (lista más corta), nuevo elemento). Matemáticamente, este es Max (lista más larga).

8) Establecer la invariante del bucle: Inicialmente se establece la invariante del bucle apuntando ambos dedos al primer elemento.

9) Condición de salida: Ha terminado cuando su dedo derecho ha terminado de recorrer la lista.

10) Final: al final, sabemos que el problema se resuelve de la siguiente manera. Por la condición de salida, su dedo derecho ha encontrado todas las entradas. Por el bucle invariante, su dedo izquierdo apunta al máximo de estos. Devuelve esta entrada.

11) Terminación y tiempo de ejecución: el tiempo requerido es algunas veces constante la longitud de la lista.

12) Casos especiales: verifique qué sucede cuando hay múltiples entradas con el mismo valor o cuando n = 0 o n = 1.

13) Detalles de codificación e implementación: ...

14) Prueba formal: la corrección del algoritmo se deduce de los pasos anteriores.


Creo que esta respuesta realmente "pone su dedo" en la esencia intuitiva de un invariante :).
scanny

6

Cabe señalar que un Loop Invariant puede ayudar en el diseño de algoritmos iterativos cuando se considera una afirmación que expresa relaciones importantes entre las variables que deben ser verdaderas al comienzo de cada iteración y cuando el ciclo termina. Si esto se cumple, el cálculo está en camino a la efectividad. Si es falso, entonces el algoritmo ha fallado.


5

Invariante en este caso significa una condición que debe ser verdadera en un cierto punto en cada iteración del ciclo.

En la programación por contrato, una invariante es una condición que debe ser verdadera (por contrato) antes y después de que se llame a cualquier método público.


4

El significado de invariante nunca cambia

Aquí, el invariante del bucle significa "El cambio que sucede a la variable en el bucle (incremento o decremento) no está cambiando la condición del bucle, es decir, la condición es satisfactoria", de modo que el concepto invariante del bucle ha llegado


2

La propiedad invariante de bucle es una condición que se cumple para cada paso de la ejecución de un bucle (es decir, para bucles, bucles while, etc.)

Esto es esencial para una Prueba Invariante de Bucle, donde uno puede demostrar que un algoritmo se ejecuta correctamente si en cada paso de su ejecución se mantiene esta propiedad invariante de bucle.

Para que un algoritmo sea correcto, la Invariante de bucle debe mantenerse en:

Inicialización (el comienzo)

Mantenimiento (cada paso después)

Terminación (cuando está terminado)

Esto se utiliza para evaluar un montón de cosas, pero el mejor ejemplo son los algoritmos codiciosos para el cruce de gráficos ponderados. Para que un algoritmo codicioso produzca una solución óptima (una ruta a través del gráfico), debe llegar a conectar todos los nodos en la ruta de menor peso posible.

Por lo tanto, la propiedad invariante del bucle es que la ruta tomada tiene el menor peso. Al principio no hemos agregado ningún borde, por lo que esta propiedad es verdadera (no es falsa, en este caso). En cada paso , seguimos el borde de menor peso (el paso codicioso), por lo que nuevamente estamos tomando la ruta de menor peso. Al final , hemos encontrado la ruta más baja ponderada, por lo que nuestra propiedad también es cierta.

Si un algoritmo no hace esto, podemos demostrar que no es óptimo.


1

Es difícil hacer un seguimiento de lo que sucede con los bucles. Los bucles que no terminan o terminan sin lograr su objetivo de comportamiento es un problema común en la programación de computadoras. Los invariantes de bucle ayudan. Un ciclo invariante es una declaración formal sobre la relación entre las variables en su programa que es verdadera justo antes de que el ciclo se ejecute (estableciendo el invariante) y es verdadera nuevamente en la parte inferior del ciclo, cada vez a través del ciclo (manteniendo el invariante ) Aquí está el patrón general del uso de invariantes de bucle en su código:

... // la Invariante del bucle debe ser verdadera aquí
mientras que (CONDICIÓN DE PRUEBA) {
// parte superior del bucle
...
// parte inferior del bucle
// la Invariante del bucle debe ser verdadera aquí
}
// Terminación + Invariante del bucle = Meta
...
Entre la parte superior e inferior del bucle, presumiblemente se avanza hacia alcanzar la meta del bucle. Esto podría perturbar (hacer falso) lo invariante. El punto de las invariantes de bucle es la promesa de que la invariante se restaurará antes de repetir el cuerpo del bucle cada vez. Hay dos ventajas para esto:

El trabajo no se traslada al siguiente paso de formas complicadas que dependen de los datos. Cada paso a través del bucle es independiente de todos los demás, y el invariante sirve para unir los pases en un todo funcional. El razonamiento de que su ciclo funciona se reduce al razonamiento de que el ciclo invariante se restaura con cada pasada a través del ciclo. Esto divide el complicado comportamiento general del bucle en pequeños pasos simples, cada uno de los cuales puede considerarse por separado. La condición de prueba del bucle no es parte de la invariante. Es lo que hace que el ciclo termine. Considera por separado dos cosas: por qué el ciclo debería terminar alguna vez, y por qué el ciclo logra su objetivo cuando termina. El bucle terminará si cada vez que se acerque al bucle se acerque para satisfacer la condición de terminación. A menudo es fácil asegurar esto: por ejemplo pasando una variable de contador por una hasta que alcance un límite superior fijo. A veces el razonamiento detrás de la terminación es más difícil.

El bucle invariante debe crearse de modo que cuando se alcance la condición de terminación y el invariante sea verdadero, se alcance el objetivo:

invariante + terminación => objetivo
Se necesita práctica para crear invariantes que sean simples y se relacionen y que capturen todo el logro del objetivo, excepto la terminación. Es mejor usar símbolos matemáticos para expresar invariantes de bucle, pero cuando esto lleva a situaciones demasiado complicadas, confiamos en una prosa clara y sentido común.


1

Lo siento, no tengo permiso para comentar.

@Tomas Petricek como mencionaste

Una invariante más débil que también es cierta es que i> = 0 && i <10 (¡porque esta es la condición de continuación!) "

¿Cómo es un bucle invariante?

Espero no estar equivocado, por lo que entiendo [1] , la invariante de bucle será verdadera al comienzo del bucle (Inicialización), será verdadera antes y después de cada iteración (Mantenimiento) y también será verdadera después La terminación del bucle (terminación) . Pero después de la última iteración, se convierte en 10. Entonces, la condición i> = 0 && i <10 se convierte en falsa y termina el ciclo. Viola la tercera propiedad (Terminación) del bucle invariante.

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


Supongo que esto es cierto porque el bucle no se ejecuta en esas condiciones.
muiiu

0

Loop invariant es una fórmula matemática como (x=y+1). En ese ejemplo, xy yrepresentan dos variables en un bucle. Teniendo en cuenta el cambio de comportamiento de esas variables a lo largo de la ejecución del código, es casi imposible probar todo posible xy ylos valores y ver si se produce ningún error. Digamos que xes un número entero. El entero puede contener un espacio de 32 bits en la memoria. Si ese número excede, se produce un desbordamiento del búfer. Por lo tanto, debemos asegurarnos de que durante la ejecución del código, nunca exceda ese espacio. para eso, necesitamos entender una fórmula general que muestre la relación entre variables. Después de todo, solo tratamos de entender el comportamiento del programa.


0

En palabras simples, es una condición LOOP que es verdadera en cada iteración de bucle:

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

En esto podemos decir que el estado de i es i<10 and i>=0


0

Un bucle invariante es una afirmación que es verdadera antes y después de la ejecución del bucle.


-1

En la búsqueda lineal (según el ejercicio dado en el libro), necesitamos encontrar el valor V en una matriz dada.

Es simple como escanear la matriz desde 0 <= k <longitud y comparar cada elemento. Si se encuentra V, o si el escaneo alcanza la longitud de la matriz, simplemente finalice el ciclo.

Según mi comprensión en el problema anterior

Invariantes de bucle (inicialización): V no se encuentra en la iteración k - 1. Primera iteración, esto sería -1, por lo tanto, podemos decir que V no se encuentra en la posición -1

Mantenimiento: en la próxima iteración, V no encontrado en k-1 es verdadero

Terminación: si V se encuentra en la posición k o k alcanza la longitud de la matriz, finalice el bucle.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.