
Глава 12
Рекурсия
Понятие рекурсии
Рекурсивным называется объект, частично состоящий или определяемый с помощью самого себя. Факториал — это классический пример рекурсивного объекта. Факториал числа п — это произведение целых чисел от 1 до п. Обозначается факториал числа п так: n!.
Согласно определению
п! = 1 х 2 х 3 х ... х (п - 1) х п. Приведенное выражение можно переписать так:
n! = nх ((n - 1) х (n - 2) х ...х 3 х 2 х 1) = n х (n - 1)!
То есть, факториал числа п равен произведению числа п на факториал числа (п - 1). В свою очередь, факториал числа (n-1) — это произведение числа (п - 1) на факториал числа (п - 2) и т. д.
Таким образом, если вычисление факториала п реализовать как функцию, то в теле этой функции будет инструкция вызова функции вычисления факториала числа (п - 1), т. е. функция будет вызывать сама себя. Такой способ вызова называется рекурсией, а функция, которая обращается сама к себе, называется рекурсивной функцией.
В листинге 12.1 приведена рекурсивная функция вычисления факториала.
Листинг 12.1. Рекурсивная функция вычисления факториала
function factorial(n: integer): integer;
begin
if n <> 1
then factorial:= n * factorial(n-1)
// функция вызывает сама себя
else factorial := 1; // рекурсивный процесс закончен
end;
Обратите внимание, что функция вызывает сама себя только в том случае, если значение полученного параметра k не равно единице. Если значение параметра равно единице, то функция сама себя не вызывает, а возвращает значение, и рекурсивный процесс завершается.
На рис. 12.1 приведен вид диалогового окна программы, которая для вычисления факториала числа использует рекурсивную функцию factorial. Текст программы приведен в листинге 12.2.
Рис. 12.1. Окно программы вычисления факториала
Листинг 12.2. Использование рекурсивной функции
unit factor ;
Interface
uses Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel; Label2: TLabel;
Edit1: TEdit;
Button1: TButton;
procedure ButtonlClick(Sender: TObject) ;
private { Private declarations }
public { Public declarations }
end;
var
Form1: TForm1;
Implementation
{$R *.DFM}
// рекурсивная функция
function factorial(n: integer): integer;
begin
if n > 1
then factorial:=n*factorial(n-1) //функция вызывает сама себя
else factorial:= 1; // факториал 1 равен 1
end;
procedure TForml.ButtonlClick(Sender: TObject);
var
k:integer; // число, факториал которого надо вычислить
f:integer; // значение факториала числа k
begin
k := StrToInt(Edit1.Text);
f := factorial(k);
label2.caption:='Факториал числа '+Edit1.Text
+ ' равен '+IntToStr(f);
end;
end.
На рис. 12.2 приведены два диалоговых окна. Результат вычисления факториала, представленный на рис. 12.2, а, соответствует ожидаемому.
Рис. 12.2. Примеры работы программы вычисления факториала
Результат, представленный на рис. 12.2, б, не соответствует ожидаемому. Факториал числа 44 равен нулю! Произошло это потому, что факториал числа 44 настолько велик, что превысил максимальное значение для переменной типа integer, и, как говорят программисты, произошло переполнение с потерей значения.
Delphi может включить в исполняемую программу инструкции контроля диапазона значений переменных. Чтобы инструкции контроля были добавлены в программу, нужно во вкладке Compiler диалогового окна Project Options (рис. 12.3) установить флажок Overflow checking (Контроль переполнения), который находится в группе Runtime errors (Ошибки времени выполнения).
Рис. 12.3. Вкладка Compiler диалогового окна Project Options
Примеры программ
Поиск файлов
В качестве примера использования рекурсии рассмотрим задачу поиска файлов. Пусть нужно получить список всех файлов, например, с расширением bmp, которые находятся в указанном пользователем каталоге и во всех подкаталогах этого каталога.
Словесно алгоритм обработки каталога может быть представлен так:
1. Вывести список всех файлов удовлетворяющих критерию запроса.
2. Если в каталоге есть подкаталоги, то обработать каждый из этих каталогов.
Приведенный алгоритм (его блок-схема представлена на рис. 12.4) является рекурсивным: для того чтобы обработать подкаталог, процедура обработки текущего каталога должна вызвать сама себя.
Рис. 12.4. Рекурсивный алгоритм поиска файлов
Вид диалогового окна программы приведен на рис. 12.5, текст — в листинге 12.3.
Поле Файл (Edit1) используется для ввода имени искомого файла или маски (для поиска файлов одного типа). Имя каталога, в котором нужно выполнить поиск, можно ввести непосредственно в поле Папка или выбрать из стандартного диалогового окна Обзор папок, которое появляется в результате щелчка на кнопке Папка. Окно Обзор папок (рис. 12.6) выводит на экран стандартная функция SelectDirectory. Следует обратить внимание, что имя каталога, который используется в диалоговом окне Обзор папок в качестве корневого, должно передаваться функции SelectDirectory как Строка WhideChar. Для Преобразования обычной строки в строку WideChar использована функция StringToWhideChar.
Рис. 12.5. Окно программы Поиск файлов
Рис. 12.6. Диалоговое окно Обзор папок появляется в результате щелчка на кнопке Папка
Основную работу выполняет рекурсивная функция Find. У функции Find один-единственный параметр — структура searchRec, которая используется функциями FindFirst и FindNext для поиска соответственно -первого и следующего файла, удовлетворяющего критерию поиска. Следует обратить внимание на то, как осуществляется перебор каталогов в текущем каталоге. Если текущий каталог не корневой, то помимо обычных, то есть имеющих имя, в каталоге есть еще два каталога: .. и ., которые обозначают каталог предыдущего уровня. Эти два каталога не обрабатываются, так как при входе в эти каталоги фактически выполняется выход (переход) в родительский каталог. Если этого не учесть, то программа зациклится.
Листинг 12.3. Программа поиск файлов
// поиск файла в указанном каталоге и его подкаталогах
// используется рекурсивная процедура Find
unit FindFile_;