Проанализируйте два варианта реализации вычисления чисел Фибоначчи на Python: рекурсивный без мемоизации и с мемоизацией — сравните временную и пространственную сложность, приведите пример входных данных, где разница критична
Реализации: - Рекурсивная без мемоизации: fib(n)=fib(n−1)+fib(n−2)fib(n)=fib(n-1)+fib(n-2)fib(n)=fib(n−1)+fib(n−2) с базами fib(0),fib(1)fib(0),fib(1)fib(0),fib(1). - Рекурсивная с мемоизацией: при первом вычислении fib(k)fib(k)fib(k) сохраняют результат в словаре/массиве и при повторных обращениях возвращают сохранённое значение. Временная сложность: - Без мемоизации: рекуррент T(n)=T(n−1)+T(n−2)+O(1)T(n)=T(n-1)+T(n-2)+O(1)T(n)=T(n−1)+T(n−2)+O(1) даёт экспоненциальный рост: T(n)=Θ(φn)T(n)=\Theta(\varphi^n)T(n)=Θ(φn), где φ=1+52≈1.618\varphi=\tfrac{1+\sqrt5}{2}\approx1.618φ=21+5≈1.618. Число вызовов функции примерно равно 2Fn+1−12F_{n+1}-12Fn+1−1 (где FkF_kFk — kkk-е число Фибоначчи). - С мемоизацией: каждый аргумент k≤nk\le nk≤n вычисляется один раз, поэтому время T(n)=Θ(n)T(n)=\Theta(n)T(n)=Θ(n) (с константной накладной на доступ/запись в таблицу). Пространственная сложность (доп. память + стек): - Без мемоизации: максимальная глубина стека вызовов =O(n)=O(n)=O(n); дополнительных больших структур нет, итого O(n)O(n)O(n) по глубине стека. - С мемоизацией: словарь/массив размера O(n)O(n)O(n) + стек глубиной O(n)O(n)O(n) (при рекурсивной реализации) → суммарно O(n)O(n)O(n). (Итеративный вариант может снизить память до O(1)O(1)O(1).) Пример, где разница критична: - Пусть n=40n=40n=40. Тогда число вызовов у наивной рекурсии примерно равно 2F41−12F_{41}-12F41−1. Численно F41=165580141F_{41}=165580141F41=165580141, следовательно вызовов ≈ 331160281331160281331160281. У мемоизации — вычисляется примерно n+1=41n+1=41n+1=41 значений. - На практике наивный вариант становится непригодным уже для n≳30n\gtrsim 30n≳30-404040 (секунды→минуты→часы), тогда как мемоизованный — линейно быстрый и работает мгновенно для тех же nnn. Краткий вывод: наивная рекурсия — экспоненциальное время и глубокий стек; мемоизация снижает время до линейного при памяти O(n)O(n)O(n). Для любых nnn больше ~30 используйте мемоизацию или итеративный алгоритм.
- Рекурсивная без мемоизации: fib(n)=fib(n−1)+fib(n−2)fib(n)=fib(n-1)+fib(n-2)fib(n)=fib(n−1)+fib(n−2) с базами fib(0),fib(1)fib(0),fib(1)fib(0),fib(1).
- Рекурсивная с мемоизацией: при первом вычислении fib(k)fib(k)fib(k) сохраняют результат в словаре/массиве и при повторных обращениях возвращают сохранённое значение.
Временная сложность:
- Без мемоизации: рекуррент T(n)=T(n−1)+T(n−2)+O(1)T(n)=T(n-1)+T(n-2)+O(1)T(n)=T(n−1)+T(n−2)+O(1) даёт экспоненциальный рост: T(n)=Θ(φn)T(n)=\Theta(\varphi^n)T(n)=Θ(φn), где φ=1+52≈1.618\varphi=\tfrac{1+\sqrt5}{2}\approx1.618φ=21+5 ≈1.618. Число вызовов функции примерно равно 2Fn+1−12F_{n+1}-12Fn+1 −1 (где FkF_kFk — kkk-е число Фибоначчи).
- С мемоизацией: каждый аргумент k≤nk\le nk≤n вычисляется один раз, поэтому время T(n)=Θ(n)T(n)=\Theta(n)T(n)=Θ(n) (с константной накладной на доступ/запись в таблицу).
Пространственная сложность (доп. память + стек):
- Без мемоизации: максимальная глубина стека вызовов =O(n)=O(n)=O(n); дополнительных больших структур нет, итого O(n)O(n)O(n) по глубине стека.
- С мемоизацией: словарь/массив размера O(n)O(n)O(n) + стек глубиной O(n)O(n)O(n) (при рекурсивной реализации) → суммарно O(n)O(n)O(n). (Итеративный вариант может снизить память до O(1)O(1)O(1).)
Пример, где разница критична:
- Пусть n=40n=40n=40. Тогда число вызовов у наивной рекурсии примерно равно 2F41−12F_{41}-12F41 −1. Численно F41=165580141F_{41}=165580141F41 =165580141, следовательно вызовов ≈ 331160281331160281331160281. У мемоизации — вычисляется примерно n+1=41n+1=41n+1=41 значений.
- На практике наивный вариант становится непригодным уже для n≳30n\gtrsim 30n≳30-404040 (секунды→минуты→часы), тогда как мемоизованный — линейно быстрый и работает мгновенно для тех же nnn.
Краткий вывод: наивная рекурсия — экспоненциальное время и глубокий стек; мемоизация снижает время до линейного при памяти O(n)O(n)O(n). Для любых nnn больше ~30 используйте мемоизацию или итеративный алгоритм.