Сложная рекурсия
Косвенная рекурсия возникает в том случае, когда некоторая функция обращается к другой функции, а та в свою очередь вызывает первоначальную функцию.
Если обычную рекурсию можно уподобить уроборосу (рис. 3), то образ сложной рекурсии можно почерпнуть из известного детского стихотворения, где «Волки с перепуга, скушали друг друга». Представьте себе двух съевших друг друга волков, и вы поймете сложную рекурсию.
Рис. 3. Уроборос – змей, пожирающий свой хвост. Рисунок из алхимического трактата «Synosius» Теодора Пелеканоса (1478г).
Рис. 4. Сложная рекурсия.
Итак, возможна чуть более сложная схема: функция A вызывает функцию B, а та в свою очередь вызывает A. Это называется сложной рекурсией.
При этом оказывается, что описываемая первой процедура должна вызывать еще не описанную. Чтобы это было возможно, требуется использовать опережающее описание.
Для предотвращения зацикливания одна или обе функции должны проверять условие завершения. При косвенной рекурсии возникают ссылки вперед. Один из рекурсивных модулей, вызывающих друг друга, описывается предварительно добавлением к заголовку функции ключевого слова FORWARD. Предварительное указание называется директивой.
По правилам синтаксиса языка Паскаль каждая вызываемая подпрограмма должна быть описана до ее вызова. Но если подпрограмма А вызывает подпрограмму В, а В вызывает А, то получается замкнутый круг. Для подобных ситуаций принято следующее правило: одна из рекурсивных, вызывающих друг друга подпрограмм, описывается предварительно следующим образом:
Пример: Опережающее описание процедуры B позволяет вызывать ее из процедуры A.
Здесь forward - директива компилятору, указывающая, что текст процедуры В помещен ниже
procedure B(n: integer); forward; {Опережающее описание второй процедуры}
procedure A(n: integer); {Полное описание процедуры A}
begin
writeln(n);
B(n-1);
end;
procedure B(n: integer); {Полное описание процедуры B}
begin
writeln(n);
if n<10 then
A(n+2);
end;
Результат работы:
Если в основной программе вызвать А(1), или А(2) или …. А(9), то будут выведены числа
вида 9 8 10 9 11 10 (для А(9)) вида 7 6 8 7 9 8 10 9 11 10 (для А(7)) и т.п.
Если в основной программе вызвать А(10), то будут выведены числа 10 9 11 10
Если в основной программе вызвать А(11), то будут выведены числа 11 10
Если в основной программе вызвать А(56), то будут выведены числа 56 55
Если в основной программе вызвать А(888), то будут выведены числа 888 887
Итерация
Рекурсию следует использовать только в тех случаях, когда решение задачи на каждом шаге разбивается на несколько подобных подзадач более низкого ранга.
Итерация - способ организации обработки данных, при котором определенные действия повторяются многократно, не приводя при этом к рекурсивным вызовам программ.
Когда какое-то действие необходимо повторить большое количество раз, в программировании используются циклы.
В основе итеративного вычислительного процесса лежит итеративный цикл While, Repeat-Until, For. Наиболее общим является цикл While:
While < условие цикла > do < тело цикла >;
Пример: Перевод числа в двоичную систему.
Получение цифр двоичного числа, как
известно, происходит с помощью деления
с остатком на основание системы счисления
2. Если есть число
,
то его последняя цифра в его двоичном
представлении равна
..
Взяв же целую часть от деления на 2:
,
получим число, имеющее то же двоичное
представление, но без последней цифры.
Таким образом, достаточно повторять
приведенные две операции пока поле
очередного деления не получим целую
часть равную 0.
Без рекурсии это будет выглядеть так:
while x>0 do
begin
c:=x mod 2;
x:=x div 2;
write(c);
end;
Проблема здесь в том, что цифры двоичного представления вычисляются в обратном порядке (сначала последние). Чтобы напечатать число в нормальном виде придется запомнить все цифры в элементах массива и выводить в отдельном цикле.
С помощью рекурсии нетрудно добиться вывода в правильном порядке без массива и второго цикла. А именно:
procedure BinaryRepresentation(x: integer);
var
c, x: integer;
begin
{Первый блок. Выполняется в порядке вызова процедур}
c := x mod 2;
x := x div 2;
{Рекурсивный вызов}
if x>0 then
BinaryRepresentation(x);
{Второй блок. Выполняется в обратном порядке}
write(c);
end;
Вообще говоря, никакого выигрыша мы не получили. Цифры двоичного представления хранятся в локальных переменных, которые свои для каждого работающего экземпляра рекурсивной процедуры. То есть, память сэкономить не удалось. Даже наоборот, тратим лишнюю память на хранение многих локальных переменных x. Тем не менее, такое решение кажется программистам более красивым.
Эта программа целиком (проверенная)
program BinaryRepresentations;
uses crt;
var
c,x,a : integer;
procedure BinaryRepresentation(x: integer);
var
c: integer;
begin
{Первый блок. Выполняется в порядке вызова процедур}
c := x mod 2;
x := x div 2;
{Рекурсивный вызов}
if x>0 then
BinaryRepresentation(x);
{Второй блок. Выполняется в обратном порядке}
write(c);
end;
begin
write('Ведите натуральное число в 10-сс: ');
readln(x);
writeln('Переведем ', x,' в 2-сс: ');
a:=x; {сохраняем исходное число в дополнительной переменной а}
write('1 способ - с помощью итерации (цифры в обратном порядке): ');
{цифры двоичного представления вычисляются в обратном порядке (сначала последние)}
while x>0 do
begin
c:=x mod 2;
x:=x div 2;
write(c);
end;
writeln;
write('2 способ - с помощью рекурсии: ');
BinaryRepresentation(a);
readkey;
end.
