Рекурсия и рекурсивные алгоритмы: разбор для ЕГЭ
Если «Рекурсия и рекурсивные алгоритмы» кажется тёмным лесом — это нормально: тема собрана из нескольких простых идей. Покажем их по порядку, разберём пример и предупредим о ловушках экзамена.
- Понятие рекурсии и базовый случай: учимся не уходить в бесконечность
- Стек вызовов и глубина: почему падает программа
- Ручная трассировка: как просчитать алгоритм на черновике
- Разбор типового задания №16: пишем программный код
- Мемоизация и ускорение: что делать при зависании алгоритма
- Что запомнить для экзамена и план дальнейших действий
Понятие рекурсии и базовый случай: учимся не уходить в бесконечность
Задание 16 на ЕГЭ по информатике кажется простым, пока функция не выдаёт ошибку переполнения стека или не зависает прямо на экзамене. В этой статье мы по шагам разберём механику рекурсивных алгоритмов, научимся обходить частые ловушки составителей и автоматизировать решения на Python.
Рекурсия в программировании — это подход, при котором функция вызывает саму себя для решения подзадачи меньшего размера. Чтобы программа не зациклилась навсегда, любой корректный рекурсивный алгоритм обязан содержать две обязательные части:
1. Базовый случай (условие остановки): ветка вычислений, возвращающая конкретный ответ без повторного вызова функции.
2. Рекурсивный шаг (шаг редукции): вызов функцией самой себя с новыми аргументами, которые обязательно приближают вычисления к базовому случаю.
Представь стопку тетрадей: чтобы проверить самую нижнюю, нужно сначала снять все лежащие сверху. Если забыть базовое условие, программа будет пытаться снимать несуществующие тетради бесконечно и аварийно завершит работу.
Стек вызовов и глубина: почему падает программа
Каждый раз, когда функция совершает рекурсивный вызов, интерпретатор Python приостанавливает текущее вычисление. Он запоминает значения локальных переменных и адрес возврата в специальной области оперативной памяти — стеке вызовов (call stack).
Память стека организуется по правилу «последним пришёл — первым ушёл» (LIFO). Когда цепочка вызовов наконец достигает базового случая, стек начинает «сворачиваться» обратно: функция подставляет готовые числа в ожидающие выражения и отдаёт итоговый результат в точку первого запуска.
В Python по умолчанию установлен лимит глубины рекурсии ровно в 1000 вызовов. Если в задании требуется найти значение F(2025), стандартный запуск моментально завершится критической ошибкой RecursionError: maximum recursion depth exceeded. Увеличивай размер стека заранее через модуль sys.
Управлять лимитом вызовов легко: добавь команду sys.setrecursionlimit(5000) в самое начало скрипта. Проверить своё понимание работы стека на практике можно в личном кабинете «Просто Урок», где интерактивный тренажёр сразу подсвечивает опасные места в коде.
Ручная трассировка: как просчитать алгоритм на черновике
В номерах с небольшими аргументами (например, поиск F(5) или F(6)) надёжнее и быстрее посчитать значение функции прямо на бумаге. Для этого используется трассировочная таблица: мы вычисляем значения последовательно снизу вверх — от известного базового случая к целевому числу.
Рассмотрим алгоритм, заданный соотношениями:
F(1) = 1;
F(n) = F(n - 1) + 2 · n, при n > 1.
Построим пошаговую таблицу вычислений для значений аргумента от n = 1 до n = 5:
| Аргумент (n) | Формула перехода | Подстановка значений | Итог F(n) |
|---|---|---|---|
| 1 | Базовый случай | 1 | 1 |
| 2 | F(1) + 2 · 2 | 1 + 4 | 5 |
| 3 | F(2) + 2 · 3 | 5 + 6 | 11 |
| 4 | F(3) + 2 · 4 | 11 + 8 | 19 |
| 5 | F(4) + 2 · 5 | 19 + 10 | 29 |
Такой ручной подсчёт полностью исключает опечатки в коде на экзамене, когда глубина вычислений мала, а закономерность очевидна с первого взгляда.
Разбор типового задания №16: пишем программный код
На реальном экзамене часто встречаются формулы с условиями чётности и большими числами. Разберём типовую задачу: алгоритм вычисления значения функции F(n), где n — натуральное число, задан следующими соотношениями:
F(n) = 2 при n = 1;
F(n) = 3 · F(n - 1) + 1, если n > 1 и число n нечётно;
F(n) = F(n - 1) + n, если n > 1 и число n чётно.
Чему равно значение функции F(26)?
Перенесём математическое условие в программный код на языке Python:
def F(n):
\( if n == 1: \)
return 2
\( if n \% 2 != 0: \)
\( return 3 * F(n - 1) + 1 \)
\( return F(n - 1) + n \)
print(F(26))
Интерпретатор последовательно рассчитает вложенные вызовы и мгновенно выдаст ответ 413418. Главное — внимательно перенести знаки арифметических операций и условия проверки остатка от деления.
Если ветка условия завершается инструкцией return, не пиши лишний блок else на следующей строке. Это сохраняет плоскую структуру кода, предотвращает ошибки вложенности отступов и ускоряет чтение программы во время проверки.
Мемоизация и ускорение: что делать при зависании алгоритма
Если в теле функции присутствуют два и более рекурсивных вызова (например, F(n - 1) + F(n - 2)), количество операций растёт экспоненциально со скоростью 2ⁿ. Уже при n = 40 программа потребует миллиарды операций и намертво зависнет.
Причина медленной работы — повторный расчёт одних и тех же промежуточных значений. Чтобы программа работала за доли секунды, применяется мемоизация — автоматическое кэширование вычисленных результатов в оперативной памяти.
import sys
from functools import lru_cache
sys.setrecursionlimit(5000)
@lru_cache(None)
def F(n):
\( if n <= 2: \)
return 1
\( return F(n - 1) + 2 * F(n - 2) \)
print(F(400))
Декоратор @lru_cache(None) сохраняет ответ для каждого аргумента в хэш-таблице. Повторные обращения забирают готовое число за константное время O(1). Отработать технику кэширования на реальных прототипах ЕГЭ можно бесплатно через образовательную платформу.
Что запомнить для экзамена и план дальнейших действий
Для безошибочного выполнения задания 16 на ЕГЭ по информатике держи в голове ключевой чек-лист:
1. Всегда чётко формулируй базовый случай, чтобы исключить бесконечные циклы.
2. При вычислении больших номеров (n > 900) расширяй лимит вызовов через sys.setrecursionlimit().
3. Если функция ветвится на два и более вызова — обязательно подключай декоратор @lru_cache(None).
4. Внимательно проверяй операции целочисленного деления // и остатка % в теле условий.
Алгоритмическая база у тебя уже есть, теперь важно закрепить её на практике. Прямо сейчас переходи в личный кабинет, открывай тематический тренажёр и реши 5 типовых заданий по рекурсии, чтобы довести навык кодинга до автоматизма.
Частые вопросы
Чем рекурсивный вызов отличается от обычного цикла?
Цикл многократно выполняет блок инструкций в одном фрейме памяти, а рекурсия порождает цепочку независимых вызовов функций с сохранением их состояний в стеке.
Что делать, если программа выдаёт ошибку RecursionError?
Подключи модуль sys и увеличь глубину стека вызовом sys.setrecursionlimit(5000), а также убедись в наличии корректного базового случая.
Всегда ли обязательно использовать декоратор lru_cache?
Декоратор жизненно необходим только для древовидной рекурсии с несколькими вызовами F() в формуле, где без кэширования программа зависает от избыточных вычислений.
Как закрепить тему после разбора?
Авторизуйся в бесплатном личном кабинете на «Просто Урок», открой раздел 16-го задания и прорешай подборку свежих задач с моментальной автопроверкой решений.
Проверь себя на реальных заданиях
После такого разбора решите 5–7 заданий подряд — и тема ваша. Тренажёр Просто Урок подберёт их автоматически, ошибки объяснит по шагам. Бесплатно, в браузере.