Рекурсия
Такой процесс, когда в процедуре происходит обращение к самой себе, называется рекурсией
(рекурсия - возврат). (Происходит от латинского recurreus - возвращающийся).
Теперь нам предстоит более подробно поговорить о рекурсиях.
Рекурсия - это такой способ организации подпрограммы, при котором в ходе выполнения она обращается сама к себе.
Приведем примеры, которые уже стали классическими, использования рекурсий в подпрограммах:
Пример 10. Вычисление факториала числа.
Ниже приведена программа вычисления факториала числа, в которой используется рекурсивная процедура fac:
Procedure fac(n : integer; var f : real);
begin
if (n = 0) or (n = 1) then f := 1
else
begin
fac(n - 1, f);
f := f*n
end
end;
Разберемся детально в работе этой процедуры. Для этого снова обратимся к математике.
Для вычисления факториала числа n, т.е. n! надо умножить последовательно n натуральных чисел от 1 до n:
Так, 4! будет равно:
Это прямой путь вычисления или итеративный.
Возможен и другой путь вычисления:
Этот путь можно назвать возвратным или
рекурсивным.
Именно на этом принципе основана работа приведенной процедуры.
Пусть введено в программу значение 4 для вычисления факториала 4! Как будет работать процедура?
После начала ее работы, будет выполнена проверка:
if (n = 0) or (n = 1) then
f := 1
Понятно, что 4 не равно 0 и не равно 1, значит будет выполняться оператор после else, т. е. fac(n - 1, f), а это означает, что снова будет вызвана также процедура, но значение n уменьшится на единицу и станет равным 3; затем снова будет выполнена проверка условия:
if
(n = 0) or (n = 1) then f := 1 и переход к вызову процедуры fac(n - 1, f).
Значение n
уменьшится на 1 и станет равным 2 и т.
д. до тех пор, пока n не станет равным 1, а значение f получит значение 1 ( надо заметить, что при всех предыдущих операциях значение f оставалось равным 0, а точнее говоря, неопределенным).
После этого, начнется обратный процесс, в котором будет выполняться команда: f := f*n. Он будет происходить так:
f := 1*4; f := 4*3; f := 12*2; f := 24*1.
Образно говоря, при первоначальном процессе, значения n от 4 до 1 "запоминаются" в стековую память "Паскаль-машины", а при следующем процессе, значения n "считываются" из стековой памяти “Паскаль-машины”
и умножаются на значения f.
Условно-схематически это можно изобразить так: значения n запоминаются в стек-память "Паскаль-машины":
а затем начинают считываться в обратном порядке, начиная с единицы.
Обязательным элементом в описании всякого рекурсивного процесса является некоторое утверждение, определяющее условие завершения рекурсии; иногда его называют
опорным условием рекурсии (или "
якорем"). В нашем случае это условие:
if
(n = 0)
or (n = 1)
then f := 1.
В опорном условии может быть задано какое-то фиксированное значение, заведомо достигаемое в ходе рекурсивного вычисления и позволяющего организовать своевременную остановку процесса; применительно к вычислению факториала им будет равенство 1! = 1. Таким образом, любое рекурсивное определение всегда содержит два элемента: условие завершения и способ выражения одного шага решения посредством другого, более простого шага.
Для четкого понимания происходящих при этом процессов необходимо иметь в виду, что:
а) при каждом вызове процедуры создается новый экземпляр f;
б) все экземпляры f накапливаются во внутреннем стеке “Паскаль-машины” и
в) в любой момент обработке доступен только один, хронологически последний экземпляр переменной f, который
г) по завершению очередного рекурсивного вызова автоматически уничтожается).
Вывод
При каждом входе в рекурсивную подпрограмму ее локальные переменные размещаются в особым образом организованной области памяти, называемой
программным стеком.
Программа
Program Problem10;
uses WinCrt;
var
n : integer;
f : real;
{---------------------------------------------------------------------------------------}
Procedure fac(n : integer;
var f : real);
begin
if (n=0)
or (n=1)
then f := 1
else
begin
fac(n - 1, f);
f := f*n
end
end;
{---------------------------------------------------------------------------------------}
begin
write('Введите натуральное значение n '); readln(n);
fac(n, f);
writeln('Факториал числа ', n, ' равен ', f:12:0)
end.
Турбо-Паскаль 7 или 6 дает очень удобную возможность пошаговой трассировки программы и процедуру. Для этого достаточно последовательно нажимать клавишу
F7 и вы сможете полностью убедиться в правильности наших соображений.
Рекурсивная форма организации подпрограммы обычно выглядит изящнее итерационной (последовательной) и дает более компактный текст программы, но при выполнении, как правило, медленнее и может вызвать переполнение стека.
Как избавиться от этой неприятности вы узнаете позже. Но стоит знать, что при зацикливании программы следует выйти из нее нажатием <Ctrl>+<Z>+<Enter> (<
Ввод>) (для Турбо-Паскаля 7 или 6).
Еще примеры с использованием рекурсивных процедур.
Пример 11. Над цепью озер летела стая белых гусей. На каждом озере садилось половина гусей и еще полгуся, а остальные летели дальше. Все гуси сели на семи озерах. Сколько гусей было в стае?
Решение
Математически задача решается устно очень остроумным способом.
Пусть вместе со стаей белых гусей все время летит еще один, Серый гусь.
Если к некоторому озеру подлетит
m белых гусей и Серый, то на этом озере садится
- ровно половина всех гусей вместе с серым. Поэтому после каждого озера число летящих гусей уменьшается ровно вдвое. После семи озер оно уменьшится в 27 = 128 раз, а остается летящим один Серый гусь. Значит, вначале было 128 гусей, из них 127 - белых.
А теперь выполним, образно говоря, прямые рассуждения для решения задачи.
Обозначим через xk количество летящих белых гусей, когда впереди еще k озер. Тогда условие задачи записывается так:
Отсюда получаем для последовательности (xk) рекуррентное соотношение
Зная его, легко составить
рекурсивную процедуру:
Procedure goose(x, k : integer);
begin
if k = 1
then writeln(x)
else
goose(2*x + 1, k - 1)
end;
В процедуру вводятся всего две переменные:
x - искомое число гусей;
k - число озер. Процедура устроена с расчетом, что гуси уже пролетели все 7 озер, значит надо вводить значение для
x - один (1), а для
k - семь (7). В процедуре устроено, что число
k уменьшается на 1 и тогда опорным условием ("
якорем") завершения процедуры является условие равенства 1 значений
k и после этого на экран надо выдать значение числа гусей:
if k = 1
then writeln(x)
Опорное условие может быть и другим, если первоначальным значением
k будет 1, тогда при повторном обращении к процедуре значение
k надо не уменьшать, а увеличивать на 1 (
k + 1), опорным условием, в этом случае, будет
k = 7.
Ниже приводится законченная
программа решения этой задачи:
Program Problem11;
uses WinCrt;
var
k : integer;
{----------------------------------------------------------------------------------------}
Procedure goose(x, k : integer);
begin
if k = 1
then write(x)
else
goose(2*x + 1, k - 1)
end;
{----------------------------------------------------------------------------------------}
begin
write('Введите число озер '); readln(k);
write('В стае было ');
goose(1, k);
writeln(' гусей')
end.
Придерживаясь подобных соображений, решите следующую задачу.