Код: `def fib(n): return n if n<2 else fib(n-1)+fib(n-2)` Как работает (кратко) - Для n<2n<2n<2 функция возвращает nnn (базовые случаи). - Иначе вычисляет рекурсивно fib(n−1)fib(n-1)fib(n−1) и fib(n−2)fib(n-2)fib(n−2) и суммирует: fib(n)=fib(n−1)+fib(n−2)fib(n)=fib(n-1)+fib(n-2)fib(n)=fib(n−1)+fib(n−2). - Это прямая рекурсивная реализация определения чисел Фибоначчи. Анализ сложности - Рекуррент для числа вызовов/времени: 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\varphi=\frac{1+\sqrt{5}}{2}φ=21+5 (золотое сечение). - Пространство (стек вызовов): глубина рекурсии =O(n)=O(n)=O(n) (в худшем случае). - Числовые величины растут экспоненциально: число Фибоначчи FnF_nFn имеет длину в битах Θ(n)\Theta(n)Θ(n), поэтому при больших nnn стоимость арифметики тоже существенна. Проблемы - Экспоненциальное время из‑за многократного повторного вычисления одних и тех же подзадач. - Глубокая рекурсия — риск переполнения стека в Python. - Нет проверки входных данных (на целое, неотрицательное). - При больших nnn арифметика больших целых становится узким местом. Оптимизации и анализ выигрыша 1) Мемоизация (top‑down) - Идея: хранить уже вычисленные значения в словаре или использовать `functools.lru_cache`. - Пример: `@lru_cache(None)\ndef fib(n): return n if n<2 else fib(n-1)+fib(n-2)` - Сложность: время O(n)O(n)O(n), память O(n)O(n)O(n) (таблица + стек). - Выигрыш: из экспоненциального в линейное — огромный для больших nnn (примерно фактор Θ(φn/n)\Theta(\varphi^n/n)Θ(φn/n)). 2) Итеративный (bottom‑up) с O(1)O(1)O(1) памятью - Идея: пройти от 000 до nnn, хранить только последние два значения. - Код: `a,b=0,1\nfor _ in range(n): a,b=b,a+b\nreturn a` - Сложность: время O(n)O(n)O(n), память O(1)O(1)O(1). - Выигрыш: сравним с мемоизацией по времени, но без затрат на стек и с меньшим потреблением памяти/накладными расходами. 3) Быстрое удвоение (fast doubling) - Формулы: F2k=Fk (2Fk+1−Fk),F2k+1=Fk+12+Fk2.F_{2k}=F_k\,(2F_{k+1}-F_k),\qquad F_{2k+1}=F_{k+1}^2+F_k^2.F2k=Fk(2Fk+1−Fk),F2k+1=Fk+12+Fk2.
- Рекурсивно/итеративно вычисляет FnF_nFn за O(logn)O(\log n)O(logn) арифметических операций. - Сложность по операциям: O(logn)O(\log n)O(logn). С учётом стоимости умножений больших чисел — O(M(b)logn)O(M(b)\log n)O(M(b)logn), где b=Θ(n)b=\Theta(n)b=Θ(n) — длина числа в битах, M(b)M(b)M(b) — стоимость умножения. - Выигрыш: асимптотически наилучший по времени для больших nnn (переход от линейного к логарифмическому числу операций). 4) Матрица + быстрое возведение в степень - Связь: (1110)n\begin{pmatrix}1&1\\1&0\end{pmatrix}^n(1110)n даёт числа Фибоначчи. - Используя бинарное возведение в степень — требуемое число матричных умножений O(logn)O(\log n)O(logn). - Сложность и замечания аналогичны быстрому удвоению. 5) Приближённая формула (Бине) - Формула: Fn=φn−(−φ)−n5F_n=\frac{\varphi^n-(-\varphi)^{-n}}{\sqrt{5}}Fn=5φn−(−φ)−n. Приближённо можно взять Fn≈round(φn/5)F_n\approx\mathrm{round}(\varphi^n/\sqrt{5})Fn≈round(φn/5). - Быстро (O(1)O(1)O(1)), но ненадёжно для больших nnn из‑за погрешностей плавающей арифметики. Рекомендации - Для практического использования: для небольших/умеренных nnn используйте итеративную реализацию (простота + O(1)O(1)O(1) памяти). Для многократных вызовов — мемоизация. - Для очень больших nnn (когда важна скорость) — реализовать fast doubling или матричное экспоненцирование. - Всегда проверяйте вход (целое неотрицательное) и учитывайте, что арифметика больших целых даёт рост затрат пропорционально длине числа.
Как работает (кратко)
- Для n<2n<2n<2 функция возвращает nnn (базовые случаи).
- Иначе вычисляет рекурсивно fib(n−1)fib(n-1)fib(n−1) и fib(n−2)fib(n-2)fib(n−2) и суммирует: fib(n)=fib(n−1)+fib(n−2)fib(n)=fib(n-1)+fib(n-2)fib(n)=fib(n−1)+fib(n−2).
- Это прямая рекурсивная реализация определения чисел Фибоначчи.
Анализ сложности
- Рекуррент для числа вызовов/времени: 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\varphi=\frac{1+\sqrt{5}}{2}φ=21+5 (золотое сечение).
- Пространство (стек вызовов): глубина рекурсии =O(n)=O(n)=O(n) (в худшем случае).
- Числовые величины растут экспоненциально: число Фибоначчи FnF_nFn имеет длину в битах Θ(n)\Theta(n)Θ(n), поэтому при больших nnn стоимость арифметики тоже существенна.
Проблемы
- Экспоненциальное время из‑за многократного повторного вычисления одних и тех же подзадач.
- Глубокая рекурсия — риск переполнения стека в Python.
- Нет проверки входных данных (на целое, неотрицательное).
- При больших nnn арифметика больших целых становится узким местом.
Оптимизации и анализ выигрыша
1) Мемоизация (top‑down)
- Идея: хранить уже вычисленные значения в словаре или использовать `functools.lru_cache`.
- Пример: `@lru_cache(None)\ndef fib(n): return n if n<2 else fib(n-1)+fib(n-2)`
- Сложность: время O(n)O(n)O(n), память O(n)O(n)O(n) (таблица + стек).
- Выигрыш: из экспоненциального в линейное — огромный для больших nnn (примерно фактор Θ(φn/n)\Theta(\varphi^n/n)Θ(φn/n)).
2) Итеративный (bottom‑up) с O(1)O(1)O(1) памятью
- Идея: пройти от 000 до nnn, хранить только последние два значения.
- Код: `a,b=0,1\nfor _ in range(n): a,b=b,a+b\nreturn a`
- Сложность: время O(n)O(n)O(n), память O(1)O(1)O(1).
- Выигрыш: сравним с мемоизацией по времени, но без затрат на стек и с меньшим потреблением памяти/накладными расходами.
3) Быстрое удвоение (fast doubling)
- Формулы:
F2k=Fk (2Fk+1−Fk),F2k+1=Fk+12+Fk2.F_{2k}=F_k\,(2F_{k+1}-F_k),\qquad F_{2k+1}=F_{k+1}^2+F_k^2.F2k =Fk (2Fk+1 −Fk ),F2k+1 =Fk+12 +Fk2 . - Рекурсивно/итеративно вычисляет FnF_nFn за O(logn)O(\log n)O(logn) арифметических операций.
- Сложность по операциям: O(logn)O(\log n)O(logn). С учётом стоимости умножений больших чисел — O(M(b)logn)O(M(b)\log n)O(M(b)logn), где b=Θ(n)b=\Theta(n)b=Θ(n) — длина числа в битах, M(b)M(b)M(b) — стоимость умножения.
- Выигрыш: асимптотически наилучший по времени для больших nnn (переход от линейного к логарифмическому числу операций).
4) Матрица + быстрое возведение в степень
- Связь: (1110)n\begin{pmatrix}1&1\\1&0\end{pmatrix}^n(11 10 )n даёт числа Фибоначчи.
- Используя бинарное возведение в степень — требуемое число матричных умножений O(logn)O(\log n)O(logn).
- Сложность и замечания аналогичны быстрому удвоению.
5) Приближённая формула (Бине)
- Формула: Fn=φn−(−φ)−n5F_n=\frac{\varphi^n-(-\varphi)^{-n}}{\sqrt{5}}Fn =5 φn−(−φ)−n . Приближённо можно взять Fn≈round(φn/5)F_n\approx\mathrm{round}(\varphi^n/\sqrt{5})Fn ≈round(φn/5 ).
- Быстро (O(1)O(1)O(1)), но ненадёжно для больших nnn из‑за погрешностей плавающей арифметики.
Рекомендации
- Для практического использования: для небольших/умеренных nnn используйте итеративную реализацию (простота + O(1)O(1)O(1) памяти). Для многократных вызовов — мемоизация.
- Для очень больших nnn (когда важна скорость) — реализовать fast doubling или матричное экспоненцирование.
- Всегда проверяйте вход (целое неотрицательное) и учитывайте, что арифметика больших целых даёт рост затрат пропорционально длине числа.