1.2. Программирование рекуррентных соотношений

Программно рекуррентные соотношения можно реализовать с помощью обычных циклов (итерационно) и с помощью рекурсии.

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

Приведем два примера рекурсивных подпрограмм:

  1. Вычисление n! Вначале запишем рекурсивное соотношение

а) 0!=1; б) n!=(n-1)!*n.

После чего составим рекурсивную функцию:

... Листинг1.1

function Fn(i:Word):Word;

begin

if i=0 then Fn:=1

else Fn:=i*Fn(i-1);

end;

Обращение к функции для вычисления 6! – u:=Fn(6);

Теперь запишем более сложную функцию для нахождения максимального элемента в массиве из n чисел:

Листинг1.2

Type mas1=array[1..100] of extended;

Function Mxa(a:mas1;n:word):extended;

Var z:extended;

function Mxr(i:Word):extended;

begin

if i=1 then Mxr:=a[1]

Else begin

Z:=Mxr(i-1);

If z>a[i] then Mxr:=z

else Mxr:=a[i];

end;

end; //конец Mxr

begin

Mxa:=Mxr(n)

End;

Обращение: чтение a[i], i=1..n; ma:=Mxa(a,n);

При выполнении правильно организованной рекурсивной подпрограммы вначале осуществляется многократный переход от некоторого текущего уровня (i) организации алгоритма к нижнему (или верхнему) уровню последовательно, до тех пор, пока, наконец не будет получено тривиальное решение задачи. В вышеприведенных примерах: при i=0 или i=1. При обратном переходе от полученного тривиального решения к решению для заданного i=n выполняется решение последовательности элементарных подзадач с нарастанием i (или убыванием), которые здесь описаны после первого else.

Рекурсивная форма программирования алгоритма обычно выглядит изящнее итерационной и дает более компактный текст программы. Однако, такое изящество дается не бесплатно. При каждом переходе к следующему уровню (рекурсивной активации процедуры) происходит создание и запоминание всех ее локальных и формальных параметров. В результате после n-й активации в памяти будет находиться список из n+1 комплектов таких параметров. Организован этот список таким образом, что комплект параметров, засланный в список последним, будет вызываться первым, и наоборот. Такая структура организации списка данных в памяти получила название стек. Под организацию стека каждой программе системой Delphi выделяется определенный ограниченный ресурс оперативной памяти. В результате рекурсивная версия программирования алгоритма выполняется медленнее чем, например, итерационная и может привести к исключительной ситуации EstackOverFlow – переполнение программного стека.

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

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

Листинг 1.3

Procedure B(j:byte);Forward;

Procedure A(i:byte);

begin

...

B(i);

end;

Procedure B;

begin

...

A(j);

end;

В этом примере обращение к процедуре B(i) записано раньше, чем ее описание, что в принципе запрещено. Для разрешения этой ситуации используется опережающее описание с помощью стандартной директивы Forward.