Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект Лекций «программирование На Языке Высокого Уровня Си» По Информатике (Попов Д. И.).pdf
Скачиваний:
156
Добавлен:
07.10.2014
Размер:
1.31 Mб
Скачать

Вызов функций в языке Си

Вызов функции имеет следующий формат: <Адресное выражение> ([<список выражений>])

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

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

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

Выполнение вызова функции происходит следующим образом:

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

2.Происходит присваивание значений фактических параметров соответствующим формальным параметрам.

3.Управление передается на первый оператор функции.

4.Выполнение оператора return в теле функции возвращает управление и возможно, значение в вызывающую функцию. При отсутствии оператора return

114

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

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

int (*fun)(int x, int *y);

Здесь объявлена переменная fun как указатель на функцию с двумя параметрами: типа int и указателем на int. Сама функция должна возвращать значение типа int. Круглые скобки, содержащие имя указателя fun и признак указателя *, обязательны, иначе следующая запись будет интерпретироваться как объявление функции fun возвращающей указатель на int.

int *fun (intx,int *y);

Вызов функции возможен только после инициализации значения указателя fun и имеет вид:

(*fun)(i,&j);

В этом выражении для получения адреса функции, на которую ссылается указатель fun, используется операция разадресации *.

Указатель на функцию может быть передан в качестве параметра функции. При этом разадресация происходит во время вызова функции, на которую ссылается указатель на функцию. Присвоить значение указателю на функцию можно в операторе присваивания, употребив имя функции без списка параметров.

double (*fun1)(int x, int y); double fun2(int k, int l);

fun1 = fun2; /* инициализация указателя на функцию */

(*fun1)(2,7); /* обращение к функции */

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

Пример 27. Разработать функцию, вычисляющую производную от функции cos(x) с использованием указателя на функцию в качестве параметра.

115

double proiz(double x, double dx, double (*f)(double x) );

 

double fun(double z);

 

 

 

int main()

 

 

 

 

{

/* точка вычисления производной

*/

double x;

double dx;

/* приращение

*/

 

double z;

/* значение производной */

*/

scanf("%f,%f",&x,&dx); /* ввод значений x и dx

z=proiz(x,dx,fun);

/* вызов функции */

*/

printf("%f",z); /* печать значения производной

return 0;

 

 

 

 

}

 

 

 

 

double proiz(double x,double dx, double (*f)(double z) )

 

{ /* функция вычисляющая производную

*/

 

double xk,xk1,pr;

 

 

 

xk=fun(x);

}

xk1=fun(x+dx); pr=(xk1/xk-1e0)*xk/dx; return pr;

double fun( double z)

{ /* функция от которой вычисляется производная */ return (cos(z));

}

Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме

z=proiz(x,dx,cos);

а для вычисления производной от функции sin(x) в форме

z=proiz(x,dx,sin);

Рекурсивные функции

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

116

Определение рекурсивных объектов в математике происходит по индукции. При этом сначала формулируется базис индукции, как рекурсивное определение исключительных случаев при вычислении функции, а затем шаг индукции, как рекурсивное правило построения того же объекта. Под индукцией понимается метод доказательства утверждений, который строится на базе индукции при n = 0, 1, затем утверждение полагается правильным при n=n, и проводится доказательство для n+1.

Часто используется термин рекуррентные соотношения, который определяет математическое задание функции с помощью рекурсии. Основной задачей исследования рекурсивно заданных функций является получение f(n) в явной или как еще говорят «замкнутой» форме, т.е. в виде аналитически заданной функции, выражающейся через саму себя.

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

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

имеет аналог без использования рекурсии, с помощью последовательных итераций (тезис Черча-Тьюринга). Изучению и исследованию рекурсивных функций посвящены целые разделы современной математики – теория вычислений, теория вычислимости, теория алгоритмов, теория рекурсивных функций. Практика показывает, что использовать в программировании рекурсивные процедуры и функции необходимо осторожно, поскольку непонимание механизмов рекурсии может привести к бесконечным циклам, зависанию программы, большим затратам времени выполнения и используемой памяти.

117

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

 

 

 

int P();

int P();

int P();

{

P;

{

операторы;

{

операторы;

 

операторы;

 

P;

 

P;

}

 

}

 

} операторы;

 

Рекурсивный подъём

 

Рекурсивный спуск

 

Рекурсивный спуск и

 

 

 

 

 

рекурсивный подъём

Рис.33. Способы вызова рекурсивных подпрограмм

Пример 28. Представим в виде рекурсии функцию вычисления суммы двух натуральных чисел.

