числа фибоначчи по номеру формула

5 способов вычисления чисел Фибоначчи: реализация и сравнение

Введение

Программистам числа Фибоначчи должны уже поднадоесть. Примеры их вычисления используются везде. Всё от того, что эти числа предоставляют простейший пример рекурсии. А ещё они являются хорошим примером динамического программирования. Но надо ли вычислять их так в реальном проекте? Не надо. Ни рекурсия, ни динамическое программирование не являются идеальными вариантами. И не замкнутая формула, использующая числа с плавающей запятой. Сейчас я расскажу, как правильно. Но сначала пройдёмся по всем известным вариантам решения.

Код предназначен для Python 3, хотя должен идти и на Python 2.

Для начала – напомню определение:

Замкнутая формула

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Решаем квадратное уравнение:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Откуда и растёт «золотое сечение» ϕ=(1+√5)/2. Подставив исходные значения и проделав ещё вычисления, мы получаем:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

что и используем для вычисления Fn.

Хорошее:
Быстро и просто для малых n
Плохое:
Требуются операции с плавающей запятой. Для больших n потребуется большая точность.
Злое:
Использование комплексных чисел для вычисления Fn красиво с математической точки зрения, но уродливо — с компьютерной.

Рекурсия

Самое очевидное решение, которое вы уже много раз видели – скорее всего, в качестве примера того, что такое рекурсия. Повторю его ещё раз, для полноты. В Python её можно записать в одну строку:

Хорошее:
Очень простая реализация, повторяющая математическое определение
Плохое:
Экспоненциальное время выполнения. Для больших n очень медленно
Злое:
Переполнение стека

Запоминание

У решения с рекурсией есть большая проблема: пересекающиеся вычисления. Когда вызывается fib(n), то подсчитываются fib(n-1) и fib(n-2). Но когда считается fib(n-1), она снова независимо подсчитает fib(n-2) – то есть, fib(n-2) подсчитается дважды. Если продолжить рассуждения, будет видно, что fib(n-3) будет подсчитана трижды, и т.д. Слишком много пересечений.

Поэтому надо просто запоминать результаты, чтобы не подсчитывать их снова. Время и память у этого решения расходуются линейным образом. В решении я использую словарь, но можно было бы использовать и простой массив.

(В Python это можно также сделать при помощи декоратора, functools.lru_cache.)

Хорошее:
Просто превратить рекурсию в решение с запоминанием. Превращает экспоненциальное время выполнение в линейное, для чего тратит больше памяти.
Плохое:
Тратит много памяти
Злое:
Возможно переполнение стека, как и у рекурсии

Динамическое программирование

После решения с запоминанием становится понятно, что нам нужны не все предыдущие результаты, а только два последних. Кроме этого, вместо того, чтобы начинать с fib(n) и идти назад, можно начать с fib(0) и идти вперёд. У следующего кода линейное время выполнение, а использование памяти – фиксированное. На практике скорость решения будет ещё выше, поскольку тут отсутствуют рекурсивные вызовы функций и связанная с этим работа. И код выглядит проще.

Это решение часто приводится в качестве примера динамического программирования.

Хорошее:
Быстро работает для малых n, простой код
Плохое:
Всё ещё линейное время выполнения
Злое:
Да особо ничего.

Матричная алгебра

И, наконец, наименее освещаемое, но наиболее правильное решение, грамотно использующее как время, так и память. Его также можно расширить на любую гомогенную линейную последовательность. Идея в использовании матриц. Достаточно просто видеть, что

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

А обобщение этого говорит о том, что

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Два значения для x, полученных нами ранее, из которых одно представляло собою золотое сечение, являются собственными значениями матрицы. Поэтому, ещё одним способом вывода замкнутой формулы является использование матричного уравнения и линейной алгебры.

Так чем же полезна такая формулировка? Тем, что возведение в степень можно произвести за логарифмическое время. Это делается через возведения в квадрат. Суть в том, что

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

