
3. Два примера рекурсивных программ
Симпатичный узор на рис. 3.3 состоит из суперпозиции пяти кривых. Эти кривые соответствуют некоторому регулярному образу и считается, что их можно нарисовать на экране дисплея или на графопостроителе под управлением какой-либо вычислительной машины. Наша задача – найти рекурсивную схему, по которой можно было бы создать программу для такого рисования. Рассматривая рис. 3.3, мы обнаруживаем, что три наложенные друг на друга кривые имеют форму, показанную на рис. 3.4; обозначим их через H1, H2 и H3.. Видно, что Hi+1 получается соединением четырех экземпляров Hi вдвое меньшего размера, повернутых соответствующим образом и «стянутых» вместе тремя прямыми линиями. Заметим, что Н1 можно считать состоящей из четырех вхождений пустой H0, соединенных этими же тремя линиями. Кривая Hi называется кривой Гильберта i-го порядка в честь ее первооткрывателя Д. Гильберта (1891 г.).
П
П
P
Рис.
3.5. Рамка для кривых
BEGIN
IF i > 0 THEN
D(i-1); line{4,u);
A(i-l); line(6,u);
A(i-1); line(0,u);
B(i-1)
END
END A;
Эта процедура инициируется главной программой по одному разу для каждой из кривых Гильберта, образующих приведенный на рис. 3.4 узор. Главная программа определяет начальную точ» кривой, т. е. исходные координаты пера (Рх и Ру) и единичное приращение u. Заданного размера квадрат, где рисуется кривая, помещается в середине страницы (см. рис. 3.5). Величина h0 должна удовлетворять равенству h0 = 2k для некоторого k n. Эти параметры, так же как и процедура рисования прямой, находятся в модуле с названием LineDrawing. Программа рисует n кривых Гильберта – H1,. H2, , Hn (см. листинг 3.1 и рис. 3.4).
Кривые Гильберта
MODULE Hilbert;
FROM Terminal IMPORT Read;
FROM LineDnwing IMPORT width, height, Px, Py, clear, line;
CONST SquareSize = 512;
UNSIGNED i, x0, y0, u;
CHAR ch;
PROCEDURE A(i)
BEGIN
IF i>0 THEN
D(i-1); line(4,u); A(i-1); line(6,u);
A(i-1); line(0,u); B(i-1);
END A;
PROCEDURE B(i)
BEGIN
IF i>0 THEN
C(i-1); line(2,u); B(i-1); line(0,u);
B(i-1); line(6,u); A(i-l)
END B;
PROCEDURE C(i)
BEGIN
IF i>0 THEN
B(i-1); line(0,u); C(i-1): line(2,u);
C(i-l); line(4,u); D(i-1)
END C;
PROCEDURE D(i)
BEGIN
IF i>0 THEN
A(i-1); line(6,u); D(i-1); line(4,u);
D(i-1); line(2,u); C(i-l)
END D;
BEGIN clear;
x0 = width >> 1; y0 = height >> 1;
u=SquareSize; i=0;
DO
{
i++;
u>>1;
x0+=(u>>1); y0+=(u>>1);
Px=x0; Py=y0: A(i); Read(ch)
UNTIL (ch = 33C)||(i=6);
END Hilbert
П
Основной образ кривых Серпинского задается схемой
а рекурсивные составляющие (горизонтальные и вертикальные отрезки – удвоенной длины) – схемой
Если использовать те же примитивы рисования, что и в случае кривых Гильберта, то приведенные рекурсивные схемы без труда трансформируются в (прямо или косвенно) рекурсивный алгоритм.
P
BEGIN
IF k>0 THEN
A(k-1); line(7,h);
B(k-1); line(0,2*h);
D(k-1); lined,h); A(k-1)
END A;
Эта процедура порождается из первой строки рекурсивной схемы (3.22). Аналогично получаются и процедуры, соответствующие образам В, С и D. Главная программа строится по образу (3.21). Ее задача – установить начальные значения для координат рисунка и задать единичную длину линий h; это все зависит от формата бумаги (листинг 3.2). На рис. 3.7 приведен результат работы такой программы с n = 4.
Ясно, что в этих примерах рекурсия используется элегантным способом. Правильность программ легко подтверждается их структурой и формируемыми образами. Кроме того, употребление в соответствии со схемой (3.5) явного параметра для уровня гарантирует окончание работы, так как глубина рекурсии не может быть больше n. По сравнению с таким рекурсивным построением эквивалентные программы, где избегали употребления рекурсии, выглядят крайне сложными и запутанными.
Листинг 3.2. Кривые Серпинского
MODULE Sierpinski;
FROM Terminal IMPORT Read:
FROM lineDrawing IMPORT width, height, Px, Py, clear, line;
CONST SquareSize = 512;
UNSIGNED i, h, x0, y0;
CHAR ch;
PROCEDURE A(k);
BEGIN
IF k>0 THEN
A(k-1); line(7, h); B(k-1), line(0, 2*h);
D(k-1); lined, h); A(k-1)
END A;
PROCEDURE B(k);
BEGIN
IF k>0 THEN
B(k-1); line(5, h); C(k-1); line(6, 2*h);
A(k-1); line(7, h); B(k-1)
END B;
PROCEDURE C(k);
BEGIN
IF k>0 THEN
C(k-1); line(3, h): D(k-1); line(4, 2*h);
B(k-1); line(5. h); C(k-1)
END C;
PROCEDURE D(k);
BEGIN
IF k>0 THEN
D(k-1); lined, h); A(k-1); line(2, 2*h);
C(k-1); line(3, h); D(k-1)
END D;
BEGIN
clear;
i = 0; h = SquareSize>>2;
x0 = width >> 1; y0 = height >> 1 + h;
DO i++: x0-=h;
h>>1; yO+=h; Px = x0: Py = y0;
A(i): line(7, h); B(i); line(5, h):
C(i): line(3, h); D(i); line(1, h); Read(ch)
UNTIL (i = 6) || (ch = 33C);
clear
END Sierpinski.