Сначала проанализируем задачу.

Пусть X=23 – это первое число, второе число Y=34. Сумма их равна: F1 = Sum (X, Y) = X+Y = Sum (23, 34)= 23+34=57

Уменьшим X на 1 и опять найдем сумму, X=23–1=22. Тогда

F2 = Sum (X–1,Y) = X–1+Y = Sum (22,34)= 22+34=56=F1 – 1

Видим, что при уменьшении первого слагаемого на 1, сумма тоже уменьшилась на 1. Последнее можно выразить так:

F2 = Sum (X–1,Y) = (X–1) + Y = (X+Y) – 1= Sum (X,Y)–1.

Т.е. получили рекуррентное соотношение: Sum (X–1,Y) = Sum (X,Y)–1, отсюда:

Sum (X,Y) = Sum (X–1,Y)+1.

Т.е. для вычисления суммы некоторого числа X с числом Y нужно вычислить сумму предыдущего числа X – 1 c Y и прибавить к ней 1.

Теперь нужно определить граничные условия или условие завершения рекурсии (базис индукции). Мы видим, что на каждом шаге X уменьшается на 1. До какой границы это нужно делать? Очевидно, пока Х не станет равным 0, т.к. по условию – числа натуральные. Но сумма любого числа с нулем дает в результате это число, т.е. граничное условие будет таким Sum(0,Y) = Y.

118

if (x == 0) return(y);
else return(SumXY(x-1,y)+1);

Итак, окончательная рекуррентная формула для сложения двух натуральных чисел такая:

ì

Y е ,с Xл=

;

S u( mXY =,)í

S u( Xm-1 Y, +

1) е, с Xл>

î

Для такой формулы просто написать рекурсивную функцию:

int SumXY (int x, int y)

{

if (x == 0) return(y);

}

else return(SumXY(x-1,y)+1);

Аналогично можно вывести рекуррентные соотношения для операций целочисленного (натурального) вычитания, умножения, деления, вычисления остатка от деления:

S

u( XbY

ì

X е

,с Yл=

;

 

 

=,)í

S

u(

XbY-1, -1)

е, с

>

 

 

î

M

 

 

ì

X е

,с Yл=

;

 

 

(uXYl t=,)í

M

 

(uXYl-t1,

+)X е

,с Xл>

 

 

 

î

 

D

(iXvY

ì

0 е,

с Xл£ иY

;

 

 

=,)í

D

(iXv-Y Y,+

1) е,

с Xл>

и0

 

 

î

 

 

 

ì

0,е

с лXи= Y

;

 

 

M

 

 

ï

X е,с лX<иY

;

 

 

O( XDY,=)í

 

 

 

 

 

ï

M

 

O( XD-Y Y, е) ,с

лXи>

Y

 

 

 

î

 

Пример 29. Напишем программу для реализации этих рекурсивных функций:

int SumXY (int x, int y)

{

119

}

int SubXY (int x, int y)

{

if (y == 0) return(x);

}

else return(SubXY(x,y-1)-1);

int MultXY (int x, int y)

{

if (y == 1) return(x);

}

else return(MultXY(x,y-1)+x);

int DivXY (int x, int y)

{

if (x <= y) return(0);

}

else return(DivXY(x-y,y)+1);

int MODXY (int x, int y)

{

if (x < y) return(x); else

}

if (x == y) return(0); else return(MODXY(x-y,y));

int main()

{

int a,b;

printf("Введите целое положительное A:\n"); scanf("%i",&a);

printf("Введите целое положительное B:\n"); scanf("%i",&b);

}

printf(" A + B = %d" , SumXY(a,b)); printf(" A - B = %d" , SubXY(a,b)); printf(" A * B = %d" , MultXY(a,b)); printf(" A / B = %d" , DivXY(a,b)); printf(" A mod B = %d" , MODXY(a,b));

Пример 30. Напишем программу для реализации алгоритма Евклида нахождения наибольшего общего делителя (НОД) двух натуральных чисел. Данный алгоритм был впервые описан в книге Евклида "Начала" (около 300 г. до н.э.). Суть алгоритма в следующем: пусть m и n одновременно неравные 0 целые неотрицательные числа и пусть m>n. Тогда если n=0, то НОД(m,n)=m; если же n≠0, то для чисел m, n, r (где r – остаток от деления m на n) выполняется равенство НОД(m,n)=НОД(n,r).

Из описания алгоритма выводим рекуррентное соотношение:

120

ì

m е ,с