где первое выражение используется для чётных A, второе для нечётных. Осталось только организовать перемножения матриц, и всё готово. Получается следующий код. Я организовал рекурсивную реализацию pow, поскольку её проще понять. Итеративную версию смотрите тут.

Хорошее:
Фиксированный объём памяти, логарифмическое время
Плохое:
Код посложнее
Злое:
Приходится работать с матрицами, хотя они не так уж и плохи

Сравнение быстродействия

Сравнивать стоит только вариант динамического программирования и матрицы. Если сравнивать их по количеству знаков в числе n, то получится, что матричное решение линейно, а решение с динамическим программированием – экспоненциально. Практический пример – вычисление fib(10 ** 6), числа, у которого будет больше двухсот тысяч знаков.

n = 10 ** 6
Вычисляем fib_matrix: у fib(n) всего 208988 цифр, расчёт занял 0.24993 секунд.
Вычисляем fib_dynamic: у fib(n) всего 208988 цифр, расчёт занял 11.83377 секунд.

Теоретические замечания

Не напрямую касаясь приведённого выше кода, данное замечание всё-таки имеет определённый интерес. Рассмотрим следующий граф:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Подсчитаем количество путей длины n от A до B. Например, для n = 1 у нас есть один путь, 1. Для n = 2 у нас опять есть один путь, 01. Для n = 3 у нас есть два пути, 001 и 101. Довольно просто можно показать, что количество путей длины n от А до В равно в точности Fn. Записав матрицу смежности для графа, мы получим такую же матрицу, которая была описана выше. Это известный результат из теории графов, что при заданной матрице смежности А, вхождения в А n — это количество путей длины n в графе (одна из задач, упоминавшихся в фильме «Умница Уилл Хантинг»).

Почему на рёбрах стоят такие обозначения? Оказывается, что при рассмотрении бесконечной последовательности символов на бесконечной в обе стороны последовательности путей на графе, вы получите нечто под названием «подсдвиги конечного типа», представляющее собой тип системы символической динамики. Конкретно этот подсдвиг конечного типа известен, как «сдвиг золотого сечения», и задаётся набором «запрещённых слов» <11>. Иными словами, мы получим бесконечные в обе стороны двоичные последовательности и никакие пары из них не будут смежными. Топологическая энтропия этой динамической системы равна золотому сечению ϕ. Интересно, как это число периодически появляется в разных областях математики.

Источник

Как я посчитал миллионное число Фибоначчи

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Все мы понимаем, что рекурсивное вычисление чисел Фибоначчи крайне неэффективно. Многим людям наверняка хотелось проверить, где пределы (не)эффективности, но не доходили руки, не хватало времени. Специально к старту нового потока курса Fullstack-разработчик на Python мы решили поделиться переводом статьи, автор которой шаг за шагом показывает возможности современного Python на примере разных подходов к вычислению чисел Фибоначчи. В статье вы найдёте проблемные значения n и сравнение производительности оптимального и неоптимального решений на графике.

Нет, заголовок вообще не кликбейтный. Несколько дней назад я действительно хотел найти оптимальное решение для расчёта чисел Фибоначчи, хотелось попробовать вычислить стотысячное число последовательности, но я задумался; если я могу вычислить стотысячное число, то что остановит меня в поисках миллионного числа Фибоначчи? Сегодня я покажу, как шёл к вычислению и с какими проблемами столкнулся.

Последовательность Фибоначчи — одна из наиболее известных математических последовательностей и самый простой пример рекуррентных отношений. Каждое число последовательности — это сумма двух предыдущих чисел, начиная с 0 и 1. Получается такая последовательность:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144 и так далее.

В следующие несколько минут я исследую несколько разных подходов, а затем покажу оптимальное решение:

Расчёт 1000000-го числа Фибоначчи.

Но, прежде чем мы начнём, я должен сказать, что все упомянутые тайминги касаются оборудования, на котором я работаю сейчас, а версия Python — 3.9.1.

