Дан код на Python: def factorial(n): return n*factorial(n-1) if n>0 else 1 — объясните возможные ошибки при больших n, предложите альтернативы с оценкой по времени и памяти, включая итеративный, хвостовую рекурсию и использование мемоизации

25 Ноя в 15:54
1 +1
0
Ответы
1
Коротко о проблемах и решениях.
Проблемы при больших nnn:
- Рекурсивное переполнение стека: в CPython по умолчанию глубина рекурсии ограничена (max_depth≈1000\text{max\_depth}\approx 1000max_depth1000), поэтому получите RecursionError\text{RecursionError}RecursionError для n≳1000n\gtrsim 1000n1000.
- Время вычисления: число операций растёт линейно по количеству множителей, т.е. Θ(n)\Theta(n)Θ(n) умножений в модельной единице; при учёте длины чисел умножения становятся дороже.
- Память/размер результата: точный факториал быстро растёт — число битов в n!n!n! примерно
log⁡2n!≈nlog⁡2n−nlog⁡2e+O(log⁡n), \log_2 n! \approx n\log_2 n - n\log_2 e + O(\log n),
log2 n!nlog2 nnlog2 e+O(logn),
т.е. хранение результата требует примерно Θ(nlog⁡n)\Theta(n\log n)Θ(nlogn) бит; для очень больших nnn возможна MemoryError\text{MemoryError}MemoryError.
Альтернативы (с оценками):
1) Итеративный (рекомендовано для Python)
- Код:
```
def factorial_iter(n):
r = 1
for i in range(2, n+1):
r *= i
return r
```
- Время: O(n)O(n)O(n) по числу умножений (в модельной модели). С учётом роста длины чисел — умножения становятся дороже, итоговая битовая сложность зависит от алгоритма умножения больших целых.
- Память: O(1)O(1)O(1) дополнительной памяти (кроме результата).
2) Хвостовая рекурсия
- Пример:
```
def fact_tail(n, acc=1):
return fact_tail(n-1, acc*n) if n>0 else acc
```
- В Python бесполезна: Python не делает оптимизацию хвостовой рекурсии, глубина стека всё равно O(n)O(n)O(n) и для больших nnn будет RecursionError\text{RecursionError}RecursionError.
- В языках с TCO (tail-call optimization): время O(n)O(n)O(n), стек O(1)O(1)O(1).
3) Мемоизация / кэширование
- Идея: сохранять уже вычисленные факториалы, полезно при множественных запросах для разных nnn.
- Пример (итеративное накопление):
```
cache = [1]
def factorial_cached(n):
while len(cache) <= n:
cache.append(cache[-1] * len(cache))
return cache[n]
```
- Время: первое вычисление до nnnO(n)O(n)O(n); последующие вызовы для меньших или равных nnnO(1)O(1)O(1).
- Память: O(n)O(n)O(n) для хранения всех значений 0..n0..n0..n.
4) Использовать стандартную реализацию
- Python: используйте math.factorial(n)\text{math.factorial(n)}math.factorial(n) — реализовано на C и использует оптимизации (быстрее чистого Python).
- Часто лучший выбор для практических задач.
5) Алгоритмы для очень больших nnn - Для экстремально больших nnn используются быстрые методы (binary splitting, prime-swing и др.), дающие время примерно O(M(N)log⁡N)O(M(N)\log N)O(M(N)logN), где M(N)M(N)M(N) — стоимость умножения чисел длины NNN бит. Это нужно при вычислении факториалов с миллионами и более факториалов — в обычных задачах не требуется.
Резюме/рекомендации:
- Для большинства задач — заменить рекурсию на итеративный вариант или вызвать math.factorial\text{math.factorial}math.factorial.
- Если нужны множественные запросы — храните кеш (итеративно) для O(1)O(1)O(1) доступа после первого расчёта.
- Не полагайтесь на хвостовую рекурсию в Python — она не избавит от переполнения стека.
25 Ноя в 16:46
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир