Есть одна вещь, которую многие люди не осознают сразу, имея дело с циклами и инвариантами.Они путаются между инвариантом цикла и условным циклом (условием, управляющим завершением цикла).
Как отмечают люди, инвариант цикла должен быть истинным
- до начала цикла
- перед каждой итерацией цикла
- послецикл завершается
(хотя он может временно быть ложным в теле цикла). С другой стороны, условный цикл должен быть ложным после завершения цикла, иначе цикл никогда не завершится.
Таким образом, инвариант цикла и условный цикл должны быть разными условиями.
Хороший пример инварианта сложного цикла - бинарный поиск.
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
}
Таким образом, условный цикл кажется довольно прямым - когда начало> конец цикла завершается.Но почему цикл правильный?Что такое инвариант цикла, который доказывает его правильность?
Инвариант - это логическое утверждение:
if ( A[mid] == a ) then ( start <= mid <= end )
Это утверждение является логической тавтологией - оно всегда верно в контекстеконкретный цикл / алгоритм, который мы пытаемся доказать .И он предоставляет полезную информацию о правильности цикла после его завершения.
Если мы вернемся, потому что нашли элемент в массиве, то утверждение явно верно, так как если A[mid] == a
, то a
находится вмассив и mid
должны находиться между началом и концом.И если цикл завершается из-за start > end
, то не может быть такого числа, чтобы start <= mid
и mid <= end
, и поэтому мы знаем, что утверждение A[mid] == a
должно быть ложным.Однако в результате общее логическое утверждение все еще верно в нулевом смысле.(В логике утверждение if (false) затем (что-то) всегда истинно.)
А что насчет того, что я сказал о условном цикле, который обязательно должен быть ложным, когда цикл завершается?Похоже, когда элемент найден в массиве, тогда условие цикла истина, когда цикл завершается !?На самом деле это не так, потому что условный цикл подразумевается действительно while ( A[mid] != a && start <= end )
, но мы сокращаем реальный тест, поскольку подразумевается первая часть.Это условие явно ложно после цикла независимо от того, как цикл завершается.