Простая рекурсия

Это очень простой способ получить N-ное число Фибоначчи на Python:

В коде используется рекурсия, он вызывает сам себя несколько раз, вычисляя предыдущее число и используя это число для вычисления следующего. Но это также недостаток, поскольку функция чрезвычайно неэффективна и ресурсоёмка: на каждом этапе она вычисляет предыдущие 2 числа, а также предыдущие 2 числа этих чисел и т. д.

Вскоре вы достигаете точки, когда вычисление следующего числа занимает слишком много времени, например, на моём компьютере мне потребовалось 1,43 секунды, чтобы вычислить 35-е число. Очевидно, что вычисление более высоких значений будет чрезвычайно медленным и практически невозможным.

Кеш с рекурсией

Поскольку мы постоянно вычисляем предыдущие 2 числа, для хранения числа можно воспользоваться возможностями кеширования, не нужно будет вычислять числа несколько раз. Встроенный модуль functools позволяет нам работать с LRU кешем; этот тип кеша организует элементы в порядке их использования. Такой подход может значительно ускорить процесс.

Во-первых, нам нужно импортировать декоратор lru_cache из модуля functools и поместить его перед нашей функцией. Мы можем указать значение maxsize, чтобы сообщить кешу, сколько элементов нужно хранить, но по умолчанию оно равно 128, это значение прекрасно работает. Используя кеш, мы можем вычислить 200-е число Фибоначчи всего за 0,0002252 секунды!

Одна проблема с использованием рекурсии заключается в том, что если вы попытаетесь вычислить 501-е число, то получите ошибку RecursionError: maximum recursion depth exceeded in comparison. Но, к счастью, проблему можно решить, установив большее значение глубины рекурсии:

Теперь мы можем вычислить 1000-е число Фибоначчи, на вычисление которого мне потребовалось всего 0,001198 секунды. Однако это создало для меня ещё одну проблему: по какой-то странной причине я не мог вычислить 1553-е число в последовательности, и даже после увеличения предела рекурсии ничего не произойдёт, ничего не будет распечатано на терминале, и программа просто закончит выполнение. Очевидно, что это проблема и недостаток на моём пути к вычислению миллионного числа Фибоначчи.

Итеративный метод

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

Мы можем воспользоваться им, чтобы вычислить любое число Фибоначчи (я не тестировал подход с особенно большими числами), и часто этот подход работает также очень быстро, 1000-е число вычислилось всего за 0,0028195 секунды.

Вы можете задаться вопросом, почему нельзя воспользоваться этим подходом для вычисления 1000000-го числа, и да, это возможно, но займёт немного времени. Продолжайте читать, и я расскажу, почему.

Формула Бине

Формула Бине — это формула, которая может использоваться для вычисления n-го члена последовательности Фибоначчи, а это именно то, что мы хотим сделать; эта формула названа в честь открывшего её французского математика Жака Филиппа Мари Бине. Вот она:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формулаФормула Бине для вычисления n-ного числа Fibonacci

Вы можете заметить греческую букву PHI (ϕ), она означает золотое сечение:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формулаУравнение золотого сечения, phi

Можно написать формулу на Python и сразу же начать работать с ней:

Примечание: для реализации на Python нам нужно вернуть округление вычисляемого числа, потому что при вычислении большого числа Python вернёт результат, в котором может быть более двадцати девяток после запятой.

Всё это хорошо, так как теперь у нас нет никаких циклов и мы можем мгновенно вычислить ответ, верно? Что ж, в этом методе есть небольшая загвоздка. Если мы попытаемся вычислить что-либо выше 1475-го числа, то столкнёмся с ошибкой: OverflowError: (34, result too large). Это связано с тем, как в python реализованы числа с плавающей точкой, они могут иметь конкретное максимальное значение, которое мы превышаем, когда используем этот метод.

