Идеи реализации

Удивительным образом числа Фибоначчи получаются по формуле Бине F n = φ n ψ n 5 , где φ = 1 + 5 2 = 1,61803398874989…  — число Фидия, ψ = 1 5 2 = 1 φ . Удивительно здесь то, что, несмотря на присутствие иррационального числа Фидия, формула даёт целые числа.

Докажем формулу по индукции. Для этого нужно доказать равенство φ n ψ n + φ n + 1 ψ n + 1 = φ n + 2 ψ n + 2 , которое после преобразования превращается в φ n 1 + φ φ 2 = ψ n 1 + ψ ψ 2 . Осталось заметить, что оба числа, φ и ψ, служат корнями многочлена 1 + α α 2 , так что выражения в скобках в обеих частях равенства обращаются в ноль одновременно. Вот и доказан индуктивный переход. Что же касается базы индукции, то она совершенно очевидна при n = 0 и n = 1 .

Увы, это доказательство не приносит полного удовлетворения, поскольку не отвечает на вопрос, откуда вообще берутся подобные удивительные формулы. Мы выясним это в разделе «Вывод формулы Бине».

Обратим внимание на то, что ψ n 5 < 1 при любом целом неотрицательном n. Поэтому, зная, что формула Бине даёт целые числа, можно утверждать, что F n = φ n 5 (здесь забавные скобки означают округление заключённого в них числа до ближайшего целого).

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

Мы провели эксперимент, и выяснилось, что вычисление по формуле Бине даёт первую ошибку для числа F 71 : вместо правильного значения 308061521170129 получилось 308061521170130.

Для вывода формулы Бине мы без колебаний выбрали из миллиона различных методов самый, на наш взгляд, впечатляющий — метод производящих функций.

Производящей функцией числовой последовательности A = A 0 A 1 A 2 A 3 называется выражение A z = A 0 + A 1 z + A 2 z 2 + A 3 z 3 + (элементы последовательности умножаются на z в степени, равной номеру элемента, и затем складываются в бесконечную сумму). Соответствие между последовательностью и её производящей функцией (взаимно-однозначное, между прочим) будем обозначать как A A z .

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

Сделаем небольшое замечание, которое читатели, не знакомые с производными, могут спокойно пропустить. Попытки вычислить A 1 как значение A z A 0 z при нулевом z приводят к неопределённости типа 0 0 , которая раскрывается по правилу Лопиталя: A z A 0 z 0 = A 0 (штрих означает производную по z). Впрочем, и так понятно, что A n = A n 0 n ! ( A n n-я производная по z).

Теперь можно заняться формулой Бине. Заметим, что рекуррентное соотношение F n = F n 1 + F n 2 превращается в уравнение для производящей функции F z : F = F + F F z F 0 F 1 z z 2 = F z F 0 z + F z , откуда, с учётом равенств F 0 = 0 и F 1 = 1 , получаем F z = z 1 z z 2 .

Замечательно, мы нашли производящую функцию последовательности Фибоначчи, но как получить из неё формулу Бине? Заметим, производящая функция рациональная, то есть отношение двух многочленов. Она может быть представлена в виде суммы простейших дробей вида S k s k + z , где S k и s k — некоторые числа. Для этого заметим, что 1 z z 2 = φ + z ψ + z . Поэтому F z = z φ + z ψ + z = P φ + z + Q ψ + z = P ψ + z + Q φ + z φ + z ψ + z , откуда для P и Q получаем систему линейных уравнений: P ψ + Q φ = 0 , P + Q = 1 . Из неё находим P = φ 5 , Q = ψ 5 . Теперь после некоторых преобразований с учётом равенства ψ = 1 φ получаем: F z = 1 5 1 1 φ z 1 1 ψ z .

По формуле суммы бесконечной геометрической прогрессии 1 1 q z = 1 + q z + q 2 z 2 + q 3 z 3 + , и, таким образом, функция 1 1 q z служит производящей для геометрической прогрессии 1 q q 2 q 3 . Следовательно, F является производящей для разности геометрических прогрессий со знаменателями φ и ψ и начальными членами, равными 1 5 , что и требовалось доказать. Это истинное торжество метода производящих функций.

Пожалуй, самый простой подход к вычислению числа Фибоначчи по его заданному номеру — это вычисление по рекуррентной формуле. Заведём две переменные a и b для хранения двух соседних чисел Фибоначчи, и присвоим им начальные значения 0 и 1. Затем в цикле n раз сдвинемся по последовательности, заменяя a на b, а b на a + b . Здесь важно отметить, что присваивания в цикле следует производить одновременно, так как после присваивания a b в выражении a + b окажется новое значение переменной a, в то время как там требуется старое. Так что параллельное присваивание — это лучший выбор. a b 0 1 цикл n раз a b b a + b конец цикла вывод a

Рекуррентная сводит задачу вычисления F n к задачам вычисления чисел F n 1 и F n 2 . Это соображение приводит нас к рекурсивному решению, в котором вычислению F n посвящена отдельная процедура F с параметром n. Она возвращает n, если n < 2 . В противном случае она дважды рекурсивно вызывает сама себя — с параметрами n 1 и n 2 , после чего возвращает сумму значений, возвращённых при этих вызовах. Проще некуда.

Но, несмотря на изящество и простоту алгоритма, мы получаем программу-катастрофу. Мы убедились в этом, попытавшись вычислить с её помощью всего-то F 40 — это заняло на нашем компьютере около 108 секунд. Чтобы дождаться F 50 , у нас уже не хватило терпения.

Постараемся разобраться, что же приводит к столь долгой работе программы. На рисунке 9.1. «Рекурсивные вызовы при вычислении чисел Фибоначчи» мы протянули стрелки от одного числа Фибоначчи к другому, если вычисление первого из них повлекло рекурсивный вызов для вычисления другого.

Рисунок 9.1. Рекурсивные вызовы при вычислении чисел Фибоначчи

Теперь, глядя на рисунок, подсчитаем, сколько раз вызывалась рекурсивная процедура для вычисления F 5 :

F 5 1 раз
F 4 1 раз (для вычисления F 5 )
F 3 2 раза (1 раз для вычисления F 5 и 1 раз для F 4 )
F 2 3 раза (1 раз для вычисления F 4 и 2 раза для F 3 )
F 1 5 раз (2 раза для вычисления F 3 и 3 раза для F 2 )
F 0 2 раза (для вычисления F 2 )

Всего для вычисления F 5 рекурсивная процедура вызывалась 14 раз, причём для каждого значения параметра в среднем больше двух раз. Это явный перебор, но пока это не производит столь жуткого впечатления. Что же будет при больших n?

Наблюдательный читатель наверняка заметил, что числа в правой колонке таблицы сами образуют (за исключением последнего) последовательность Фибоначчи. Их сумма и есть нужное число рекурсивных вызовов. Как же ведёт себя сумма элементов последовательности Фибоначчи при больших значениях n? Если сами числа растут примерно как геометрическая прогрессия со знаменателем φ, то для суммы первых n чисел хорошее приближение даёт формула суммы геометрической прогрессии: φ n 1 5 φ 1 . В этой формуле главный член, обеспечивающий быстрый рост — это φ n в числителе. Так что сумма растёт с той же самой скоростью, как геометрическая прогрессия со знаменателем φ.

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

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

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

Матрица представляет собой прямоугольную таблицу, заполненную числами: a 1 , 1 a 1 , 2 a 1 , n a k , 1 a k , 2 a k , n . Каждое число (элемент матрицы) снабжается двумя индексами: первый — номер строки, а второй — столбца. Упомянутую матрицу можно кратко обозначить как a i , j , i = 1 k , j = 1 n .

Две матрицы одинакового размера можно поэлементно складывать: a i , j + b i , j = a i , j + b i , j . Можно определить также поэлементное умножение матрицы на число: λ a i , j = λ a i , j .

Но по-настоящему полезным для нас будет умножение матриц. Две матрицы можно перемножить, если ширина (количество столбцов) первой матрицы совпадает с высотой (количеством строк) второй. В результате умножения получается матрица, у которой высота берётся от первого сомножителя, а ширина — от второго: a i , l b l , j = c i , j = l a i , l b l , j . Здесь индекс суммирования l изменяется от единицы до ширины первой матрицы (или, если угодно, до высоты второй). В словесном виде правило умножения матриц звучит так: чтобы получить элемент с индексами i , j произведения двух матриц, следует перемножить элементы i-й строки первой матрицы и j-го столбца второй, и сложить полученные произведения. Теперь понятно, откуда берётся требование совпадения ширины первого сомножителя и высоты второго.

Проиллюстрируем умножение матриц на примере: 2 3 1 5 4 6 1 7 3 0 2 4 1 5 8 2 0 4 = 2 1 + 3 2 + 1 8 2 7 + 3 4 + 1 2 2 3 + 3 1 + 1 0 2 0 + 3 5 + 1 4 5 1 + 4 2 + 6 8 5 7 + 4 4 + 6 2 5 3 + 4 1 + 6 0 5 0 + 4 5 + 6 4 = 12 24 9 19 61 31 11 44 .

Ещё раз обращаем внимание читателя, что матричные операции (сложение и умножение) определены лишь для матриц, чьи размеры должным образом соответствуют друг другу. Это условие удовлетворяется, если рассматривать квадратные матрицы одного и того же размера — их можно и складывать, и умножать без препятствий. Особо заметим, что квадратные матрицы можно также возводить в натуральную степень, умножая на себя нужное количество раз. Сложение и умножение матриц естественным образом обобщают сложение и умножение чисел, если отождествлять числа с матрицами размера 1 × 1 . Сложение и умножение матриц наследует у чисел все арифметические законы, за исключением одного — перестановочного закона умножения.

Довольно интриговать читателей. Пора уже объяснить, какое отношение матрицы имеют к числам Фибоначчи.

Оказывается, рекуррентное соотношение для чисел Фибоначчи можно записать в матричной форме: F n + 1 F n + 2 = 0 1 1 1 F n F n + 1 = F n + 1 F n + F n + 1 . В качестве очевидного следствия этого равенства получим F n + k F n + k + 1 = 0 1 1 1 k F n F n + 1 . В частности, при n = 0 получим F k F k + 1 = 0 1 1 1 k 0 1 .

Возможно, читатели уже догадываются, что нам предстоит быстрое возведение в степень матрицы 0 1 1 1 (о быстром возведении чисел в степень мы писали в главе 8. «Быстрое возведение в степень»). Для получения k-го числа Фибоначчи нужно возвести эту матрицу в k-ю степень и у получившейся матрицы взять элемент, стоящий в правом верхнем углу — это результат умножения на матрицу 0 1 . Дело в том, что в алгоритме быстрого возведения в степень нет ничего такого, что помешало бы быстро возводить в степень матрицы. Остался единственный вопрос, на который необходимо ответить, чтобы адаптировать алгоритм быстрого возведения в степень для матриц: что считать результатом возведения квадратной матрицы в нулевую степень? Каков аналог единицы в мире матриц? Это единичная матрица, всюду заполненная нулями, за исключением главной диагонали, идущей с северо-запада на юго-восток, и содержащей единицы: I = 1 0 0 0 1 0 0 0 1 . Легко проверить, что умножение такой матрицы на любую матрицу подходящего размера (как слева, так и справа) ничего не изменяет: I A = A I = A .

Информатика-54© А. Н. Швец