PythonPython 8. Рекурсия0%
Python для ЕГЭ · тема 8 из 11

Рекурсия

Функция, которая вызывает саму себя. Звучит страшно, но в №16 это просто переписать условие в код. Плюс lru_cache, без которого рекурсия на ЕГЭ не успевает.

6 разделов~40 минут№16, 13, 19–21
  1. 1Функция вызывает себя
  2. 2Переписываем условие №16
  3. 3lru_cache: память для функции
  4. 4Большие n
  5. 5Сломай код
  6. 6Где пригодится на ЕГЭ
Раздел 1 из 6

Функция вызывает себя

Факториал числа n это 1 · 2 · 3 · … · n. Заметим, что факториал n это n, умноженное на факториал n − 1. А факториал 1 просто 1. Так и запишем:

python
def fact(n):
    if n == 1:
        return 1                # база: ответ известен сразу
    return n * fact(n - 1)      # шаг: сводим к задаче поменьше

print(fact(5))

Как это считается: fact(5) просит fact(4), тот просит fact(3), и так до fact(1), который сразу отвечает 1. Потом ответы возвращаются обратно наверх: 1, 2, 6, 24, 120.

У любой рекурсивной функции две части:

Раздел 2 из 6

Переписываем условие №16

В №16 функцию дают готовой, словами или формулами. Нужно аккуратно перенести её в Python, строчка в строчку. Например:

F(n) = 1 при n ≤ 2; F(n) = F(n − 1) + 2 · F(n − 2) при n > 2.

Чему равно F(20)?

python
def F(n):
    if n <= 2:
        return 1
    return F(n - 1) + 2 * F(n - 2)

print(F(20))

Каждая строка условия стала строкой кода. Первое условие это база, второе шаг. Если условий больше двух, пишем их через if и elif в том же порядке, что в задаче.

Проверь на маленьком

Посчитай руками F(3) и F(4): F(3) = 1 + 2 · 1 = 3, F(4) = 3 + 2 · 1 = 5. Выведи их кодом. Совпало, значит, функция переписана верно, и можно считать F(20).

Попробуй самучебная задача

Чему равно F(20) для функции из примера?

Раздел 3 из 6

lru_cache: память для функции

Есть проблема. Попробуем числа Фибоначчи: каждое равно сумме двух предыдущих.

python
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(32))

Заметно задумалось. А fib(40) уже не дождёшься. Причина: fib(32) вызывает fib(31) и fib(30), а fib(31) снова вызывает fib(30). Одно и то же считается миллионы раз.

Решение: пусть функция запоминает уже посчитанные ответы. Для этого есть lru_cache. Пишем его строкой над def:

python
from functools import lru_cache

@lru_cache(None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(32))
print(fib(100))

Мгновенно. Теперь каждое fib(k) считается один раз, а дальше берётся из памяти.

Правило

Если в №16 функция вызывает себя два раза или больше, сразу ставь @lru_cache(None). Хуже не будет, а без него код может не досчитать.

Раздел 4 из 6

Большие n

В современных №16 часто спрашивают что-то вроде «найдите значение F(2024) / F(2021)». Пробуем:

python
from functools import lru_cache

@lru_cache(None)
def F(n):
    if n == 1:
        return 1
    return n * F(n - 1)

print(F(2024) // F(2021))

RecursionError: maximum recursion depth exceeded. Python не даёт функции вызывать себя глубже примерно тысячи раз подряд, а тут нужно больше двух тысяч.

Самый надёжный способ обойти это: посчитать функцию по порядку снизу вверх. Тогда каждый вызов находит ответ для n − 1 уже в памяти и не уходит вглубь:

python
from functools import lru_cache

@lru_cache(None)
def F(n):
    if n == 1:
        return 1
    return n * F(n - 1)

for i in range(1, 2025):     # заполняем память: F(1), F(2), ..., F(2024)
    F(i)

print(F(2024) // F(2021))

Работает, и ответ 2024 · 2023 · 2022.

А setrecursionlimit?

В разборах часто пишут sys.setrecursionlimit(10000), чтобы разрешить глубже. На станции КЕГЭ это обычно работает, но при очень большой глубине Python может просто вылететь без сообщения. Цикл снизу вверх надёжнее, и на сайте он работает всегда.

Попробуй самучебная задача

Чему равно F(2024) / F(2021) для функции из примера?

Раздел 5 из 6

Сломай код

Нет базы

python
def F(n):
    return n * F(n - 1)

print(F(5))

RecursionError. Функция вызывает F(4), F(3), F(2), F(1), F(0), F(−1) и дальше без конца, пока Python не остановит. Первое, что проверяешь при этой ошибке: есть ли база и достижима ли она.

База, до которой не дойти

python
def F(n):
    if n == 0:
        return 1
    return F(n - 2) + 1

print(F(5))

Снова RecursionError. Из 5 через шаг −2 получаются 3, 1, −1, −3… До нуля мы никогда не попадём. В условии №16 база обычно с неравенством, n <= 1, как раз чтобы такого не было. Переписывай знак ровно как в задаче.

Забыли return

python
def F(n):
    if n == 1:
        return 1
    n * F(n - 1)

print(F(3))

unsupported operand type(s) for *: 'int' and 'NoneType'. Во второй ветке посчитали, но не вернули. Функция без return отдаёт None.

Раздел 6 из 6

Где пригодится на ЕГЭ

НомерЧто из темы
№16переписать функцию из условия, lru_cache, подсчёт снизу вверх
№13число программ исполнителя: рекурсия с двумя аргументами (с 2027 года это №13)
№19–21теория игр: функция, которая перебирает ходы и вызывает себя

Пример задачи про число программ. Исполнитель умеет прибавлять 1 и умножать на 2. Сколько существует программ, которые превращают 2 в 30?

python
from functools import lru_cache

@lru_cache(None)
def count(a, b):          # сколько программ из a в b
    if a > b:
        return 0          # перескочили, назад дороги нет
    if a == b:
        return 1          # пришли: одна программа, пустая
    return count(a + 1, b) + count(a * 2, b)

print(count(2, 30))

Тот же шаблон: база (перескочили или пришли) и шаг (сумма по всем командам). В уроке по номеру добавятся условия «проходит через» и «избегает».

Попробуй самучебная задача

Сколько существует программ, которые превращают 1 в 10 командами «прибавь 1» и «умножь на 2»?

Код в блоках можно менять и запускать прямо в браузере.