Однако исправить ситуацию очень легко. Мы можем использовать встроенный модуль под названием decimal, чтобы создать десятичный объект с гораздо более высокой точностью и подходящим для работы с уравнением размером:

В этой новой функции мы устанавливаем значение точности длиной 10000 цифр, преобразуем наше значение квадратного корня из 5 в десятичное значение объекта и используем его в нашем уравнении. Это позволяет нам легко вычислить 10000-е число в последовательности за поразительные 0,0692986 секунды, а это по сравнению со всеми нашими предыдущими методами огромное улучшение.

Расчёт 1000000-го числа Фибоначчи

Теперь вы, возможно, заметили, что формула работает медленнее итерационного решения, когда n=10000. Это связано с тем, что в формуле нам нужно создать десятичный объект и использовать его в уравнении, этот процесс занимает больше времени, чем повторение одной простой инструкции 10000 раз. Но история ещё не окончена.

Увеличение количества циклов может радикально увеличить длительность всего процесса. В какой-то момент, когда n составляет приблизительно 89200, время, необходимое итеративному решению для вычисления ответа, равно времени, которое необходимо при вычислении по формуле; когда же n увеличивается, время выполнения итеративного алгоритма возрастает с большей скоростью, чем время на решение по формуле.

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формулаГрафик, показывающий время работы формулы Бине и итерационного решения

На графике видно точку пересечения времени выполнения формулы и итерационных графиков. Исходя из этого мы можем сказать, что с увеличением n время вычисления числа Фибоначчи по формуле возрастает линейно. Но при итеративном решении время увеличивается с увеличением n. Это даёт нам понять, что для вычисления миллионного числа Фибоначчи нам нужно использовать формулу. Дополнительное изменение, которое я должен был сделать, чтобы правильно вычислить число, — увеличить точность моего десятичного объекта строкой кода decimal.getcontext().prec = 300000.

Ваше время выполнения алгоритма может отличаться. На моём компьютере, чтобы вычислить 1000000-е число Фибоначчи, потребовалось:

8,832661 секунды при решении с итерацией;

1,151380 секунды с формулой Бине, это в 7,7 раза быстрее!

Если вам хочется узнать число, оно состоит из 208988 цифр и в текстовом файле занимает 209 КБ:

Заключение

Вот так я вычислил миллионное число Фибоначчи, конечно, я мог бы вычислить число больше, но на самом деле для этого нет никакой реальной причины, и это заняло бы много времени, даже с использованием формулы Бине. Из приведённого выше графика я могу оценить затраты времени: чтобы вычислить миллиардное число Фибоначчи, мне потребуется примерно 310,8467 секунды, я оставлю это читателям. А чтобы получить специальность Fullstack-разработчик на Python — потребуется немногим более года. Но можно и быстрее — на нашем курсе студенты не привязаны к программе и скорость их прогресса зависит от них самих.

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Источник

Фибоначчи на собеседовании

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

1. Быть проще, и люди к вам потянутся.

Самое простое решение — это банальный цикл.

Шутка. Разумеется, так писать не нужно — если, конечно, вы не собеседуетесь на должность штатного обфускатора.

У вас кончились талончики на переменные? cypok

Ладно, ладно, для ещё большей читаемости напишем так:

Это — вариант классический, простой и элегантный. Но, возможно, вы хотите продемонстрировать своё знание ещё каких-то концепций? Например…

2. Чтобы понять рекурсию, надо понять рекурсию

Например, да, вы можете продемонстрировать, что умеете в рекурсию. Например, так:

Запомните этот вариант. Так делать не стоит. Не следует. Нельзя. Никогда. Это хуже, чем пинать щеночков, и сравнимо с небольшим холокостом.

Возможно, вы спросите, почему. В таком случае просто запустите этот код и попытайтесь посчитать, скажем, пятидесятое число Фибоначчи. Полагаю, вы ощутите некую задержку. Шутка. Полагаю, если вы запускаете этот код не на суперкомпьютере, то попросту не дождётесь результата. При том, что простой, не рекурсивный код из предыдущих примеров посчитает пятидесятый член последовательности Фибоначчи быстрее, чем вы успеете произнести слово «пятьдесят» или любой его слог.

