Паскаль. Основы программирования

       

Рекурсия


Такой процесс, когда в процедуре происходит обращение к самой себе, называется рекурсией

(рекурсия - возврат). (Происходит от латинского 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 запоминаются в стек-память "Паскаль-машины":

4

3

4

 
2

3

4

 
1

2

3

4

а затем начинают считываться в обратном порядке, начиная с единицы.

Обязательным элементом в описании всякого рекурсивного процесса является некоторое утверждение, определяющее условие завершения рекурсии; иногда его называют опорным условием рекурсии (или "якорем"). В нашем случае это условие:

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.

Придерживаясь подобных соображений, решите следующую задачу.


Содержание раздела