Что такое инвариант цикла?

Я читаю" введение в алгоритм " CLRS. и авторы говорят об инвариантах цикла в главе 2 (сортировка вставки). Я понятия не имею, что это значит.

15 ответов


простыми словами, инвариант цикла - это некоторый предикат (условие), который выполняется для каждой итерации цикла. Например, давайте посмотрим на простой for цикл, который выглядит так:

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

в этом примере верно (для каждой итерации), что i + j == 9. Слабый инвариант, что это тоже правда, что i >= 0 && i <= 10.


мне нравится очень простое определение: (источник)

инвариант цикла-это условие [среди переменных программы], которое обязательно верно непосредственно перед и сразу после каждой итерации цикла. (Обратите внимание, что это ничего не говорит о его истинности или ложности частично через итерацию.)

сам по себе инвариант цикла не делает многого. Однако, учитывая соответствующий инвариант, его можно использовать для доказательства правильности алгоритм. Простой пример в CLRS, вероятно, связан с сортировкой. Например, пусть ваш инвариант цикла будет чем-то вроде, в начале цикла, первого i сортируются записи этого массива. Если вы можете доказать, что это действительно инвариант цикла (т. е. что он выполняется до и после каждой итерации цикла), вы можете использовать это для доказательства правильности алгоритма сортировки: в конце цикла инвариант цикла по-прежнему выполняется, а счетчик i - длина из массива. Поэтому первый i сортировка записей означает сортировку всего массива.

еще более простой пример: инварианты циклов, корректность и вывод программы.

как я понимаю, инвариант цикла является систематическим, формальным инструментом для рассуждения о программах. Мы делаем одно утверждение, которое мы фокусируем на доказательстве истинности, и мы называем его инвариантом цикла. Это упорядочивает нашу логику. В то время как мы можем просто неофициально спорить о правильность некоторого алгоритма, используя инвариант цикла, заставляет нас думать очень тщательно и гарантирует, что наши рассуждения герметичны.


есть одна вещь, которую многие люди не понимают сразу при работе с петлями и инвариантами. Они путаются между инвариантом цикла и условным циклом ( условием, которое управляет завершением цикла ).

как указывают люди, инвариант цикла должен быть истинным

  1. перед началом цикла
  2. перед каждой итерацией цикла
  3. после завершения цикла

( хотя он может временно быть ложным во время тела цикла ). С другой стороны, условный цикл должны будет false после завершения цикла, иначе цикл никогда не завершится.

таким образом, инвариант цикла и условного цикла должны быть разные условия.

хорошим примером инварианта комплексного цикла является двоичный поиск.

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

}

таким образом, цикл условный кажется довольно прямо вперед-когда start > end цикл завершается. Но почему петля правильная? Что такое инвариант цикла, который доказывает его правильность?

инвариантом является логическое утверждение:

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

это утверждение является логической тавтологией-оно всегда истинно в контексте конкретного цикла / алгоритма мы пытаемся доказать. И он предоставляет полезную информацию о правильности цикла после него прекращает.

если мы вернемся, потому что мы нашли элемент в массиве, то утверждение очевидно, так как если A[mid] == a затем a находится в массиве и mid должна быть между началом и концом. И если цикл завершается, потому что start > end тогда не может быть такого числа, что start <= mid и mid <= end и поэтому мы знаем, что заявление A[mid] == a должно быть false. Однако в результате общее логическое утверждение остается истинным в нулевом смысле. ( В логике утверждение if (false ) тогда ( something) всегда истинно. )

теперь как насчет того, что я сказал об условном цикле, обязательно ложном, когда цикл завершается? Похоже, когда элемент найден в массиве, условие цикла истинно, когда цикл завершается!? На самом деле это не так, потому что подразумеваемое условие цикла действительно while ( A[mid] != a && start <= end ) но мы сокращаем фактический тест, так как подразумевается первая часть. Это условие явно false после цикла независимо от того, как завершается цикл.


предыдущие ответы очень хорошо определили инвариант цикла.

теперь позвольте мне попытаться объяснить, как авторы CLRS использовали инварианты цикла для доказательства достоверность рода вставки.

алгоритм сортировки вставки(как указано в книге):

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

инвариант цикла в этом случае (источник: CLRS book): Subarray[1 to j-1] всегда сортируется.

теперь давайте проверим это и докажем, что алгоритм правильный.

инициализации: перед первой итерацией j=2. Таким образом, Subarray [1: 1] - это массив для тестирования.Поскольку он имеет только один элемент, он сортируется.Таким образом, инвариант удовлетворяется.