Выражаясь грубым языком O-нотации, такое решение имеет временную сложность O(e n ). То есть — время выполнения этой функции растёт экспоненциально при увеличении n. То есть — когда n увеличивается на, время выполнения увеличивается в. Грубо говоря, если fib(45) вам пришлось ждать час, то fib(46) вы будете ждать два часа, fib(47) — 4 часа, и так далее. Я разжёвываю так подробно, чтобы каждый читатель, даже верстальщик, впервые попробовавший свои силы в написании скриптов, мог осознать ужас ситуации.

Это правильно, но слишком грубо. Можно получить более точную оценку числа вызов функции

(1+sqrt(5)) fib(n) и красивое замечание «Для вычисления числа Фибонначи наивным рекуррентным методом понадобится вызовов функции в 3.2 раза больше чем само число Фибонначи». Taus

И мы получаем ещё один метод его вычисления. Надо просто запустить наивный рекурректный метод, подсчитать количество вызовов функции и разделить на 3.2! Cerberuser

Если от вас на собеседовании потребуют рекурсивного решения этой задачи, скорее всего, это ловушка. «Правильная» рекурсия, работающая за линейное время, может выглядеть, например, так:

Подводя итог: несмотря на то, что числа Фибоначчи являются классическим, хрестоматийным примером рекурсии, в действительности это не самый удобный случай для применения рекурсии. Но можно попробовать блеснуть ещё какими-нибудь знаниями.

3. Мемная функция

Существует волшебный способ, превращающий чудовищно неэффективное решение из прошлого параграфа в потенциально очень быстрое (хотя и не лишённое проблем). Имя ему — мемоизация. А если говорить по-русски — мы просто запоминаем результаты предыдущих вызовов вместо того, чтобы вычислять их заново.

Так вот, теперь предыдущие значения будут вычисляться по одному разу, а при их повторном запросе — просто браться из кэша. Представляете, во сколько раз быстрее мы сможем теперь вычислить сорок пятое число Фибоначчи? Серьёзно, как вы думаете, во сколько?

Замечательно! Теперь первый вызов fib(45) отработает со скоростью, сравнимой с версией с циклом. А дальнейшие вызовы вообще сработают за константное время… Оп! Опять обманул. Получение значения свойства объекта по ключу — это операция быстрая, но всё-таки O(1) только в среднем, в худшем случае она может деградировать до O(n). Чтобы стало совсем хорошо, в нашем случае мы можем сменить тип cache с объекта на массив.

Разумеется, не стоит также забывать, что мемоизация требует памяти. И пока мы уменьшаем сложность по времени, сложность по памяти растёт с O(1) до O(n).

Как ещё мы можем выпендриться? Например, продемонстрировав своё глубокое знание математики

4. Мистер Бине

Существует особая прекрасная наука о том, как рекуррентные соотношения превращать в явные формулы. Здесь мы не будем вдаваться в её детали. Скажем лишь, что для чисел Фибоначчи с помощью достаточно несложных рассуждений можно вывести следующую формулу, известную как формула Бине:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Однако довольно языка математики, запишем это на языке JavaScript:

Прогоним на первых нескольких числах. Замечательно, кажется, всё работает. Вот 13, вот 21, вот 34, вот… 54.99999999999999?

Да, разумеется, такой результат закономерен. Формула Бине точна математически, но компьютер оперирует дробями конечной точности, и при действиях над ними может накопиться ошибка, что и произошло в данном случае. Однако мы можем всё исправить. Зная, что вычитаемое в числителе всегда будет маленьким по модулю, мы можем упростить формулу до следующего состояния:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Здесь странные недоделанные квадратные скобки означают ближайшее целое число, то есть — округление. Перепишем наш код:

