Формы рекурсивных процедур
В общем случае любая рекурсивная процедура Rec включает в некоторое множество операторов S и один или несколько операторов рекурсивного вызова.
Как было показано на предыдущем шаге, безусловные рекурсивные процедуры приводят к бесконечным процессам, и на эту проблему нужно обратить особое внимание, так как практическое использование процедур с бесконечным самовызовом невозможно. Такая невозможность вытекает из того, что для каждой копии рекурсивной процедуры необходимо выделить дополнительную область памяти, а бесконечной памяти не существует.
Следовательно, главное требование к рекурсивным процедурам заключается в том, что вызов рекурсивной процедуры должен выполняться по условию, которое на каком-то уровне рекурсии станет ложным.
Если условие истинно, то рекурсивный спуск продолжается. Когда оно становится ложным, то спуск заканчивается и начинается поочередный рекурсивный возврат из всех вызванных на данный момент копий рекурсивной процедуры.
Структура рекурсивной процедуры может принимать три разных формы:
Форма с выполнением действий до рекурсивного вызова (с выполнением действий на рекурсивном спуске):
procedure Rec;
begin
S; {операторы}
If <условие> then Rec;
end;
Форма с выполнением действий после рекурсивного вызова (с выполнением действий на рекурсивном возврате):
procedure Rec;
begin
if <условие> then Rec;
S; {операторы}
end;
3. Форма с выполнением действий как до, так и после рекурсивного вызова (с выполнением действий как на рекурсивном спуске, так и на рекурсивном возврате):
procedure Rec;
begin
S1;
if <условие> then Rec;
S2;
end;
или
procedure Rec;
begin
if <условие> then
begin
S1;
Rec;
S2;
end;
end;
Все формы рекурсивных процедур находят применение на практике. Многие задачи, в том числе вычисление факториала, безразличны к тому, какая используется форма рекурсивной процедуры. Однако есть классы задач, при решении которых программисту требуется сознательно управлять ходом работы рекурсивных процедур и функций. Поэтому глубокое понимание рекурсивного механизма, и умение управлять им по собственному желанию, является необходимым качеством квалифицированного программиста.
Первые две формы рекурсивных подпрограмм рассмотрим на примере вычисления факториала (n!), третью форму - на примере реверсивной печати вводимой строки.
Рекурсивный спуск
Для реализации универсального алгоритма вычисления факториала, работающего на спуске, в рекурсивную функцию требуется дополнительно ввести два параметра:
Mult - для выполнения до рекурсивного вызова (то есть на спуске) операции умножения накапливаемого значения факториала на очередной множитель;
m - для обеспечения независимости рекурсивной функции от имени конкретной глобальной переменной, то есть для повышения универсальности функции.
Программа Factorial_Down, которая использует рекурсивную функцию Fact_Dn, выполняющую вычисления на спуске, имеет такой вид:
program Factorial_Down;
var n : Integer;
function Fact_Dn(Mult : Longint; i, m : Integer ) :Longint;
begin
Mult := Mult * i;
{ Накопление факториала стоит до оператора рекурсивного вызова.}
{ Следовательно вычисление выполняется на спуске. }
if i = m then Fact_Dn := Mult
else Fact_Dn := Fact_Dn (Mult, i+1, m)
end;
begin
Write ('Введите число n: ');
Readln ( n );
Writeln ('Факториал n! = ' , Fact_Dn(1,1,n ));
end.
Для демонстрации выполняемых функцией Fact_Dn действий приведем таблицу трассировки значений ее параметров по уровням рекурсии. В этой таблице рассмотрен конкретный случай для n = 5.
Рис.1. Трассировка значений параметров
Рассмотренная выше программа Factorial, использующая рекурсивную функцию Fact, выполняет вычисление факториала на возврате. Но это не совсем очевидно, поскольку в функции Fact рекурсивный вызов и операция умножения совмещены в одном операторе присваивания. Для более понятной демонстрации работы на возврате, приведем программу Factorial_Up, использующую функцию Fact_Up, в которой рекурсивный вызов и оператор накопления факториала разделены явным образом.
program Factorial_Up;
var
n : Integer;
function Fact_Up(i :Integer) : Longint;
var
Mult: Longint;
begin
if i = 1 then Mult := 1
else Mult := Fact_Up (i-1);
Fact_Up := Mult * i {Накопление факториала стоит после }
{оператора рекурсивного вызова. }
{Следовательно вычисление выполняется на возврате. }
end;
begin
Write ( 'Введите число n: ');
Readln (n);
Writeln ('Факториал n! = ', Fact_Up (n));
end.
Приведем таблицу трассировки по уровням рекурсии, аналогичную таблице для функции Fact_Dn:
Рис.2. Трассировка значений параметров
