Дан фрагмент кода на Python: def fib(n): return fib(n-1)+fib(n-2) if n>1 else n; проанализируйте его по сложностям времени и памяти, предложите три оптимизации разного уровня (итеративная, мемоизация, матричный метод) и оцените их преимущества и ограничения
Функция fib(n) в приведённом коде является рекурсивной реализацией вычисления n-го числа Фибоначчи. При большом значении n она работает неэффективно из-за экспоненциальной сложности, так как каждый вызов функции приводит к двум новым вызовам, что приводит к множественным повторениям расчётов.
Оценка сложности:
Временная сложность:
Рекурсивный подход имеет временную сложность O(2^n). Это происходит из-за большого количества повторных вызовов функции для одинаковых значений.
Память:
Память, используемая для хранения стеков вызовов, равна O(n), так как в пике глубина рекурсии может достигать n.Оптимизации:
Итеративный метод (Временная сложность: O(n)):
def fib_iter(n): if n <= 1: return n a, b = 0, 1 for _ in range(2, n + 1): a, b = b, a + b return b
Преимущества:
Простота реализации и понятность.Сложность по времени O(n) и по памяти O(1), так как мы сохраняем всего лишь два последние числа Фибоначчи.
Ограничения:
Этот метод не позволяет быстро вычислять большие числа Фибоначчи из-за линейного времени выполнения, хотя он значительно лучше, чем рекурсивный подход.
def fib_memo(n, memo={}): if n in memo: return memo[n] if n <= 1: return n memo[n] = fib_memo(n - 1, memo) + fib_memo(n - 2, memo) return memo[n]
Преимущества:
Значительно уменьшает количество повторных вызовов, что приводит к O(n) времени выполнения, сохраняя результаты.По сути становится рекурсивной реализацией, которая использует память для хранения уже вычисленных значений.
Ограничения:
По памяти O(n) из-за хранения всех промежуточных результатов.Но для маленьких значений n не всегда требуется запоминание всех значений.
Матричный метод (Временная сложность: O(log n), Память: O(1)):
def fib_matrix(n): def matrix_mult(A, B): return [ [A[0][0] * B[0][0] + A[0][1] * B[1][0], A[0][0] * B[0][1] + A[0][1] * B[1][1]], [A[1][0] * B[0][0] + A[1][1] * B[1][0], A[1][0] * B[0][1] + A[1][1] * B[1][1]] ] def matrix_pow(M, p): if p == 1: return M if p % 2 == 0: half_power = matrix_pow(M, p // 2) return matrix_mult(half_power, half_power) else: return matrix_mult(M, matrix_pow(M, p - 1)) if n == 0: return 0 M = [[1, 1], [1, 0]] result = matrix_pow(M, n - 1) return result[0][0]
Преимущества:
Наилучшее время выполнения O(log n), что делает его очень эффективным для больших значений n.Использует всего лишь фиксированное количество переменных, так что память используется минимально O(1).
Ограничения:
Сложнее в понимании и реализации по сравнению с итеративным и мемоизированным подходами.Важно учитывать, что при больших числах может возникнуть проблема с переполнением.Заключение:
Каждый из предложенных подходов имеет свои преимущества и недостатки, и выбор решения зависит от контекста задачи. Для небольших чисел можно использовать итеративный метод, для средних — мемоизацию, а для больших — матричный метод.
Функция fib(n) в приведённом коде является рекурсивной реализацией вычисления n-го числа Фибоначчи. При большом значении n она работает неэффективно из-за экспоненциальной сложности, так как каждый вызов функции приводит к двум новым вызовам, что приводит к множественным повторениям расчётов.
Оценка сложности:Временная сложность:
Рекурсивный подход имеет временную сложность O(2^n). Это происходит из-за большого количества повторных вызовов функции для одинаковых значений.Память:
Память, используемая для хранения стеков вызовов, равна O(n), так как в пике глубина рекурсии может достигать n.Оптимизации:Итеративный метод (Временная сложность: O(n)):
def fib_iter(n):if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
Преимущества:
Простота реализации и понятность.Сложность по времени O(n) и по памяти O(1), так как мы сохраняем всего лишь два последние числа Фибоначчи.Ограничения:
Этот метод не позволяет быстро вычислять большие числа Фибоначчи из-за линейного времени выполнения, хотя он значительно лучше, чем рекурсивный подход.Мемоизация (Временная сложность: O(n), Память: O(n)):
def fib_memo(n, memo={}):if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fib_memo(n - 1, memo) + fib_memo(n - 2, memo)
return memo[n]
Преимущества:
Значительно уменьшает количество повторных вызовов, что приводит к O(n) времени выполнения, сохраняя результаты.По сути становится рекурсивной реализацией, которая использует память для хранения уже вычисленных значений.Ограничения:
По памяти O(n) из-за хранения всех промежуточных результатов.Но для маленьких значений n не всегда требуется запоминание всех значений.Матричный метод (Временная сложность: O(log n), Память: O(1)):
def fib_matrix(n):def matrix_mult(A, B):
return [
[A[0][0] * B[0][0] + A[0][1] * B[1][0], A[0][0] * B[0][1] + A[0][1] * B[1][1]],
[A[1][0] * B[0][0] + A[1][1] * B[1][0], A[1][0] * B[0][1] + A[1][1] * B[1][1]]
]
def matrix_pow(M, p):
if p == 1:
return M
if p % 2 == 0:
half_power = matrix_pow(M, p // 2)
return matrix_mult(half_power, half_power)
else:
return matrix_mult(M, matrix_pow(M, p - 1))
if n == 0:
return 0
M = [[1, 1], [1, 0]]
result = matrix_pow(M, n - 1)
return result[0][0]
Преимущества:
Наилучшее время выполнения O(log n), что делает его очень эффективным для больших значений n.Использует всего лишь фиксированное количество переменных, так что память используется минимально O(1).Ограничения:
Сложнее в понимании и реализации по сравнению с итеративным и мемоизированным подходами.Важно учитывать, что при больших числах может возникнуть проблема с переполнением.Заключение:Каждый из предложенных подходов имеет свои преимущества и недостатки, и выбор решения зависит от контекста задачи. Для небольших чисел можно использовать итеративный метод, для средних — мемоизацию, а для больших — матричный метод.