Целевой узел
Рис. 11.6. Отношение depthfirst (
Path, Node, Solution)
Соответствующая программа поиска в глубину приведена в листинге 11,1,
Листинг 11,1.Программа поиска в глубину, которая позволяет предотвратить возникновение циклов
% solve[ Node, Solution);
% Решение solution представляет собой ациклический путь (узлы в котором * указаны в обратном порядке) между узлом Node и целью
solve { Mode, Solution) :-
depthfirst ( [], Mode, Solution).
Хрешение 'Solution o формируется "в результате продления пути (Node | Path] % до целевого узла
depthfirst( Path, Node, [Node i Path] ) :-goal; Node).
depthfirstl Path, Node, Sol} :-
s ; Node, Nodel),
not member( Nodel, Path), % Предотвращение цикла
... depthfirst.(_.[Node |....Path], ...Nodel, Sol).............................................................................................
Благодаря применению механизма обнаружения циклов рассматриваемая процедура поиска в глубину позволяет находить пути решения в пространствах состояний, подобных приведенному на рис. 11.5. Тем не менее есть такие пространства состояний, в которых эта программа вполне может потерпеть неудачу. В частности, многие пространства состояний являются бесконечными. В подобном пространстве состояний программа поиска в глубину может пропустить целевой узел, проходя по бесконечной ветви графа. Затем программа неопределенно долго будет исследовать эту бесконечную часть пространства, не приближаясь к цели. На первый взгляд может показаться, что причиной возникновения ловушки такого рода может стать пространство состояний задачи с восемью ферзями, которое определено в этом разделе. Но по стечению обстоятельств данное пространство является конечным, поскольку максимальное количество вариантов выбора координат Y клеток, на которых могут быть безопасно расставлены восемь ферзей, является ограниченным.
Для предотвращения бессмысленного прохождения по бесконечным (нециклическим) ветвям можно ввести еще одно усовершенствование в основную процедуру поиска в глубину: ограничить глубину поиска. В таком случае процедура поиска в глубину будет иметь следующие параметры: depthfirst2{ Node, Solution, Maxdepth)
Глава 11,Основные стратегии решения проблем
Поиск не должен проводиться в глубину, превышающую Maxdepth. Это ограничение можно запрограммировать путем уменьшения предельного значения глубины при каждом рекурсивном вызове и контроля над тем, чтобы этот предел оставался положительным. Результирующая программа приведена в листинге 11.2.
Листинг 11.2. Программа поиска в глубину с ограничением глубины поиска
% depthfirst2 [ Node, Solution, Maxdepth) :
% Решение Solution представляет собой путь от узла Node до целевого узла, % длима которого не превышает Maxdepth
dePthfirst2C Node, [Node], _) :-goal ( Node) .
depthfirst2( Node, [Node . Sol], Maxdepth) :-Maxdepth > 0, s( Node, Model],
Maxl is Maxdenth - 1, depthfirst2( Model, Sol, Maxl).
При использовании программы поиска в глубину (см. листинг 11.2) возникает определенное затруднение, связанное с тем, что подходящий предел необходимо устанавливать заранее. Если этот предел будет установлен слишком низким {т.е. меньшим по сравнению с длиной любого пути решения), поиск завершится неудачей. А если предел будет установлен слишком высоким, поиск потребует лишних затрат времени. Для преодоления этой сложности можно выполнять поиск в глубину итеративно, постепенно изменяя предел глубины, начиная с очень низкого предела глубины и постепенно увеличивая этот предел до тех пор, пока не будет найдено решение. Этот метод называется итеративным углублением. Его можно реализовать путем модификации программы, приведенной в листинге 11.2, следующим образом: предусмотреть вызов процедуры depthf irst2 из другой процедуры, которая при каждом рекурсивном вызове будет увеличивать предел на 1.
Тем не менее существует более изящная реализация, которая основана на использовании следующей процедуры; pathf Model, Mode?, Path)
где Path - ациклический путь в обратном порядке между узлами Nodel и Node2 в про
странстве состояний. Предположим, что этот путь представлен как список узлов в обрат
ном порядке. В таком случае процедуру path можно записать следующим образом:
path! Bode, Node, [Node]).% Путь с одним узлом
path; FirstNcde, LastNode, [LastHode I Path]) :-
path! FirstNode, OneButLMt, Path), % Путь, который включает все yaJM,
* кроме предпоследнего
К CneSutLast, LastNode), % Последний этап пути
not member; LastNode, Path). % Отсутствие цикла
Попытаемся найти некоторые пути, начинающиеся с узла а в пространстве состояний, приведенном на рис. 11.4, как показано ниже.
?- path( a, Last, Path).
Last = а
Path - [а];
Last - b
Path = [b,a] ,*
Last = с
Path = [c,a];
Last = d
Path = [d,b,aj;
Часть II, Применение языка Prolog в области искусственного интеллекта
Процедура path, в которой задан некоторый начальный узел, находит все возможные ациклические пути с постоянно возрастающей длиной. Именно это и требуется в подходе с использованием итеративного углубления: находить пути, имеющие все большую и большую длину, до тех пор, пока не будет найден путь, который оканчивается в целевом узле. Применение этой процедуры позволяет сразу же создать следующую программу поиска в глубину с итеративным углублением:
depth first iterative deepening С Node, solution) :-path( Mode, GoalKodi, Solution), goal GoalNode].
Такая процедура действительно является очень удобной с точки зрения практики, при условии, что комбинаторная сложность рассматриваемой задачи не требует использования эвристик, характерных для этой задачи. Данная процедура проста, и, даже притом что она не выполняет каких-либо достаточно "разумных" действий, ее использование не требует больших ресурсов времени или пространства. По сравнению с некоторыми другими стратегиями поиска, такими как поиск в ширину, основным преимуществом итеративного углубления является то, что для реализации этой стратегии необходим относительно небольшой объем памяти. На любом этапе выполнения программы требования к памяти, по сути, ограничиваются необходимостью хранить данные об одном пути, между начальным узлом поиска и текущим узлом. Пути формируются, проверяются и отбрасываются, в отличие от некоторых других процедур поиска (таких как поиск в ширину), при которых во время поиска одновременно хранится информация о многих возможных путях. Недостаток стратегии итеративного углубления является продолжением ее основного достоинства; при увеличении предела глубины во время каждой итерации ранее вычисленные пути необходимо перевычислять и продлевать до нового предела. Но при решении типичных задач поиска такое повторное вычисление не слишком сильно влияет на общую продолжительность вычислений. Как правило, основной объем вычислений выполняется на самых низких уровнях поиска, поэтому повторные вычисления на верхних уровнях занимают относительно небольшую часть в общем объеме времени.