лn= и0

;

N O( mnD=),í

N O(

nmDM,

On е)D,с nл< и0

î

Кроме того, вычислим на основе НОД наименьшее общее кратное (НОК),

m ×n

вспомнив формулу связи между НОД и НОК: НОК (m, n) = НОД (m, n) .

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

Алгоритм будет такой:

int NODWhile (int m, int n)

{

int x;

while (n != 0) { m = m % n;

x = m; m = n;

} n = x; //меняем местами m и n – по алгоритму Евклида

return(m);

}

Итак, программа, использующая написанные функции будет следующей:

int NODWhile (int m, int n)

{

int x;

while (n != 0)

{ m = m % n; x =m;

m =n;

} n =x; // меняем местами m и n

return(m);

}

int NOD (int m, int n)

{

}

if (n == 0) return(m); else return(NOD(n,m % n));

int main()

{

int m,n;

printf("Введите М\n"); scanf("%d", &m); printf("Введите N\n"); scanf("%d", &n);

121

printf("n=%d m=%d NOD=%d\n", n, m, NOD(m,n)); printf("n=%d m=%d NODWhile=%d\n", n, m,NODWhile(m,n)); printf("n=%d m=%d NOK=%d\n", n, m, ceil(m*n/NOD(m,n)));

}

Пример 31. Напишем функции рекурсивного и не рекурсивного вычисления факториала числа и вывода чисел Фибоначчи.

Напомним, факториалом числа N называется функция, обозначаемая, как N!, которая последовательно вычисляет произведение всех целых чисел от 1 до

N включительно, т.е.

N!=1×2 ×3×... ×(N -1) × N . Для приближенного

вычисления

факториала больших

чисел N может использоваться формула

Стирлинга:

N!»

N N ×

 

 

.

 

 

2πN

 

 

 

 

 

 

 

 

eN

 

 

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

Ф( n=

ì

1 е,

с

лn=и1 , 2

;

)í

Ф(

n-

2 +)Ф( n-1

)е ,с nл>и2.

 

î

Найдем рекуррентное соотношение для вычисления функции факториала. Для этого заметим, что 1!=1, N!=(N-1)!×N. Таким, образом:

ì

1 е, с

лNи= 1

;

N = !í

( N-1

)× N! е,с

лN>и1.

î

Алгоритм вычисления чисел Фибоначчи без рекурсии основывается на том, что необходимо запомнить в переменных prelast, last два последних числа Фибоначчи, чтобы вычислить следующее. Затем предыдущее можно «забыть» и перейти к вычислению следующего:

1.Начало

2.prelast = 1; last = 1;

122

3.Если n > 2, то сделать (n − 2) раз:

3.1.c = prelast + last;

3.2.prelast = last;

3.3.last = c;

4.Выдать ответ last;

5.Конец

Вычисление чисел Фибоначчи также осуществляется по формуле Бине:

 

 

 

ææ

+

 

ön

æ

-

 

ön ö

 

 

1

5

5

 

Ф(n) =

 

çç1

÷

ç1

÷ ÷

. Это выражение всегда принимает целые

 

 

 

 

 

 

 

5

çç

2

 

÷

-ç

2

 

÷ ÷

 

èè

 

ø

è

 

ø ø

 

значения.

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

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

int Fib (int n)

{

}

if ((n == 1) || (n == 2)) return(1); else return(Fib(n-1)+Fib(n-2));

int FibCircle (int n)

{

int prelast,last,c,i;

if ((n == 1) || (n == 2)) return(1); else

{ prelast = 1; last = 1;

for(i=1;i<n-2;i++)

{ c = prelast + last; prelast = last; last = c;

} }

}

return(last);

int FibBine (int n)

{

}

return(ceil(1/sqrt(5) * (pow((1+sqrt(5))/2,n) - pow((1-sqrt(5))/2,n))));

int Factorial (int n)

{

if (n == 1) return(1);

123

else return(Factorial(n-1)*n);

}

int FactorialNotRec (int n)

{

int i,f = 1; for(i=1;i<n;i++)

{

} f = f*i;

return(f);

}

int FactorialStirling (int n)

{

}

return(ceil(pow(n,n)*sqrt(2*M_PI*n)/exp(n)));

int _tmain(int argc, _TCHAR* argv[])

