Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Рекурсия из Вирта.doc
Скачиваний:
1
Добавлен:
05.11.2018
Размер:
101.89 Кб
Скачать

Когда рекурсию использовать не нужно

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

Программы, в которых следует избегать алгоритмической рекурсии, можно охарактеризовать некоторой схемой, отражающей их строение (специфично, что тут есть единственное обращение к Р в конце или начале всей конструкции):

P IF В THEN S; P END, (3.6)

P S; IF В THEN P END. (3.7)

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

i=0,1,2,3,4,5,...,

fi=1, 1, 2, 6, 24, 120,....

Первое из чисел определяется явно – f0 = 1, а последующие определяются рекурсивным образом с помощью предшествующего числа:

Такое рекуррентное отношение предполагает рекурсивный алгоритм вычисления n-го факториального числа. Если мы введем две переменные i и f, обозначающие на i-м уровне рекурсии, то обнаружим, что для перехода к следующему числу последовательности нужно проделать такие вычисления:

i++; f*=i;

откуда получаем простую рекурсивную программу:

Однако более часто употребляется и полностью эквивалентна ей запись, где вместо Р приведена так называемая процедура-функция, т. е. процедура, с которой связывается значение–результат и которую поэтому можно применять прямо как составляющую выражения. В этом случае переменная F становится излишней, а роль i явно выполняет параметр процедуры:

PROCEDURE F (I: INTEGER): INTEGER; BEGIN

IF I > 0 THEN RETURN I*F(I-1) ELSE RETURN 1 END END F;

Теперь уже ясно, что в нашем примере рекурсия крайне просто заменяется итерацией. Это проделано в такой программе:

I := 0; F : = 1;

WHILE I < n DO I := 1+1; F := I*F END;

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

fibn+1 =fibn + fibni для n > 0 (3.16)

и fib1 =1; fib0 =0.

Прямое, "наивное", переписывание приводит к рекурсивной программе:

PROCEDURE Fib(i: INTEGER): INTEGER;

BEGIN

IF n = 0 THEN RETURN 0

ELSIF n = 1 THEN RETURN 1 (3.17)

ELSE RETURN Fib(n-1)+Fib{n-2)

END

END Fib;

Вычисление fib путем обращения к Fib(n) приводит к рекурсивным активациям этой процедуры-функции. Как часто это происходит? Каждое обращение с п > 1 приводит еще к двум обращениям, т. е. общее число вызовов растет экспоненциально (рис. 3.2).

Ясно, что такая программа практического интереса не представляет.

Однако, к счастью, числа Фибоначчи можно вычислять и по итеративной схеме, где с помощью вспомогательных переменных х = fibi и у = fibi-i удается избежать повторных вычислений одной и той же величины:

i = 1; х = 1; у = 0;

WHILE i < n DO z = х; х+ = y, у := z; i++ END;

И

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