Мейнтаненс: Это можно легко проверить, проверив инвариант после каждой итерации.В этом случае он удовлетворен.

прекращение: это шаг, на котором мы докажем правильность алгоритм.

когда цикл завершается, то значение j=n+1. Снова выполняется инвариант цикла.Это означает, что Подрешетка[от 1 до n] должна быть отсортирована.

Это то, что мы хотим сделать с нашим алгоритмом.Таким образом, наш алгоритм верен.


помимо всех хороших ответов, я думаю, отличный пример из Как думать об алгоритмах, Джефф Эдмондс может проиллюстрировать концепцию очень хорошо:

пример 1.2.1 "алгоритм двух пальцев Find-Max"

1) Технические характеристики: входной экземпляр состоит из списка L (1..n) элементы. Выход состоит из индекса i такого, что L (i) имеет максимум значение. Если есть несколько записей с этим же значением, то любые один из них возвращается.

2) основные шаги: вы принимаете решение о методе двух пальцев. На пальце правой руки пробегает по списку.

3) мера прогресса: мера прогресса, как далеко вдоль список ваш правый палец.

4) Инвариант Цикла: инвариант петли заявляет, что ваш левый палец указывает на одну из самых больших записей, встреченных до сих пор вашим правый палец.

5) Основные шаги: каждая итерация, ты двигаешь правым пальцем вниз. запись в списке. Если ваш правый палец теперь указывает на запись это больше, чем вход левого пальца, а затем переместите левый палец, чтобы быть с правым пальцем.

6) прогресс: вы делаете прогресс, потому что ваш правый палец движется одна запись.

7) Поддерживать Инвариант Цикла: вы знаете, что инвариант цикла сохраняется следующим образом. Для каждого шага новый элемент левого пальца есть Max (старый элемент левого пальца, новый элемент). По инварианту цикла, это Max (Max (shorter list), новый элемент). Математически, это Макс(длинный список).

8) установление инварианта цикла: вы изначально устанавливаете инвариант петли, указывая обоими пальцами на первый элемент.

9) условие выхода: вы сделаны когда ваш правый палец заканчивал просматриваю список.

10) конец: в конце концов, мы знаем задача решается следующим образом. От при условии выхода, ваш правый палец столкнулся со всеми вступления. По инварианту петли ваш левый палец указывает на максимум из этих. Верните эту запись.

11) прекращение и продолжительность: требуемое время некоторая константа умножьте длину списка.

12) особые случаи: проверьте, что происходит, когда есть несколько записей с тем же значением, или, когда n = 0 или N = 1.

13) кодирования и детали внедрения: ...

14) формальное доказательство правильности алгоритма вытекает из вышеупомянутые шаги.


следует отметить, что инвариант цикла может помочь в разработке итерационных алгоритмов при рассмотрении утверждения, которое выражает важные отношения между переменными, которые должны быть истинными в начале каждой итерации и когда цикл завершается. Если это так, то расчет находится на пути к эффективности. Если false, то алгоритм не удалось.


инвариант в этом случае означает условие, которое должно быть истинным в определенной точке каждой итерации цикла.

в контрактном программировании инвариантом является условие, которое должно быть истинным (по контракту) до и после вызова любого открытого метода.


значение инварианта никогда не меняется

здесь инвариант цикла означает " изменение, которое происходит с переменной в цикле (инкремент или декремент), не изменяет условие цикла i.e условие удовлетворяет " так, что пришло инвариантное понятие цикла


трудно отслеживать, что происходит с петель. Циклы, которые не заканчиваются или заканчиваются без достижения их поведения цели, являются общей проблемой в компьютерном программировании. Помогают инварианты цикла. Инвариант цикла-это формальное утверждение о взаимосвязи между переменными в вашей программе, которое выполняется непосредственно перед запуском цикла (установление инварианта) и снова выполняется в нижней части цикла, каждый раз через цикл (поддержание инварианта). Вот общий шаблон использования инвариантов цикла в вашем коде:

... // инвариант цикла должен быть истинным здесь
while (условие тестирования ) {
// top of the loop
...
// нижняя часть петли
// инвариант цикла должен быть истинным здесь
}
// Завершение + Инвариант Цикла = Цель
...
Между верхней и нижней частью петли, прогресс предположительно делается к достижению петли цель. Это может нарушить (сделать ложным) инвариант. Точка инвариантов цикла-это обещание, что инвариант будет восстановлен перед повторением тела цикла каждый раз. В этом есть два преимущества:

