В этой теме рассматривается применение рекурсии в графике.
Приводятся примеры построения кривых Гильберта и Серпинского.
Использование рекурсии в графике.

Рекурсия часто используется в графике. Рассмотрим некоторые примеры.
Задача. Составить рекурсивный алгоритм рисования окружностей следующего вида:

 
Центральная окружность радиуса R, на этой окружности симметрично располагаются 4 окружности радиуса R/2 каждая. На каждой из этих 4-х окружностях аналогичным образом строятся ещё 4 и т.д. Рисование прекращается, если радиус последних становится меньше некоторого числа k. Рисование окружностей будем производить относительно центральной окружности с центром (x,y). Центр первой окружности - (x+r,y), второй - (x,y+r), третьей - (x-r,y), четвертой - (x,y-r).  
uses crt,graph;
var
   gd,gm,mx,my:integer;
   ch         :char;
procedure krug(x,y,r:integer);
 begin
     if r>k  then  begin  krug(x+r,y,r div 2); krug(x,y+r,r div 2);
krug(x-r,y,r div 2);  krug(x,y-r,r div 2); end;
     circle(x,y,r);
 end;
Procedure Init; {инициализация графического режима}
var err: integer;
begin
  DetectGraph(gd,gm); InitGraph(gd,gm,' путь драйвера');
  if GraphResult<>grok then
    begin    Writeln(GraphErrorMsg(err));Readln;Halt(1);   end;
end;
BEGIN
  Init;
  krug(getmaxX div 2, getmaxY div 2, getmaxY div 4);
END.
 
Кривые Гильберта.

 
Этот узор состоит из суперпозиции пяти кривых. Три наложенные друг на друга кривые имеют форму Н1, Н2 и Н3.

 
 
H1  
H2  
 H3
Нi+1 получается соединением четырех экземпляров Нi вдвое меньшего размера, повернутых соответствующим образом и стянутых вместе тремя прямыми линиями. Н1 можно считать состоящей из четырех вхождений пустой Н0, соединенных этими же тремя линиями. Кривая Нi называется кривой Гильберта i-го порядка в честь её первооткрывателя Д. Гильберта.
Каждая кривая Каждая кривая Нi состоит из четырех вдвое меньших Нi-1, то процедура для рисования Нi будет включать четыре обращения для рисования Нi-1, соответствующим образом повернутых и уменьшенных вдвое. Обозначим эти четыре части через А, В, С и D.
 
 
 
 
Это кривые Гильберта 1-го порядка.

 Соединительные прямые будем обозначать стрелками, указывающими в соответствующем направлении. Будем предполагать, что направление задается целым параметром i как i·45 градусов.
Тогда получаем:  вправо при i=0, вниз при i=2, влево при i=4, вверх при i=6. Для построения А, В, С, D получается следующая схема рекурсий:

 
Кривые Гильберта 2-го порядка.

Для рисования линии будем использовать процедуру :
Line( i, s: integer), где i - направление, s - длина отрезка.

procedure Line( i, s: integer);
BEGIN
  x:=i*45/180*pi;
  setlinestyle(0,0,1);
  LineRel(Round(s*cos(x)),Round(s*sin(x)));
END;
Используя эту процедуру, с помощью рекурсивных обращений напишем процедуру, соответствующую схеме А:
 
 
Procedure A(i, s: integer);
BEGIN
   if i>0 then
     begin
       D(i-1,s);
       Line(4,s);
       A(i-1,s);
       Line(6,s);
       A(i-1,s);
       Line(0,s);
       B(i-1,s);
     end;
Аналогично составляются процедуры для В, С, D. Процедура А инициируется главной программой по одному разу для каждой из кривых Гильберта. Главная программа определяет начальную точку кривой, т.е. исходные координаты (x0,y0) и начальное значение длины отрезка u (желательно, чтобы u=2n).
Эта программа рисует кривые Гильберта 5-го порядка.
 
Uses Crt, Graph;
Var
  x: real;
  GrD, GrM    :integer;
  i,x0,y0,u  :integer;

procedure Line( i, s: integer);
BEGIN
  x:=i*45/180*pi;
  setlinestyle(0,0,1);
  LineRel(Round(s*cos(x)),Round(s*sin(x)));
