v1.0.0

Python с нуля для ЕГЭ · Рекурсия: базовый случай, шаг и трассировка

Урок курсаPython 35 задачБесплатно

Рекурсия: базовый случай, шаг и трассировка

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

Теория

Задача уменьшается до простого случая

Рекурсивная функция вызывает саму себя с более простой версией задачи. Базовый случай даёт ответ без нового вызова, а рекурсивный шаг гарантированно приближает аргумент к нему.

Нужны обе части. Без базового случая вызовы не остановятся; без уменьшения задачи базовый случай может существовать в коде, но никогда не быть достигнут.

def sum_to(number):    if number == 0:        return 0    return number + sum_to(number - 1)

Вызовы сначала углубляются, потом возвращаются

При sum_to(3) первый вызов не знает окончательного ответа, пока не завершится sum_to(2). Цепочка углубляется до нуля, а затем значения возвращаются в обратном порядке.

Для трассировки разделите лист на две части: аргументы при входе и результаты при возврате. Это не смешивает два направления движения.

Разберём на примере
Развернём sum_to(3)
Каждый шаг уменьшает number на единицу.
  1. Входящие аргументы: 3, 2, 1, 0.
  2. Базовый вызов с нулём возвращает 0.
  3. Возвраты собирают 1, затем 3, затем 6.
Проверьте себя
Почему при трассировке рекурсии полезно отдельно записывать входы и возвраты?
Сначала создаётся цепочка вложенных вызовов, затем результаты идут обратно. Раздельная запись не смешивает эти два порядка.

Та же модель встречается в задачах ЕГЭ

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

def f(number):    if number <= 1:        return 1    return f(number - 1) + f(number - 2)

Для f(3) понадобятся f(2) и f(1), а для f(2) — f(1) и f(0). Сначала подпишите обе единицы в базе, затем сложите возвраты снизу вверх: f(2) равно 2, f(3) равно 3.

Базовый случай должен быть достижим

Проверка number == 0 не спасёт функцию, если положительный аргумент на каждом шаге увеличивается. Расстояние до нуля растёт, и цепочка закончится RecursionError.

Как проверять рекурсию

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

Как действовать
Трассируем два направления
  1. Найдите базовый случай. Запишите его аргумент и немедленный результат.
  2. Проверьте уменьшение. Сравните аргумент текущего и следующего вызова.
  3. Раскройте вызовы. Дойдите до базы, не вычисляя возвраты заранее.
  4. Соберите ответы. Возвращайтесь по цепочке в обратном порядке.

Практика

Найдите базовый случай

Факториал определён так: factorial(0) возвращает 1, иначе возвращается n * factorial(n - 1). Что вернёт factorial(0)?

Подсказка
Для базового случая новый вызов не выполняется.
Решение

При n = 0 функция сразу возвращает 1. Именно этот ответ останавливает цепочку вызовов.

countdown(n) вызывает countdown(n - 1), пока n не станет 0. Сколько всего вызовов произойдёт для countdown(3), включая первый и базовый?

Подсказка
Запишите значения n в каждом вызове.
Решение

Значения n равны 3, 2, 1 и 0. Получается четыре вызова.

Проследите возврат результата

sum_to(0) возвращает 0, иначе n + sum_to(n - 1). Что вернёт sum_to(3)?

Подсказка
Разверните выражение до базового случая.
Решение

Получается 3 + 2 + 1 + 0 = 6. После базового случая результаты возвращаются в обратном порядке.

Назовите ошибку без остановки

Какой тип ошибки обычно завершает рекурсивную функцию, которая никогда не достигает базового случая?

Подсказка
Число вложенных вызовов не может расти бесконечно.
Решение

Python ограничивает глубину вызовов. Когда предел исчерпан, возникает `RecursionError`.

Проверьте рекурсивную степень

power2(0) возвращает 1, иначе 2 * power2(n - 1). Что вернёт power2(4)?

Подсказка
Получается четыре множителя 2 и базовая единица.
Решение

Вызовы разворачиваются до нуля, затем возвращают 1, 2, 4, 8 и 16.

Итог

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

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

Прогресс

0 / 5
x

Вы ещё не решали задания