Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
21
Добавлен:
26.03.2015
Размер:
498.69 Кб
Скачать
      1. Когда нужно использовать рекурсию

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

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

    1. Хвостовая рекурсия

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

Private Function Factorial(num As Integer) As Integer

If num <= 0 Then

Factorial = 1

Else

Factorial = num * Factorial(num - 1)

End If

End Function

Private Function GCD(A As Integer, B As Integer) As Integer

If B Mod A = 0 Then

GCD = A

Else

GCD = GCD(B Mod A, A)

End If

End Function

Private Function BigAdd(N As Double) As Double

If N <= 1 Then

BigAdd = 1

Else

BigAdd = N + BigAdd(N - 1)

End If

End Function

Во всех этих функциях, последнее действие перед завершением функции — это рекурсивный шаг. Этот тип рекурсии в конце процедуры называется хвостовой рекурсией(tailrecursion).

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

Рассмотрим общий случай рекурсивной процедуры:

Private Sub Recurse(A As Integer)

' Выполняются какие‑либо действия, вычисляется B, и т.д.

Recurse B

End Sub

Эту процедуру можно переписать без рекурсии как:

Private Sub NoRecurse(A As Integer)

Do While (not done)

' Выполняются какие‑либо действия, вычисляется B, и т.д.

A = B

Loop

End Sub

Эта процедура называется устранением хвостовой рекурсии(tailrecursionremoval). Этот прием не изменяет время выполнения программы. Рекурсивные шаги просто заменяются проходами в циклеWhile.

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

Некоторые компиляторы автоматически устраняют хвостовую рекурсию, но компилятор VisualBasicэтого не делает.

Используя устранение хвостовой рекурсии, легко переписать функции факториала, наибольшего общего делителя, и BigAddбез рекурсии. Эти версии используют зарезервированное словоByValдля сохранения значений своих параметров для вызывающей процедуры.

Private Function Factorial(ByVal N As Integer) As Double

Dim value As Double

value = 1# ' Это будет значением функции.

Do While N > 1

value = value * N

N = N - 1 ' Подготовить аргументы для "рекурсии".

Loop

Factorial = value

End Function

Private Function GCD(ByVal A As Double, ByVal B As Double) As Double

Dim B_Mod_A As Double

B_Mod_A = B Mod A

Do While B_Mod_A <> 0

' Подготовить аргументы для "рекурсии".

B = A

A = B_Mod_A

B_Mod_A = B Mod A

Loop

GCD = A

End Function

Private Function BigAdd(ByVal N As Double) As Double

Dim value As Double

value = 1# ' ' Это будет значением функции.

Do While N > 1

value = value + N

N = N - 1 ' подготовить параметры для "рекурсии".

Loop

BigAdd = value

End Function

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

Для функции BigAdd, тем не менее, разница огромна. Рекурсивная версия приводит к переполнению стека даже для довольно небольших входных значений. Поскольку нерекурсивная версия не использует стек, она может вычислять результат для значенийNвплоть до 10154. После этого наступит переполнение для данных типаdouble. Конечно, выполнение 10154шагов алгоритма займет очень много времени, поэтому возможно вы не станете проверять этот факт сами. Заметим также, что значение этой функции совпадает со значением более просто вычисляемой функцииN* N(N + 1) / 2.