END;
Procedure B(i, s: integer);forward;
Procedure C(i, s: integer);forward;
Procedure D(i, s: integer);forward;
Procedure A(i, s: integer);
BEGIN
   if i>0 then
     begin
       D(i-1,s);  Line(4,s);
       A(i-1,s);  Line(6,s);
       A(i-1,s);  Line(0,s);
       B(i-1,s);
     end;
END;
Procedure B;
BEGIN
   if i>0 then
     begin
       C(i-1,s);  Line(2,s);
       B(i-1,s);  Line(0,s);
       B(i-1,s);  Line(6,s);
       A(i-1,s);
     end;
END;
Procedure C;
BEGIN
   if i>0 then
     begin
       B(i-1,s);  Line(0,s);
       C(i-1,s);  Line(2,s);
       C(i-1,s);  Line(4,s);
       D(i-1,s);
     end;
END;
Procedure D;
BEGIN
   if i>0 then
     begin
       A(i-1,s);  Line(6,s);
       D(i-1,s);  Line(4,s);
       D(i-1,s);  Line(2,s);
       C(i-1,s);
     end;
END;
BEGIN  
   Grd := Detect;
   InitGraph(GrD,GrM, ' путь драйвера ');
   if GraphResult=GrOk
     then
     begin
       x0:=450;y0:=350;u:=128;i:=1;
       while i<=5 do
         begin
           MoveTo(x0,y0);
           A(i,u); i:=i+1;u:=u div 2;
           x0:=x0+(u div 2);y0:=y0+(u div 2);
           Readln;
         end;
       CloseGraph;
     end;
END.

Кривые Серпинского.
 
Аналогично, путем наложения друг на друга нескольких кривых, получается рисунок из кривых Серпинского. Первые две из них С1 и С2 показаны ниже:
 
С1
С2
                   Главное отличие кривой Серпинского от кривой Гильберта в том, что первая кривая замкнута. Значит, основная рекурсивная схема должна давать разомкнутую кривую, четыре части которой соединяются линиями, не принадлежащими самому рекурсивному образу. Четыре составляющих образа обозначим через A, B, C, D.
 
 
Соединительные прямые будем обозначать стрелками, указывающими в соответствующем направлении. Будем предполагать, что направление задается целым параметром i как i·45 градусов. Кроме направлений, описанных в предыдущем примере, понадобятся ещё : Вниз-вправо i=1, вниз-влево i=3, вверх-влево i=5, вверх-вправо i=7

Основной образ кривых Серпинского задается схемой:
  S:    A  Вниз-вправо   B вправо C вверх-влево D вверх-вправо
Рекурсивные составляющие по схемам:

A:  A вправо-вниз B вправо D вправо-вверх A
B:  B влево-вниз C вниз A вправо-вниз B
C:  C влево-вверх D влево B влево-вниз C
D:  D вправо-вверх A вверх C влево-вверх D
Заметим, что горизонтальные и вертикальные отрезки - двойной длины. Если использовать ту же процедуру рисования линии, что и в случае кривых Гильберта, то приведенные рекурсивные схемы записываются в рекурсивный алгоритм:
procedure A(i,s: integer);
BEGIN
   if i>0 then
     begin      A(i-1,s); Line(7,s);
                    B(i-1,s); Line(0,2*s);
                    D(i-1,s); Line(1,s);
                    A(i-1,s);
     end;
END;
Аналогично получаются процедуры для B, C, D. Главная программа строится по образу S. Её задача - установить начальные значения для координат рисунка и задать единичную длину линий.

Литература:

1.  В.А.Дагене, Г.К.Григас, К.Ф.Аугутис. 100 задач по программированию.
2.  Ж.Арсак. Программирование игр и головоломок.
3.  Н.Вирт. Алгоритмы и структуры данных.
4.  С.Барт. Россыпи головоломок.
5.  Математический энциклопедический словарь.
6.  М.Гарднер. Математические досуги.
 Упражнения:
 
1. Составить рекурсивный алгоритм рисования кривых Серпинского 4-го порядка.
2. Составить рекурсивный алгоритм рисования окружностей следующего вида:    
3. Составить рекурсивный алгоритм рисования дерева следующего вида:
 
  
1-го порядка
2-го порядка  
Перейти на первую страницу