1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

# теория · шаг 3 из 6

Почему цикл обязан закончиться

Инвариант доказывает, что если цикл кончился, результат верен. Про «кончился» он не говорит ничего. Вторая половина доказательства выглядит проще, но именно её чаще всего пропускают - и получают программу, которая на одном входе из тысячи висит.

Как думаешь, какой из циклов не остановится

i = 0
while i < len(a):
    if a[i] < 0:
        a.pop(i)
    else:
        i = i + 1
i = len(a) - 1
while i >= 0:
    if a[i] < 0:
        a.pop(i)
    i = i - 1

Оба останавливаются, но по разным причинам, и первый из них - ловушка, знакомая всем, кто чистил список на месте. В первом i не растёт при удалении; зато уменьшается длина списка. Во втором i уменьшается всегда. У каждого есть величина, которая убывает и ограничена снизу, - и это единственное, что нужно доказать.

Мера убывания

Мерой называют целую неотрицательную величину, зависящую от переменных цикла, которая строго убывает на каждом круге. Если такая величина найдена, цикл конечен: убывать бесконечно целое число, ограниченное снизу нулём, не может.

Для второго цикла мера очевидна: i + 1. Для первого - len(a) - i: при удалении длина уменьшается, при переходе растёт i, и в обоих случаях разность падает на единицу.

Мера не обязана быть счётчиком. В алгоритме Евклида

while b != 0:
    a, b = b, a % b

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

Считаем вместе: сколько кругов делает Евклид

Меры хватило, чтобы доказать конечность. Она же часто даёт и оценку числа кругов. Посмотрим на два соседних круга Евклида: пара (a, b) превращается в (b, a mod b), а потом в (a mod b, b mod (a mod b)).

Если b ≤ a/2, то новое первое число уже не больше половины старого. Если b > a/2, то a mod b = a - b < a/2. В обоих случаях за два круга первое число уменьшается минимум вдвое.

Значит кругов не больше, чем 2·log₂ a. Для чисел до миллиарда это около шестидесяти кругов - вместо миллиарда у наивного перебора делителей. Логарифм по основанию два здесь означает ровно это: сколько раз число можно поделить пополам, пока не дойдёшь до единицы.

Разбор: цикл, у которого меры нет

n = 27
while n != 1:
    if n % 2 == 0:
        n = n // 2
    else:
        n = 3 * n + 1

Это гипотеза Коллатца. Величина n то падает вдвое, то растёт втрое с лишним, и никакой убывающей меры для неё не найдено. Проверено, что цикл кончается для всех начальных значений примерно до 2⁶⁸, но доказательства для всех нет уже почти сто лет.

Мораль не про Коллатца. Отсутствие меры - это не признак того, что цикл вечен; это признак того, что доказательства нет. Программа, у которой не получается назвать меру, требует либо перестройки, либо явного ограничения на число кругов.

Общий вопрос и почему он не решается

Напрашивается мысль: пусть машина сама проверяет, остановится ли программа. Такой проверки не существует - и это доказано, а не «пока не придумали». Утверждение называется неразрешимостью проблемы останова: нет алгоритма, который по тексту любой программы и её входу отвечает, завершится ли она. Полное доказательство разбирается в разделе про границы вычислимости; здесь достаточно следствия.

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

Что делать, когда мера не находится

Три честных выхода, все три используются в настоящем коде:

  • перестроить цикл так, чтобы мера появилась: заменить «пока не сойдётся» на фиксированное число итераций;
  • поставить потолок на число кругов и считать его превышение ошибкой - именно так устроена машина курса, и именно поэтому она отличает вечный цикл от верного, но слишком дорогого решения;
  • доказать конечность через свойство задачи, а не через переменную цикла: если на каждом круге некоторая целая величина растёт хотя бы на единицу и сверху ограничена, то кругов не больше этой границы. Так доказывают конечность алгоритма Форда-Фалкерсона (это про сети и потоки, разберём в своём разделе): величина потока растёт на целое число каждый круг и ограничена пропускной способностью сети - значит круги кончатся.

Теперь сам

Найди меру для цикла двоичного поиска из прошлого урока:

levo = 0
pravo = len(a)
while levo < pravo:
    seredina = (levo + pravo) // 2
    if a[seredina] < x:
        levo = seredina + 1
    else:
        pravo = seredina

Ответ. Мера - длина отрезка pravo - levo. Она неотрицательна, пока цикл идёт. Каждый круг она строго убывает: в первой ветви levo растёт минимум на единицу; во второй pravo становится равным seredina, а seredina < pravo, потому что деление нацело при levo < pravo даёт середину строго левее правой границы. Дополнительно видно, что отрезок уменьшается примерно вдвое, откуда и берётся log₂ n кругов.

Главное

Завершаемость доказывают мерой: целой неотрицательной величиной, строго убывающей на каждом круге. Мера не обязана быть счётчиком - ею бывает длина отрезка, остаток, размер оставшихся данных. Общего способа проверить остановку машиной нет и быть не может, поэтому меру называет автор.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

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