Да, так гораздо лучше. Мы сможем увидеть и 55, и 89, и даже моё любимое число Фибоначчи — 144 (которое я люблю за то, что оно равняется двенадцати в квадрате). Всё будет хорошо до числа за номером 76. Которое должно быть равно 3416454622906707, а наша функция вычислит 3416454622906706. Потому что проблема ограниченной точности дробных чисел никуда не делась, мы просто затолкали её поглубже и надеялись, что она не всплывёт. Как показывает данный пример — надеялись напрасно.

На самом деле мы можем сделать ещё кое-что, чтобы спасти этот метод. Но об этом ниже. А пока — шутки в сторону. Поговорим о методе суровом, хардкорном и брутальном.

5. Следуй за белым кроликом.

Говорят, если у вас есть проблема и вам пришла в голову идея, что можно решить её с помощью регулярных выражений, то теперь у вас две проблемы. Матрицы — это регулярные выражения наоборот. Многие проблемы, если их переформулировать на языке матриц, решаются просто сами собой.

Что касается чисел Фибоначчи, для них на матричном языке можно записать вот такое очевидное тождество:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

То есть если взять пару подряд идущих чисел Фибоначчи и умножить их на такую вот незамысловатую матрицу, мы получим следующую пару. А отсюда логично следует вывод: если мы возьмём пару из нулевого и первого числа Фибоначчи, то есть нуля и единицы, и умножим их на эту матрицу в энной степени, мы получим пару из энного и эн плюс первого числа Фибоначчи. То есть, говоря по-человечески:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Можно это ещё немного упростить, отказавшись от векторов. На самом деле все необходимые значения содержатся в самой матрице:

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Замечательно, не правда ли? Осталось понять, нафига попу гармонь, если он не филармонь. В смысле — зачем такие сложности на ровном месте. А ответ прост — быстрое возведение в степень.

числа фибоначчи по номеру формула. Смотреть фото числа фибоначчи по номеру формула. Смотреть картинку числа фибоначчи по номеру формула. Картинка про числа фибоначчи по номеру формула. Фото числа фибоначчи по номеру формула

Программист скажет. что он это число помнит наизусть, и ничего умножать не нужно. Однако вопросы мемоизации мы рассмотрели выше.

Так вот, быстрое возведение в степень применимо и к матрицам, и таким образом позволяет уменьшить асимптотическую временную сложность нашей функции с O(n) до O(log n). И это очень круто — если, конечно, нам действительно так важна эта сложность. Давайте напишем код:

Вот мы и получили самый быстрый алгоритм на Диком Западе. И его, в отличие от большинства предыдущих, можно неиронично продемонстрировать на собеседовании. А в каких-нибудь математико-ёмких местах именно его от вас и будут ждать.

Я обещал ремарку относительно того, как же нам спасти метод, основанный на формуле Бине. Ответ кроется вот в этой моей статье. Там я для нужд народного хозяйства написал специальный класс корень-из-пяти-рациональных чисел, которые могут без потери точности хранить результаты арифметических действий над целыми числами и корнем из пяти. Можно взять этот класс, дополнить его методом округления и использовать для поиска чисел Фибоначчи по формуле Бине. А затем впрыснуть закись азота, применив быстрое возведение в степень.

И что самое интересное: если внимательно посмотреть, какие числа будут получаться в процессе, какие будут выполняться операции, то станет понятно, что этот метод — это то же самое матричное умножение, только под другим фасадом. Разница лишь в том, храним мы числа в двумерных массивах или в полях объекта специального класса.

На этом всё. Если вы считаете, что я упустил ещё какие-то интересные способы найти никому не нужные числа, обязательно напишите об этом в комментариях.

Есть ещё такой способ как fast doubling. Работает как и матричное умножение за O(log), но с меньшей константой в асимптотике (и на практике). Если кратко, то там используется две формулы, опираясь на которые можно быстро рекурсивно откатываться к вдвое меньшим индексам:

Реализация, кстати, получается довольно компактная.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *