Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Data Structures and Algorithms in C++ 2e (На ру...docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.37 Mб
Скачать

3.5. Рекурсия 145

Вычисление Чисел Фибоначчи через Двойную Рекурсию

Давайте рассмотрим проблему вычисления kth Числа Фибоначчи. Вспомните из

Раздел 2.2.3, что Числа Фибоначчи рекурсивно определены следующим образом:

F0 = 0 F1 = 1 Fi = Fi-1 + Fi-2 для i> 1

Непосредственно применяя это определение, Алгоритм BinaryFib, показанный в Кодексе Frag-

ment 3.42, вычисляет последовательность Чисел Фибоначчи, используя двойную рекурсию.

Алгоритм BinaryFib (k):

Вход: Неотрицательное целое число k Продукция: kth Число Фибоначчи Fk

если k1£ тогда

возвратите k

еще

возвратите BinaryFib (k- 1) + BinaryFib (k- 2)

Кодовый Фрагмент 3.42: Вычисление kth Числа Фибоначчи, используя двойную рекурсию.

К сожалению, несмотря на определение Фибоначчи, бывшее похожее на набор из двух предметов, повторяются - Сьон, используя эту технику неэффективен в этом случае. Фактически, это берет показательное число требований вычислить kth Число Фибоначчи таким образом. Определенно, позвольте nk обозначить число требований, выполненных в выполнении BinaryFib (k). Затем у нас есть следующие ценности для nk's:

n0 = 1 n1 = 1 n2 = n1 + n0 + 1 = 1 + 1 + 1 = 3 n3 = n2 + n1 + 1 = 3 + 1 + 1 = 5 n4 = n3 + n2 + 1 = 5 + 3 + 1 = 9 n5 = n4 + n3 + 1 = 9 + 5 + 1 = 15 n6 = n5 + n4 + 1 = 15 + 9 + 1 = 25 n7 = n6 + n5 + 1 = 25 + 15 + 1 = 41 n8 = n7 + n6 + 1 = 41 + 25 + 1 = 67

Если мы следуем за образцом вперед, мы видим, что число требований более чем удваивается

для каждого два последовательных индекса. Таким образом, n4 более двух раз n2, n5 более двух раз n3, n6 более двух раз n4 и так далее. Таким образом, nk> 2k/2, что означает, что BinaryFib (k) сделал много звонков, которые показательны в k. Другими словами, использование двойной рекурсии, чтобы вычислить Числа Фибоначчи очень неэффективно.

146

Глава 3. Множества, связанные списки и рекурсия

Вычисление Чисел Фибоначчи через Линейную Рекурсию

Основная проблема с подходом выше, основанный на двойной рекурсии, состоит в том, что вычисление Чисел Фибоначчи - действительно линейно рекурсивная проблема. Это не хороший кандидат на использование двойной рекурсии. Мы просто были склонены к использованию двойной рекурсии из-за способа, которым kth Число Фибоначчи, Fk, зависит от двух предыдущих ценностей, Fk-1 и Fk-2. Но мы можем вычислить Fk, намного более эффективно используя линейную рекурсию.

Чтобы использовать линейную рекурсию, однако, мы должны немного пересмотреть prob-lem. Один способ достигнуть этого преобразования состоит в том, чтобы определить рекурсивную функцию, которая вычисляет пару последовательных Чисел Фибоначчи (Fk, Fk-1) использование соглашения f-1 = 0. Тогда мы можем использовать линейно рекурсивный алгоритм, показанный в Кодовом Frag-ment 3.43.

Алгоритм LinearFibonacci (k):

Вход: неотрицательное целое число k Продукция: Пара Чисел Фибоначчи (Fk, Fk-1)

если k1£ тогда

возвратитесь (k, 0)

еще

(я, j)¬ LinearFibonacci (k- 1)

возвратитесь (я + j, i)

Кодовый Фрагмент 3.43: Вычисление kth Числа Фибоначчи, используя линейную рекурсию.

Алгоритм, данный в Кодовом Фрагменте 3,43 шоу, что использование линейной рекурсии, чтобы вычислить Числа Фибоначчи намного более эффективно, чем использование двойной рекурсии. Так как каждый рекурсивный вызов к LinearFibonacci уменьшает аргумент k на 1,

оригинальное требование LinearFibonacci (k) приводит к серии k- 1 дополнительное требование. Это

вычисление kth Числа Фибоначчи через линейную рекурсию требует функции k

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

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