Pull to refresh

Comments 20

Тут нужно сделать важную оговорку, что все указанные оценки справедливы только если вы, например, делаете вычисления по модулю. Проблема в том, что длина Fn растет линейно от n из-за чего итеративный алгоритм работает на самом деле за O(n^2), а быстрый за O(nlogn) (из мастер-теоремы). Ну и соответственно если вы работаете по модулю часла длины k, то получаются оценки O(nk)/O(klogn).

Есть еще формула Бине

f_n=\frac{\varphi^n-(-\varphi)^{-n}}{\sqrt{5}}~~\varphi=\frac{1+\sqrt{5}}{2}

которая сводит вычисления чисел Фибоначчи к возведению в степень, которое можно делать за log, но из-за работы с float она не такая полезная даже для спортивного интереса, в отличии от

\left(\begin{array}{cc}f_{n+1} & f_n \\ f_n & f_{n-1}\end{array}\right)^n=\left(\begin{array}{cc}1 & 1 \\ 1 & 0\end{array}\right)^n

А еще можно вычислять по формуле Бине, но в алгебраическом расширении целых чисел корнем из 5. Тогда числа хранятся в виде a+b*sqrt(5). И умножение таких чисел будет тем же самым возведением матрицы в степень.

N log N - быстрое преобразование Фибоначчи)), БПФ

А если есть процессор с бесконечно большим числом ядер)) то за O(log(n)) парралельных шагов можно вычислять не только числа Фибоначчи, но и любую рекуррентную последовательность вида

x_n=a_n x_{n-1} + b_n x_{n-2}

Сведение Фибоначчи к умножению имхо проще всего понять, записав его как матричное умножение,
Вместо f(n) будем смотреть что происходит с вектором, точнее столбцом f(n+1), f(n):

f(n+1) = 1*f(n) + 1*f(n-1)
f(n)   = 1*f(n) + 0*f(n-1)

\begin{pmatrix}
f(n + 1)  \\
f(n)
\end{pmatrix}=\begin{pmatrix}
1   1 \\
1   0
\end{pmatrix}\times\begin{pmatrix}
f(n)  \\
f(n - 1)
\end{pmatrix}

Получаем буквально возведение в степень (мартиц)

\begin{pmatrix}
f(n+1)  \\
f(n)
\end{pmatrix}=\begin{pmatrix}
1   1 \\
1   0
\end{pmatrix}^{n}\times\begin{pmatrix}
f(1)  \\
f(0)
\end{pmatrix}

100-километровым плотным слоем кроличьих тушек.

Напомнило нефтяную планету из кротов.

public static void main (final String... args )

А зачем final аргументам?

А зачем final аргументам?

Привычка везде проставлять final, убирая его только при явной необходимости...

А какой профит? У джавы какое-то особенное отношение к наследованию и внезапно можно частично отнаследовать методы или что-то в этом роде? То есть это явно не константность.

final в джава тут - это как const в C++ - константрые, неизменяемые переменные.

Как сложно вы расписали! Это классическая задача, она разбирается в легендарной книге SICP и во многих учебниках по линейной алгебре. И в матричном виде разбор её решения занимает меньше страницы текста.

К сожалению, с математикой у меня не очень. Эта статья появилась, как раз по той причине, что во время разбора упражнения 1,19 в SICP, я не смог понять, как выводится формула a ← bq + aq + ap, b ← bp + aq, а про матричный способ вычисления чисел Фибоначчи не знал. Попробовал разобраться самостоятельно...

Ясно. Ну смотрите. Линейная алгебра очень большая наука. И даже то, что дается в университетских курсах это ее малая часть. Но есть и хорошая новость. Почти все знания из линейки, которые как то можно применить на практике изучаются за недельку (или две). Это операции сложения и умножения матриц и векторов, а ещё вычисление детерминанта ну и матрицы поворота и перехода к другому базису. Это покрывает почти все практические нужды, которые только бывают. Так что если вдруг захотите разобраться, вам надо внимательно изучить первые 20-30 страниц любого учебника. Остальные 400 страниц почти нигде не пригодятся. Такой вот принцип Парето на максималках.

Хорошая традиция - каждый раз, прежде чем начинать читать статью про быстрое нахождение чисел Фибоначчи, брать и воспроизводить по памяти решение:

julia> fib(n) = ( BigInt.( [1 1;1 0] )^n )[1];

julia> fib.(1:10)
10-element Vector{BigInt}:
  1
  2
  3
  5
  8
 13
 21
 34
 55
 89
julia> @time fib(1_000_000);
  0.029857 seconds (1.45 k allocations: 10.012 MiB)

julia> @time fib(100_000_000);
  6.999292 seconds (105.82 k allocations: 1.612 GiB, 0.14% gc time)

julia> 100_000_000 |> fib |> string |> length
20 898 764

А возведение в степень здесь происходит за логарифм или линейное? Просто в этом и заключается вся задача

Кажется по выводу, что в 100 раз увеличили число, в ~100 раз увеличилось и время. Хотя возможно это вина длинной арифметики

Хотя возможно это вина длинной арифметики

Именно. Длина чисел растет линейно от n. Более того, оно так быстро работает только потому, что там еще и умножение длинных чисел сделано быстро, через FFT какое-нибудь. A то мы бы только умножение двух чисел длиной с миллионы знаков ждали бы несколько минут. В итоге там что-то около O(n (log n)^2) получается.

TL;DR

Слишком большая статья для описания такой простой вещи. Лучше б рассказали старинную байку, как фанаты ассемблера и си зарубились из-за скорости, договорились помериться, вычисляя n-е число Фибоначчи, и всех уделал какой-то чувак, написавший чуть ли не на бейсике.

Как только узнаёшь в задаче возведение матрицы в степень – остальное должен написать, не задумываясь.

Кстати, если копнуть глубже – это решение не за логарифмическое время. Ведь числа с ростом N становятся всё длиннее, и умножение, соответственно, медленнее. Исходя из объёма вашей статьи и упоминания в ней быстрого алгоритма умножения – я ожидал увидеть как минимум алгоритм Карацубы.

Почему бы просто не воспользоваться формулой Бине?

Как так получилось, что итеративный способ с n сложениями оказался дольше/хуже, чем матричное возведение в степень, где как я понимаю 4 операции сложения и 8 операций умножения на каждую итерацию⁉️ 🤔

Я "думал-думал и всё понял" 🙂
В матричном умножении для быстрого вычисления используется быстрое возведение в степень: https://habr.com/ru/companies/otus/articles/779396/
Что логарифмически уменьшает количество операций.

Sign up to leave a comment.

Articles