Строго говоря, это не самый рациональный алгоритм, он требует логарифмическое время и логарифмическую память. Но, на мой взгляд, он ярче других демонстрирует саму идею, лежащую в основе решения.
def pow(x, p):
if p == 0:
return 1
t = pow(x * x, p // 2)
if p % 2:
t *= x
return t
На каждом шаге рекурсии вы редуцируете задачу x**n
и (x**2)**(n/2)
.
Т.е. за один шаг вы вдвое понижаете степень.
Эту идею можно реализовать и без рекурсии.
def pow(x, p):
m = x
t = 1
while p:
if p % 2:
t *= m
m *= m
p //= 2
return t
Мы получили логарифмическую сложность по времени, но уже константную сложно по памяти.
Любопытно, что небольшое дополнение к алгоритму позволяет расширить его возможности:
def pow(x, p):
if p < 0:
p = -p
x = 1. / x
m = x
t = 1
while p:
if p % 2:
t *= m
m *= m
p //= 2
return t
Напомню, то числа Фибоначчи — это последовательность, в котором два первых числа равны 0 и 1 (или 1 и 1), а каждое последующее равно сумме двух предыдущих.
Предлагаю принять такие обозначения:
F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2) при n >= 2
Числа Фибоначчи можно получать возведением в степень матрицы:
|1 1|n |F(n+1) F(n) |
|1 0| = |F(n) F(n-1)|
Справедливость этой формулы можно проверить разными способами. Можно
внимательно присмотреться. Можно вспомнить/доказать, что F(n+m)=F(n-1)F(m)+F(n)F(m+1)
.
Можно просто посмотреть, что будет при умножении матриц:
|F(n+1) F(n) | |1 1| |F(n+1)+F(n) F(n+1)+0| |F(n+2) F(n+1)|
|F(n) F(n-1)| |1 0| = |F(n)+F(n-1) F(n)+0 | = |F(n+1) F(n) |
То есть, умножив матрицу из чисел Фибоначчи на матрицу с тремя единицами, мы сделали один шаг в последовательности Фибоначчи.
def matrix_mul(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 pow(p):
m = [[1, 1], [1, 0]]
t = [[1, 0], [0, 1]]
while p:
if p % 2:
t = matrix_mul(t, m)
m = matrix_mul(m, m)
p //= 2
return t
def fib(n):
return pow(n)[1][0]
Это уже знакомы нам алгоритм возведения в степень, но не числа, а матрицы.
Используя библиотеки, решить эту задачу можно в одну строчку:
import numpy as np
def fib(n):
return np.linalg.matrix_power(
np.array([[1, 1], [1, 0]], dtype='O'),
n
)[1,0]
Единственно, что следует не забыть, это dtype='O'
, т.к. иначе, вы очень
быстро упрётесь в ограничения разрядности. И, что самое печальное, numpy
не предупредить вас об этом. Просто выдаст не правильный результат.