Задачник-практикум по основам программирования. Учебное пособие по курсу «Информатика»
.pdfПри выполнении тела процедуры сформируются следующие два оператора:
SumRec( 4, Sm ); Sm:=Sm+1/5;
Новый оператор процедуры SumRec(4, Sm)заставляет снова обра- титься к выполнению этой же процедуры, что приводит к появлению новой пары операторов:
SumRec( 3, Sm ); Sm:=Sm+1/4; Sm:=Sm+1/5;
После выполнения еще двух обращений ситуация окажется следующей:
SumRec( 1, Sm ); Sm:=Sm+1/2; Sm:=Sm+1/3; Sm:=Sm+1/4; Sm:=Sm+1/5;
ЗатемприочередномвыполненииоператорапроцедурыSumRec(1, Sm) выполнится оператор присваивания Sm:=1 и рекурсивные обращения прекратятся. В результате сформируется следующая последовательность операторов:
Sm:=1;
Sm:=Sm+1/2;
Sm:=Sm+1/3;
Sm:=Sm+1/4;
Sm:=Sm+1/5.
Эта последовательность операторов и дает результат вычисления суммы: сумма = 2.28333333
Рекурсивную процедуру SumRec следует рассматривать как иллюс- трацию аппарата рекурсии. Итеративная (нерекурсивная) процедура для нахождения суммы гораздо проще, эффективнее и естественнее:
procedure Sum( n: integer; var S: real); var i: integer;
begin S=1;
for i:=2 to n do S:=S+1/n
end;
141
Пример 8.2. Описать рекурсивную функцию для возведения числа в целую положительную степень:
1, |
|
если n = 0 |
xn = |
xn−1, |
если n > 0 |
x |
Используя эту функцию, вычислить 23. program Rec_2_1;
function Pow( x:real; n:integer): real; begin
if n=0 then Pow:=1
else Pow:=x*Pow(x,n-1)
end; begin
writeln(‘Pow(2,3)=’,Pow(2,3))
end.
Результат работы программы Rec_2_1: Pow (2, 3) = 8
Можно исследовать, как выполнятся рекурсивная функция: сколько прямых и обратных шагов выполняется и при каких значениях параметров. При изменении функции в список параметров процедуры Pow добавляется параметр i – номер вызова функции. Значения параметров будут выводиться в начале очередного выполнения тела функции (прямой ход) и перед вы- ходом из него (обратный ход).
Программа с измененной функцией: program Rec_2_2;
var i: integer;
function Pow1(var i: integer; x: real; n: integer):
real; |
|
|
begin |
|
|
i:=i+1; |
i=’,i,’ |
x=’,x,’ |
writeln(‘прямой ход’,’ |
||
n=’,n); |
|
|
if n=0 then begin
writeln(‘ |
--------------------------------’); |
|
writeln(‘завершение |
рекурсивных вызовов’, |
|
‘ |
i=’,i,’ |
n=’,n); |
142
writeln(‘ --------------------------------’); Pow1:=1
end else
Pow1:=x*Pow1(i,x,n-1);
writeln(‘обратный ход’,’ i=’,i,’ n=’,n); i:=i-1 end;
begin
i:=0; writeln(‘Pow1(i,2,3)=’,Pow1(i,2,3)) end.
Результат работы программы Rec_2_2:
прямой ход |
i = 1 |
x = 2 |
n = 3 |
прямой ход |
i = 2 |
x = 2 |
n = 2 |
прямой ход |
i = 3 |
x = 2 |
n = 1 |
прямой ход |
i = 4 |
x = 2 |
n = 0 |
–––––––––––––––––––––––––––––––––––––––––––
завершение рекурсивных вызовов i = 4 n = 0
–––––––––––––––––––––––––––––––––––––––––––
обратный ход |
i = 4 |
n = 0 |
обратный ход |
i = 3 |
n = 1 |
обратный ход |
i = 2 |
n = 2 |
обратный ход |
i = 1 |
n = 3 |
Pow1(i, 2, 3) = 8
Упражнения
1)Проверить, как будет вести себя программа, если использовать вызов функции Pow1(0,2,3).
2)Какое исправление нужно внести в описание функции, чтобы вызов Pow1(0,2,3) не давал сообщений об ошибке?
3)Поставить в функции Pow1 строку выдачи
writeln(‘прямой ход’,’ i=’,i,’ x=’,x,’ n=’,n);
непосредственно перед рекурсивным вызовом и проанализировать изменения в выводе.
4)Поставить в функции Pow1 строку выдачи
writeln(‘прямой ход’,’ i=’,i,’ x=’,x,’ n=’,n);
после рекурсивного вызова и проанализировать изменения в выводе. 5) Поставить в функции Pow1 строку выдачи
143
writeln(‘обратный ход’,’ i=’,i,’ n=’,n); i:=i-1
непосредственно перед рекурсивным вызовом и проанализировать изменения в выводе.
6)Описать рекурсивную функцию Pow(x,n) от вещественного x (x ≠ 0) и целого n, которая вычисляет величину xn по формуле
1
xn = 1/ x n
x xn−1
при |
n = 0 |
при |
n < 0 |
при |
n > 0 |
Пример 8.3. Для решения задачи, рассмотренной в Примере 8.2., можно использовать более эффективное рекурсивное определение xn, ох- ватывающее случай n < 0:
|
|
1, |
|
|
|
|
|
|
|
если |
n = 0, |
|
|
|
n |
|
|
n / 2 |
) |
2 |
, |
если |
n > 0, |
n |
−четное, |
||
x |
(x |
|
|
|
|
||||||||
|
= |
xn−1, |
если |
n > 0, |
n |
−нечетное, |
|||||||
|
|
x |
|||||||||||
|
|
|
x |
n |
, |
|
|
если |
n < 0. |
|
|
||
|
|
1 |
|
|
|
|
|
||||||
function Power(x: real; n: integer): real; begin
if n=0 then Result:=1 else
if n<0 then Result:=Power(x,-n) else
if odd(n) then Result:=x*Power(x,n-1) else Result:=sqr(Power(x,n div 2))
end;
Недостаток этой функции состоит в том, что переменная x не меняется, но передается в каждый рекурсивный вызов.
Чтобы устранить бесполезную передачу неизменяемого параметра в рекурсивный вызов, достаточно сделать переменную x глобальной по отношению к рекурсивной функции.
Для этого следует вложить в нерекурсивную функцию Power рекур- сивную функцию Power0(n):
function Power(x: real; n: integer): real; function Power0(n: integer): real;
begin
144
if n=0 then Result:=1 else if n<0 then
Result:=Power(x,-n) else if odd(n) then
Result:=x*Power0(n-1) else
Result:=sqr(Power0(n div 2)) end;
begin Result:=Power0(n)
end;
Пример 8.4. Описать и использовать функцию вычисления числа Фибоначчи с номером n.
Как известно (см. Пример 3.4.), определение чисел Фибоначчи ре- курсивно [17]:
fn |
1, n = 0, 1 |
|
= |
+ fn−2 , n > 1. |
|
|
fn−1 |
|
program Rec_4;
var n, f: integer;
function Fib(n: integer): integer; begin
if (n=0) or (n=1) then Fib:=1 else Fib:= Fib(n-1)+ Fib(n-2)
end; begin
write(‘Введите целое число ‘); readln(n);
f:= Fib(n);
writeln(n, ‘-е число Фибоначчи равно ‘, f) end.
Однако такое решение крайне неэффективно, поскольку содержит большое количество повторяющихся вычислений.
Например, дерево рекурсивных вызовов для вызова Fib (7) будет иметь следующий вид:
145
Из рисунка видно, что, Fib(5) вычисляется дважды, Fib(4) – триж- ды, Fib(3) – 5 раз и т. д., т. е. количество повторных вызовов представляет собой также последовательность чисел Фибоначчи!
Пример 8.5. Выдать цифры заданного натурального числа в порядке их следования (слева направо).
program Rec_5; var n: integer;
procedure Digit_rec(n: integer); begin
if n <> 0 then begin
Digit_rec(n div 10); write(n mod 10,’ ‘)
end end; begin
writeln(‘Введите натуральное число’); readln(n);
Digit_rec(n); writeln
end.
Упражнения
1)Изменить процедуру Digit_rec для выдачи цифр заданного на- турального числа в обратном порядке (справа налево).
2)Выдать все нечетные цифры заданного натурального числа в по- рядке их следования (слева направо).
146
Пример 8.6. Вводится с клавиатуры последовательность ненулевых целых чисел, за которыми следует 0. Используя рекурсивную процедуру без параметров, выдать сначала все отрицательные числа, затем все поло- жительные.
program Rec_6; procedure Print; var x: integer; begin
read(x);
if x<>0 then begin
if x<0 then write(x,’ ‘); Print;
if x>0 then write(x,’ ‘) end
end; begin
writeln(‘Введите последовательность ненулевых ‘, ‘целых чисел, за которыми следует 0’);
Print; writeln
end.
Пример 8.7. Последовательность положительных вещественных чи- сел, за которыми следует 0, вводится с клавиатуры. Используя рекурсивную функцию без параметров, найти их сумму.
program Rec_7; function Sum: real; var x: real;
begin read(x);
if x=0 then Sum:=0 else Sum:=x+Sum
end; begin
writeln(‘Введите последовательность положительных ‘, ‘чисел, за которыми следует 0’);
147
writeln(Sum);
end.
Пример 8.8. Вводится с клавиатуры без ошибок формула следующего вида:
<формула> ::= <цифра> | (< формула > <знак> < формула >) <знак> ::= + | – | * <цифра> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Используя рекурсивную функцию без параметров, вычислить значение вводимой формулы.
program Rec_8;
function Formula: integer;
var c, op: char; { op – знак операции } x, y: integer;
begin read(c);
if (c>=’0’) and (c<=’9’) then {цифра - формула} Formula:=ord(c)-ord(‘0’)
else { начало формулы вида (x op y) } begin
x:=Formula;
read(op);
y:=Formula; case op of
‘+’: Formula:=x+y; ‘-’: Formula:=x-y; ‘*’: Formula:=x*y;
end;
read(c) {пропуск скобки ‘)’} end
end; begin
writeln(‘Введите формулу’); writeln(‘Значение = ‘,Formula)
end.
148
Контрольные вопросы |
|
|
1) |
5 |
Значение = 5 |
2) |
(( 2 – 4 ) * 6 ) |
Значение = –12 |
3) |
((( 2 + 4 )*3) – 5 ) |
Значение = 13 |
Пример 8.9. Вычислить квадратный корень y = x из заданного положительного числа x.
Для вычисления приближенного значения y можно использовать рекуррентную формулу [17]:
yn |
= yn−1 + |
1 |
( |
x |
− yn−1 ), n = 1, 2, ... |
2 |
|
||||
|
|
|
yn−1 |
||
y0 – произвольное положительное число (например, 1, x, x / 2 ). Вычисления можно прекратить при выполнении условия
|yn – yn- 1|< eps/2.
Рекуррентную формулу и условие окончания процесса вычислений можно преобразовать:
yn = |
1 |
( |
x |
+ yn−1 ), |
||||
2 |
|
|||||||
|
|
|
yn−1 |
|||||
|
x |
|
− yn−1 |
|
< eps . |
|||
|
|
|
||||||
|
|
|
||||||
|
yn−1 |
|
|
|
|
|
||
program Rec_9;
var x, y, eps: real;
function Sq(x,y,eps: real):real; begin
if abs(x/y-y)<eps then Sq:=y
else
Sq:=Sq(x,(x/y+y)/2, eps)
end; begin
writeln(‘Введите x и eps’); readln(x,eps); y:=Sq(x,x/2,eps);
writeln(‘Квадратный корень=’,y:8:4) end.
149
Пример 8.10. Описать рекурсивную функцию, которая n раз вызывает заданную функцию f (x):
fn ( n, x )=f ( f (…f (x) ) )
Для сравнения описать нерекурсивную функцию, n раз вызывающую заданную функцию f(x).
Используя рекурсивную и нерекурсивную функции, вычислить зна- чение ((x2)2)2.
program Rec_10;
type func = function (x:real):real; var x:real;
function f(x:real):real; begin
f:=x*x
end;
function Fn(n:integer;x:real; f:func):real; begin
if n>1 then Fn:=f(Fn(n-1,x,f))
else Fn:=f(x) end;
function Fn1(n:integer;x:real; f:func):real; var i:integer;
begin
for i:=1 to n do x:=f(x);
Fn1:=x
end; begin
write(‘Введите x ‘); readln(x);
writeln(‘ Обращение к рекурсивной функции:’); writeln(Fn(3,x,f));
writeln(‘ Обращение к нерекурсивной функции:’); writeln(Fn1(3,x,f))
end.
150
