Дан псевдокод рекурсивной функции с мемоизацией для вычисления числа Фибоначчи; проведите формальный анализ вычислительной сложности и памяти, объясните, когда мемоизация приводит к существенному улучшению
Коротко и формально. 1) Наивный рекурсивный алгоритм (без мемоизации). Пусть Tnaive(n)T_{\text{naive}}(n)Tnaive(n) — число операций. Он удовлетворяет рекурренте Tnaive(n)=Tnaive(n−1)+Tnaive(n−2)+Θ(1),
T_{\text{naive}}(n)=T_{\text{naive}}(n-1)+T_{\text{naive}}(n-2)+\Theta(1), Tnaive(n)=Tnaive(n−1)+Tnaive(n−2)+Θ(1),
откуда Tnaive(n)=Θ(φ n),
T_{\text{naive}}(n)=\Theta(\varphi^{\,n}), Tnaive(n)=Θ(φn),
где φ=1+52\varphi=\dfrac{1+\sqrt5}{2}φ=21+5 (экспоненциальная сложность). Память стекa рекурсии — Θ(n)\Theta(n)Θ(n) в худшем случае (глубина рекурсии). 2) Рекурсивный алгоритм с мемоизацией. Состояния — целые 0,…,n0,\dots,n0,…,n. Каждый индекс kkk вычисляется не более одного раза и затем берётся из таблицы. Если операции объединения/листинки и доступ к таблице — Θ(1)\Theta(1)Θ(1) (массив или хэш), то общее время Tmemo(n)=Θ(n).
T_{\text{memo}}(n)=\Theta(n). Tmemo(n)=Θ(n).
Память для таблицы мемоизации — Θ(n)\Theta(n)Θ(n). Плюс стек рекурсии (топ‑даун) — глубина Θ(n)\Theta(n)Θ(n), итого память Θ(n)\Theta(n)Θ(n). Если доступ к мемо‑таблице стоит Θ(logn)\Theta(\log n)Θ(logn) (напр., сбалансированное дерево), тогда время станет Θ(nlogn)\Theta(n\log n)Θ(nlogn). 3) Формальное сравнение и когда мемоизация даёт существенный выигрыш. - Выигрыш по времени: из экспоненциального Θ(φ n)\Theta(\varphi^{\,n})Θ(φn) в линейное Θ(n)\Theta(n)Θ(n) — существенное (экспоненциальное ускорение), потому что у задачи много перекрывающихся подзадач. - Условия эффективности мемоизации: - есть много перекрывающихся подзадач (как в Фибоначчи) — мемоизация критически полезна; - пространство состояний небольшое (поли/линейно по nnn), чтобы хранить результаты. - Когда мемоизация не помогает или вредна: - подзадачи почти не пересекаются (разветвлённая структура без повторов) — накладные расходы на хранение/поиск делают её бесполезной; - ограничена память или стек (тогда лучше итеративный DP или хранить только нужные состояния).
4) Практические замечания: - Для вычисления FnF_nFn итеративный DP даёт ту же Θ(n)\Theta(n)Θ(n) по времени и меньше констант/риск переполнения стека; можно снизить память до Θ(1)\Theta(1)Θ(1), храня только два последних значения. - Мемоизация полезна, когда число уникальных состояний SSS мало по сравнению с числом рекурсивных вызовов у наивного алгоритма: выигрыш ≈ отношение затрат на повторные вычисления к SSS. Итого: для рекурсивного Fibonacci с мемоизацией время Θ(n)\Theta(n)Θ(n), память Θ(n)\Theta(n)Θ(n); мемоизация даёт существенное улучшение именно при больших перекрывающихся подзадачах (как в Fibonacci).
1) Наивный рекурсивный алгоритм (без мемоизации). Пусть Tnaive(n)T_{\text{naive}}(n)Tnaive (n) — число операций. Он удовлетворяет рекурренте
Tnaive(n)=Tnaive(n−1)+Tnaive(n−2)+Θ(1), T_{\text{naive}}(n)=T_{\text{naive}}(n-1)+T_{\text{naive}}(n-2)+\Theta(1),
Tnaive (n)=Tnaive (n−1)+Tnaive (n−2)+Θ(1), откуда
Tnaive(n)=Θ(φ n), T_{\text{naive}}(n)=\Theta(\varphi^{\,n}),
Tnaive (n)=Θ(φn), где φ=1+52\varphi=\dfrac{1+\sqrt5}{2}φ=21+5 (экспоненциальная сложность). Память стекa рекурсии — Θ(n)\Theta(n)Θ(n) в худшем случае (глубина рекурсии).
2) Рекурсивный алгоритм с мемоизацией. Состояния — целые 0,…,n0,\dots,n0,…,n. Каждый индекс kkk вычисляется не более одного раза и затем берётся из таблицы. Если операции объединения/листинки и доступ к таблице — Θ(1)\Theta(1)Θ(1) (массив или хэш), то общее время
Tmemo(n)=Θ(n). T_{\text{memo}}(n)=\Theta(n).
Tmemo (n)=Θ(n). Память для таблицы мемоизации — Θ(n)\Theta(n)Θ(n). Плюс стек рекурсии (топ‑даун) — глубина Θ(n)\Theta(n)Θ(n), итого память Θ(n)\Theta(n)Θ(n).
Если доступ к мемо‑таблице стоит Θ(logn)\Theta(\log n)Θ(logn) (напр., сбалансированное дерево), тогда время станет Θ(nlogn)\Theta(n\log n)Θ(nlogn).
3) Формальное сравнение и когда мемоизация даёт существенный выигрыш.
- Выигрыш по времени: из экспоненциального Θ(φ n)\Theta(\varphi^{\,n})Θ(φn) в линейное Θ(n)\Theta(n)Θ(n) — существенное (экспоненциальное ускорение), потому что у задачи много перекрывающихся подзадач.
- Условия эффективности мемоизации:
- есть много перекрывающихся подзадач (как в Фибоначчи) — мемоизация критически полезна;
- пространство состояний небольшое (поли/линейно по nnn), чтобы хранить результаты.
- Когда мемоизация не помогает или вредна:
- подзадачи почти не пересекаются (разветвлённая структура без повторов) — накладные расходы на хранение/поиск делают её бесполезной;
- ограничена память или стек (тогда лучше итеративный DP или хранить только нужные состояния).
4) Практические замечания:
- Для вычисления FnF_nFn итеративный DP даёт ту же Θ(n)\Theta(n)Θ(n) по времени и меньше констант/риск переполнения стека; можно снизить память до Θ(1)\Theta(1)Θ(1), храня только два последних значения.
- Мемоизация полезна, когда число уникальных состояний SSS мало по сравнению с числом рекурсивных вызовов у наивного алгоритма: выигрыш ≈ отношение затрат на повторные вычисления к SSS.
Итого: для рекурсивного Fibonacci с мемоизацией время Θ(n)\Theta(n)Θ(n), память Θ(n)\Theta(n)Θ(n); мемоизация даёт существенное улучшение именно при больших перекрывающихся подзадачах (как в Fibonacci).