{

int i,n;

printf("Введите N:\n"); scanf("%d", &n); for(i=1;i<n;i++)

{printf("FibR[%d]\n", Fib(i)); printf("FibC[%d]\n", FibCircle(i)); printf("FibB[%d]\n", FibBine(i));

}

printf("Recurs : %d! = %d\n", n, Factorial(n)); printf("Circle : %d! = %d\n", FactorialNotRec(n)); printf("Stirling : %d! = %d\n", FactorialStirling(n));

system("pause");

}

Интересно, что числа Фибоначчи были придуманы математиком Леонардо Фибоначчи в начале XXIII века, когда он размышлял над вопросом: «Сколько пар кроликов в один год родится от одной пары». В последствии в науке, искусстве, природе, во многих других приложениях и сферах деятельности человека числа Фибоначчи получили широкое распространение, порой даже мистическое. Например, выяснилось, что в расположении листьев на ветке семян подсолнечника, шишек сосны проявляет себя ряд Фибоначчи. Закономерности на основе ряда проявляются в энергетических переходах элементарных частиц, в строении некоторых химических соединений, в планетарных и космических системах, в генных структурах живых организмов. Закономерности на основе ряда Фибоначчи есть в строении отдельных органов человека и тела в целом, а также проявляются в биоритмах и функционировании головного мозга и

124

зрительного восприятия, в строении молекулы ДНК. С помощью этого ряда нашли закономерность и порядок в расстояниях между планетами солнечной системы. Однако один случай, который, казалось бы, противоречил закону: между Марсом и Юпитером не было планеты. Сосредоточенное наблюдение за этим участком неба привело к открытию пояса астероидов. Пропорции построения Египетских пирамид также основываются на ряде Фибоначчи. На основе чисел Фибоначчи получено золотое сечение (золотая симметрия) – это пропорции, применяемые в архитектуре, живописи. Например, известная картина Леонардо да Винчи «Мона Лиза» буквально «испещрена» такими пропорциями.

Приведем еще несколько примеров рекурсивного задания функций:

 

 

 

ì1,е с лnи= 0;

 

1. f(n)= 2

n

=

ï

n− 1

 

í

 

 

 

ï f(n- 1)+ f(n- 2)+ ..+. 1=

å

 

 

 

î

i= 0

f(i)+ 1.

2.f(n)= 2n

3.f(n)= an

=

ì

1,е

сn=л0;и

í

2× f(n- 1) .

 

î

=

ì

1,е

сn=л0;и

í

a× f(n- 1) .

 

î

 

 

 

 

ì

1,е с nл= и0;

4. f(n)=

2

Ф(n)

=

ï

2,е с nл= и1;

 

í

 

 

 

 

ï

f(n- 1)× f(n- 2) .

 

 

 

 

î

Первые два примера показывают, что одна и та же функция может иметь различные рекурсивные определения.

Анализ трудоемкости рекурсивных алгоритмов

125

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

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

Рассмотрим пример для функции вычисления факториала N!=F(N) при N=5 (рис.34).

Цепочка

 

Цепочка

рекурсивных

 

рекурсивных

возвратов

 

вызовов

(рекурсивный

 

(рекурсивный

подъем)

 

спуск)

 

 

 

126

Рис.34. Дерево рекурсии при вычислении факториала числа 5

При обращении к рекурсивной подпрограмме в памяти создаётся новая копия подпрограммы со всеми локальными переменными. Такие копии будут порождаться до выхода на граничное условие. Очевидно, в случае отсутствия граничного условия, неограниченный рост числа таких копий приведёт к аварийному завершению программы за счёт переполнения памяти (стека). Порождение все новых копий рекурсивной подпрограммы до выхода на граничное условие называется рекурсивным спуском. Максимальное количество копий рекурсивной подпрограммы, которое одновременно может находиться в памяти компьютера, называется глубиной рекурсии. Завершение работы рекурсивных подпрограмм, вплоть до самой первой, инициировавшей рекурсивные вызовы, называется рекурсивным подъёмом.

Дерево рекурсивных вызовов может иметь и более сложную структуру, если на каждом вызове порождается несколько обращений. Например, дерево рекурсий для чисел Фибоначчи Ф(N) при N=6 представлено на рис.35:

 

 

 

 

Ф(6)

 

 

Ф(5)

 

Ф(4)

Ф(4)

Ф(3)

 

Ф(3)

Ф(2)

Ф(3)

Ф(2)

Ф(2)

Ф(1)

Ф(2)

Ф(1)

Ф(2)

Ф(1)

 

 

 

 

Рис.35. Дерево рекурсий для чисел Фибоначчи Ф(N) при N=6