работа не переносится на следующий проход сложными, зависящими от данных способами. Каждый проход через петлю независим от всех других, с инвариантом, служащим для связывания проходов вместе в рабочее целое. Рассуждение о том, что ваш цикл работает, сводится к рассуждению что инвариант цикла восстанавливается с каждым проходом через цикл. Это разбивает сложное общее поведение цикла на небольшие простые шаги, каждый из которых можно рассматривать отдельно. Тестовое условие цикла не является частью инварианта. Это то, что заставляет цикл завершаться. Вы рассматриваете отдельно две вещи: почему цикл должен когда-либо завершаться и почему цикл достигает своей цели, когда он завершается. Цикл завершится, если каждый раз через цикл вы приближаетесь к удовлетворяющ условие прекращения. Часто это легко гарантировать: например, шагая переменную счетчика по одному, пока она не достигнет фиксированного верхнего предела. Иногда причина прекращения сложнее.

инвариант цикла должен быть создан таким образом, чтобы при достижении условия окончания и истинности инварианта была достигнута цель:

инвариант + увольнение => цель
Требуется практика для создания инвариантов, которые просты и связаны которые охватывают все достижения цели, кроме прекращения. Лучше всего использовать математические символы для выражения инвариантов цикла, но когда это приводит к чрезмерно сложным ситуациям, мы полагаемся на ясную прозу и здравый смысл.


Извините, у меня нет разрешения на комментарий.

@Tomas Petricek, как вы упомянули

более слабый инвариант, который также истинен, заключается в том, что i >= 0 && i

Как это инвариант цикла?

надеюсь, я не ошибаюсь, насколько я понимаю[1], инвариант цикла будет истинным в начале цикла (инициализация), он будет истинным до и после каждой итерации (обслуживания) и это также будет верно после завершения цикла (завершения). Но после последней итерации я становлюсь 10. Таким образом, условие i >= 0 && i

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


свойство инварианта цикла является условием, которое выполняется для каждого шага выполнения циклов(т. е. для петли, а петли и т. д.)

Это важно для доказательства инварианта цикла, где можно показать, что алгоритм выполняется правильно, если на каждом шаге его выполнения это свойство инварианта цикла выполняется.

чтобы алгоритм был правильным, инвариант цикла должен иметь значение:

инициализации (the начало)

техническое обслуживание (каждый шаг)

прекращение (когда он закончил)

Это используется для оценки множества вещей, но лучшим примером являются жадные алгоритмы для обхода взвешенного графика. Чтобы жадный алгоритм дал оптимальное решение (путь по графу), он должен достичь соединения всех узлов в наименьшем возможном пути веса.

таким образом, свойство инварианта цикла состоит в том, что выбранный путь имеет наименьший вес. На начало мы не добавили ребер, поэтому это свойство true (в данном случае это не false). At каждый шаг, мы следуем за самым низким краем веса (жадный шаг), поэтому снова мы принимаем самый низкий путь веса. At конец, мы нашли самый низкий взвешенный путь, поэтому наше свойство также верно.

Если алгоритм этого не делает, мы можем доказать, что он не является оптимальным.


инвариант цикла-это математическая формула, такая как (x=y+1). В этом примере x и y представляют собой две переменные в цикле. Учитывая изменяющееся поведение этих переменных на протяжении всего выполнения кода, практически невозможно проверить все возможные x и y значения и посмотреть, если они производят какие-либо ошибки. Скажем x - целое число. Integer может содержать 32-битное пространство в памяти. Если это число превышает, происходит переполнение буфера. Поэтому мы должны быть уверены, что на протяжении всего выполнения кода он никогда не превышает это пространство. для этого нам нужно понять общую формулу, которая показывает связь между переменными. Ведь мы просто пытаемся понять поведение программы.


простыми словами, это условие цикла, которое истинно в каждой итерации цикла:

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

в этом мы можем сказать, что состояние i i<10 and i>=0


инвариант цикла-это утверждение, которое истинно до и после выполнения цикла.


в линейном поиске (согласно упражнению, приведенному в книге), нам нужно найти значение V в данном массиве.

его просто, как сканирование массива из 0

согласно моему пониманию в вышеуказанной проблеме -

Инварианты Цикла(Инициализация): V не найден в итерации k - 1. Очень первая итерация, это будет -1, поэтому мы можем сказать V не нашли в позиции -1

содержание: На следующей итерации V, не найденный в k-1, имеет значение true

Terminatation: Если V найдено в K позиции или k достигает длины массива, завершите цикл.