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

# конспект · шаг 6 из 6

Конспект: индукция и рекурсия

выжимка - её можно унести в заметки

Понятия

Математическая индукция - доказательство утверждения для всех случаев двумя проверками: база и шаг. Шаг проверяется один раз для произвольного n.

Сильная индукция - в шаге разрешено опираться на все предыдущие случаи, а не только на соседний. Нужна, когда задача сводится к половине.

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

Контракт рекурсивной функции - формулировка того, что функция обещает. Внутри тела вложенный вызов читается как «здесь работа уже сделана правильно».

Дерево вызовов - узел это вызов, потомки это вызовы, которые он делает. Число узлов даёт время, глубина даёт память.

Три части рекурсии

Часть Что это Чем ломается
база случай, решаемый без вызовов базы нет или она поймана только при одном значении
шаг сведение к меньшему случаю и сборка ответа части шага переставлены местами
мера величина, строго убывающая при вызове убывает, но не ограничена снизу - уход в минус

Цена по форме дерева вызовов

Форма Время Глубина Пример
один вызов размера n-1 n n сумма списка рекурсией
два вызова размера n/2 n·log n log n сортировка слиянием
один вызов размера n/2 log n log n двоичный поиск
два вызова размера n-1 2^n n ханойские башни
два вызова, n-1 и n-2, с повторами 1,618^n n fib в лоб

Что унести

Инвариант цикла и есть индукция: инициализация это база, сохранение это шаг, завершение переводит доказанное в постусловие.

Шаг индукции проверяют на наименьшем значении, к которому его применяют. Разрыв между базой и областью применимости шага - это ровно та ошибка, из которой получаются «все лошади одной масти».

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

Порядок частей шага - содержательное требование. В ханойских башнях печать хода стоит между двумя вызовами: до первого большой диск завален, после второго переносить уже нечего.

Экспонента бывает свойством задачи и бывает дефектом решения. У Ханоя ходов действительно 2^n - 1; у fib той же величины можно достичь за n шагов, если не считать одно и то же дважды.

В Python нет оптимизации хвостового вызова, и предел глубины около тысячи кадров. Рекурсию глубиной n пишут только при заведомо малом n, глубиной log n - свободно.

Ловушки джокера

База поймана не тем случаем, что нужен. У возведения в степень if n == 1: return a виснет на нуле, и чинится это базой n == 0: return 1 - а не if n <= 1: return a, тогда a⁰ вернёт a вместо 1. У проверки палиндрома if levo == pravo вместо >= роняет строки чётной длины: там нужно именно неравенство.

Мера убывает, но не ограничена снизу. Формально «уменьшается каждый раз» - и вызовы уходят в отрицательные числа.

Переставил части индуктивного шага. Число вызовов остаётся верным, ответ - нет, и заметить это можно только на входе от двух элементов.

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

Решил, что ветвление само по себе даёт экспоненту. У сортировки слиянием ветвление есть, а экспоненты нет: подзадачи не повторяются и мельчают вдвое.

Проверь себя

Почему двух проверок индукции хватает для утверждения обо всех n?

Что такое контракт рекурсивной функции и почему он избавляет от прослеживания вызовов?

Какие две величины считают по дереву вызовов и какая из них отвечает за падение по стеку?

Чем экспонента ханойских башен отличается от экспоненты наивного Фибоначчи?

Комментарии

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

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

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