При вычислении значения Ф(6) будут вызваны процедуры вычисления Ф(5) и Ф(4). В свою очередь, для вычисления последних потребуется вычисление двух пар Ф(4), Ф(3) и Ф(3), Ф(2). Можно заметить, что Ф(3) вычисляется три раза, Ф(2) – пять раз. Если рассмотреть вычисление Ф(n) при больших n, то повторных вычислений будет очень много. Это и есть основной недостаток рекурсии – повторные вычисления одних и тех же значений. Кроме

127

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

Следует понимать, что при каждом вызове любой подпрограммы в особую область памяти, выделенной операционной системой программе и называемой стеком, записывается адрес инструкции, которая будет выполнена после завершения работы рекурсивной функции, а также параметры, передаваемые рекурсивной функции. Освобождение этой памяти происходит только при выходе из функции. Поэтому, если при вызове рекурсивной подпрограммы в стек заносится M байт, а размер стека N байт, то после K вызовов, примерно равном N/M – программа прекратит работу. Не смотря на то что, объемы оперативной памяти в настоящее время достаточно большие, существует вероятность переполнения стека при неправильных условиях завершения рекурсии, что грозит зависанием программы и операционной системы.

Чтобы избавиться от проблемы повторных вычислений, нужно запоминать найденные значения, например в массиве, и таким образом не вычислять их каждый раз заново. Для этого придётся активно использовать память, и осуществлять дополнительные проверки – вычислено или нет нужное число. Тогда дерево рекурсий сведется к более простому виду (рис.36):

Ф(6)

Ф(5)

Ф(4)

Ф(3)

Ф(2)

Ф(1)

Рис.36. Дерево рекурсий при оптимизации рекурсивного алгоритма

Например, для оптимизации рекурсивного алгоритма нахождения чисел Фибоначчи необходимо добавить следующие действия:

создать глобальный массив FCalculated первоначально состоящий из нулей, в котором будут храниться уже найденные числа;

128

после вычисления очередного числа Фибоначчи Ф(n) поместить его значение в FCalculated [n];

в начале рекурсивной процедуры сделать проверку на то, что FCalculated [n] = 0. Если FCalculated [n] <> 0, то вернутся из рекурсивной функции со значением FCalculated [n] в качестве результата, иначе приступить к рекурсивному вычислению Ф (n).

Такая рекурсия с запоминанием результатов промежуточных шагов иногда называется динамическим программированием сверху.

const int NFib = 300; //Максимальный размер массива с числами Фибоначчи

// описываем глобальный массив для запоминания результатов рекурсии int FCalculated[NFib];

int FibDinam (int n)

{

if (FCalculated[n] != 0) return(FCalculated[n]); else

{if ((n == 1) || (n == 2)) return(1); else

{ FCalculated[n]= FibDinam(n-1)+FibDinam(n-2); } return(FCalculated[n]);

}

}

int main()

{

int i,n;

// очищаем массив результатов рекурсии for(i=1;i<NFib;i++)

{} FCalculated[i] = 0;

FCalculated[1] = 1;

FCalculated[2] = 1;

printf("введите N:\n"); scanf("%d", &n);

}

printf("FibD[%d] = %d\n", n, FibDinam(n));

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

129

вычисления n-го числа Фибоначчи потребуется n шагов. Это можно проверить на практике. Например, если на одном и том же компьютере выполнить при N=85 три алгоритма нахождения чисел Фибоначчи:

а) алгоритм с рекурсией (Fib);

б) алгоритм с рекурсией с запоминанием результатов (FibDinam); с) алгоритм без рекурсии, с итерацией (FibCircle);

то получим следующие временные результаты вычислений (примерно): а) несколько минут; б) около секунды;

с) практически мгновенно, т.е. не более секунды.

Эти результаты демонстрируют особенности использования рекурсивных алгоритмов.

Примечание. При выполнении программ и вычислении времени их выполнения использовался компьютер: Intel® Core™ 2 Duo CPU 3 Ghz, 4ГБ ОЗУ.

Пример 32. Необходимо с помощью рекурсивной функции вычислить сумму элементов одномерного массива, который предварительно заполняется случайным образом с помощью стандартных функций генерации случайных чисел.

int Summa(int n, int A[100])

{

if (n = 0) return(0);

}

else return(A[n] + Summa(n-1,A));

int main()

{

int i,n; int a[100];

printf("Количество элементов в массиве?\n"); scanf("%i", &n); //N<100

for(i=1;i<n;i++)

{ a[i] = -10 + rand(); } printf("%d\n",a[i]);

printf("Сумма: %d\n", Summa(n,a));

}

130