1.5 Правила выбора программной реализации рекуррентных соотношений

Подводя итог, можно сформулировать рекомендации по применении рекурсии при программировании решения задач, описываемых рекуррентными соотношениями.

  1. Записываем алгоритм решения некоторой задачи в виде рекуррентного соотношения, реализующего разбиение исходной задачи на подзадачи.

  1. Объединяем одинаковые слагаемые в рекуррентном уравнении.

  1. Если рекурсивная программа, реализующая рекуррентное соотношение, содержит один вызов рекурсивной функции, то рекурсивная реализация будет не эффективна.

  1. Если рекурсивная программа, реализующая рекуррентное соотношение, содержит более одного вызова рекурсивной функции, то поступаем следующим образом:

    1. если возникающие подзадачи на каждом шаге независимы, то каждая из них будет решаться только один раз и поэтому рекурсивная реализация оправдана, т. к. не приводит к повторному решению одной и той же подзадачи;

    2. если возникающие подзадачи зависимы (например как в нахождении чисел Фибоначчи), то все зависит от того, помещаются ли все их решения в память:

  • - если помещаются, то табличная реализация позволяет решить каждую подзадачу только один раз, запоминая ее решение в таблице; при повторном возникновении некоторой подзадачи она уже не решается повторно, а ее решение берется из таблицы; в этой ситуации рекурсивная реализация приводит к многократному повторному решению некоторых подзадач;

  • - если же решения зависимых подзадач не помещаются в память, то единственный выход при программной реализации – рекурсия, конечно при условии, что она позволяет экономить память и сама может быть реализована с учетом доступной памяти (в этом случае важную роль играет глубина рекурсии).

Тема 2. Задачи перебора вариантов

2.1. Модель дерева решений

Решение многих прикладных задач сводится к задаче перебора большого числа возможных вариантов с целью выбора наиболее приемлемого из них по заданному критерию. В этом случае для наглядной интерпретации перебора вариантов удобно использовать модель в виде дерева решений.

Каждый узел в дереве можно интерпретировать как результат выполнения некоторой последовательности шагов решения задачи перебора вариантов. Ветвь в дереве при этом соответствует принятию очередного решения (шагу), которое ведет к более полному решению. В игровых деревьях, например, каждая ветвь соответствует ходу игрока. Листы (узлы у которых нет выходящих ветвей) представляют собой окончательные варианты решений.

Цель состоит в том, чтобы из всех возможных вариантов решений найти наилучший по заданному критерию путь от корня до листа при выполнении некоторых условий-ограничений. Естественно, что форма дерева, критерий оптимальности и условия-ограничения определяются конкретной задачей.

Деревья решений обычно огромны. Все дерево для игры в крестики-нолики содержит более 500 тыс. вариантов. Большинство же реальных задач несравненно сложнее. Соответствующие им деревья решений могут содержать больше листов, чем атомов составляющих нашу планету. Поэтому задача выбора необходимого варианта довольно трудоемка. Имеется много методов работы с такими деревьями. Для небольших деревьев (типа крестики-нолики) можно использовать метод полного перебора всех возможных решений.

Рассмотрим программную реализацию обхода всех узлов произвольного дерева решений. Для этого предположим, что на каждом i-том ходу возможен выбор из ai1..aiki вариантов (кандидатов) хода. Т.е. задана матрица возможных кандидатов на каждом ходу, например такая:

Путь от начала до некоторого листа будет соответствовать последовательности ходов {a1j1anjn}, aiji выбранный вариант i-го хода (1jiki), например .

Рекурсивная процедура полного перебора листьев соответствующего такой задаче дерева выглядит следующим образом:

Листинг 2.1

Var S:array[1..n]of тип <кандидатов a11..ankn>;

n,j:word;

Procedure Vbr(i:word);

Begin j:=0;

Repeat j:=j+1;

//выбор и запоминание j-го кандидата на i-том ходу

S[i]:= a[i,j];

//рекурсивный переход к следующему уровню

if i<n then Vbr(i+1)

else {лист решения сформирован в S[1..n], здесь возможна

проверка приемлемости и оптимальности,

печать или отображение варианта };

until j=ki ;// список кандидатов на i – м ходу исчерпан;

end;//Vbr

Обращение к процедуре:

Read(n); For i:= to n do S[i]:=0; Vbr(1);

Здесь каждый внутренний узел дерева имеет набор ki выходящих из него ветвей (кандидатов) по которым возможно продвижение к полному решению. Величина ki на каждом i-м ходу может быть разная, nглубина поиска. Например, для игры в крестики-нолики n не превосходит восьми при этом на первом ходу (i=1) предлагается 8 кандидатов (возможных вариантов ходов), в последующем количество кандидатов на каждом ходу уменьшается на единицу. Заметим, что при реализации полного перебора этой процедурой вариантам решений будут соответствовать на рис.1 полностью заполненные таблицы (см.лист 1). Для организации завершения формирования решения при нахождении варианта, соответствующего ситуации «конец игры» необходимо использовать отсечение последующего перебора используя условия-ограничения. Попробуйте составить программу, реализующую эту игру.