Рекурсивные алгоритмыРефераты >> Программирование и компьютеры >> Рекурсивные алгоритмы
Требуется расположить диски в том же порядке на третьем колышке, причём диски разрешается перекладывать только по одному, а класть большой диск на меньший нельзя. Один колышек используется в качестве вспомогательного. Ответим на вопрос – сколько перемещений дисков следует выполнить?
Алгоритм решения головоломки следующий:
1. Переместить верхние n-1 дисков на второй колышек.
2. Нижний диск с первого колышка переместить на третий.
3. Переместить n-1 дисков на третий колышек, используя первый в качестве вспомогательного.
4. Повторять, пока на третьем колышке не будет сформирована новая пирамида.
Исходная задача сводится, таким образом, к двум задачам о переносе n-1 диска и одной задаче о переносе одного диска. Для n-1 требуется одно перемещение. Исходный текст программы для вычисления количества ходов приведён ниже (Листинг 1). Количество ходов здесь может быть вычислено как с применением рекурсии (функция hanoi1), так и без неё (функция hanoi2). В первом случае исходный текст функции короче.
1 2 3
Рис. 5. Головоломка “Ханойская башня”
Листинг 1. Программа “Ханойская башня”
{$S+}
program hanoi_moves;
function hanoi1(n: Word): LongInt;
begin
if n=1 then hanoi1:=1
else hanoi1:=2*hanoi1(n-1)+1;
end;
function hanoi2(n: Word): LongInt;
var j: LongInt; k: Word;
begin
if n=1 then hanoi2:=1
else
begin
j:=1;
for k:=2 to n do j:=2*j+1;
hanoi2:=j;
end;
end;
writeln(hanoi1(20));
writeln(hanoi2(20));
end.
1.5.2. Двумерное множество Кантора
Ещё один пример использования рекурсии приводится в программе Cantor (Листинг 2), которая строит двумерное множество Кантора. Множество строится следующим образом. Имеется квадрат, внутренняя часть которого закрашена каким-то цветом. Этот квадрат делится на 16 равных частей (тоже квадратных). Затем удаляются 4 средних квадрата, причём изображения их границ остаются. Далее процедура повторяется для каждого оставшегося квадрата. Алгоритм построения квадратного множества Кантора следующий:
1. Построить квадрат размером L.
2. Вырезать расположенный в центре квадрат размером L/2.
3. Разделить исходный квадрат на четыре равные части размером L/2.
4. Для каждого из четырёх квадратов повторить шаги 2 и 3.
В результате получается самоподобное множество – фрактал. В программе Cantor сохраняется изображение границы вырезанного квадрата, а построение множества идёт не от большего квадрата к меньшим, а наоборот.
Процедура SetWriteMode модуля Graph устанавливает режим вывода линии. Режим задаётся с помощью логической операции. В нашем примере используется операция “исключающее ИЛИ” (ей соответствует константа xorput), поэтому изображение линии комбинируется с изображением, уже выведенным на экран.
Листинг 2. Программа “Двумерное множество Кантора”
{$S+}
program Cantor;
uses Crt, Graph, graphs;
var ch: Char;
const min_size=1;
procedure draw(x,y: integer; size: Word);
var s: Word;
begin
if size>min_size then
begin
s:=size div 2;
draw(x-size, y+size, s);
draw(x-size, y-size, s);
draw(x+size, y+size, s);
draw(x+size, y-size, s);
end;
rectangle(x-size, y-size, x+size, y+size,);
bar(x-size+1, y-size+1, x+size-1, y+size-1);
end;
begin
open_graph;
SetFillStyle(solidfill, black);
SetColor(White);
draw(GetMaxX div 2, GetMaxY div 2, GetMaxY div 4);
OutTextXY(265, 235, ‘Press <Space>’);
ch:=ReadKey;
SetColor(Black);
SetWriteMode(xorput);
draw(getmaxx div 2, getmaxy div 2, getmaxy div 4);
SetColor(White);
OutTextXY(265, 235, ‘Press <Space>’);
ch:=ReadKey;
close_graph;
end.
1.5.3. “Индийский алгоритм” возведения в степень
Этот алгоритм вычисления натуральной n-й (n>1) степени целого числа х выглядит совсем просто:
§ при n=1 xn=x;
§ при n>1 xn= xn mod2(xn div2)2.
Основная цель этого алгоритма – сократить количество умножений при возведении в степень. Например, по этому алгоритму х5=х*(х2)2, т.е. достаточно трёх умножений вместо четырёх: х*х*х*х*х. Одно умножение экономится за счёт того, что х2 хранится как промежуточное значение и умножается само на себя. Точно так же х10=1*(х5)2=(х5)2, что требует лишь четырёх умножений (три из них для вычисления х5) вместо девяти “лобовых”. Но здесь придётся хранить сначала х2, а потом х5.
Очевидно, что вычисление хn сводится к вычислению хn div2, запоминанию результата, возведению в квадрат и умножению его на х при нечётном n. Итак, вычисление xn описывается рекурсивной функцией:
function pow(x,n:integer):integer;
var t:integer;
begin
if odd(n) then t:=x else t:=1;
if n=1 then pow:=x else pow:=t*sqr(pow(x,n div 2))
end;
Как видим, промежуточные сомножители хранятся в локальной памяти процессов выполнения вызовов функции, а именно, в переменных, поставленных в соответствие её имени.
Теперь опишем зависимость глубины рекурсии вызовов функции от значения аргумента. В каждом следующем вложенном вызове значение аргумента n меньше предыдущего значения, по крайней мере, вдвое. Поскольку при n=1 происходит возвращение из вызова, то таких уменьшений значения аргумента n не может быть больше чем log2n. Следовательно, глубина рекурсии вызова с аргументом n не превышает log2n.