Базовые процедуры поиска в И / ИЛИграфах
13. 3. Базовые процедуры поиска в И / ИЛИ-графах
В этом разделе нас будет интересовать какое-нибудь решение задачи независимо от его стоимости, поэтому проигнорируем пока стоимости связей или вершин И / ИЛИ-графа. Простейший способ организовать поиск в И / ИЛИ-графах средствами Пролога - это использовать переборный механизм, заложенный в самой пролог-системе. Оказывается, что это очень просто сделать, потому что процедурный смысл Пролога это и есть не что иное, как поиск в И / ИЛИ-графе. Например, И / ИЛИ-граф Рисунок 13.4 (без учета стоимостей дуг) можно описать при помощи следующих предложений:
        а :- b.
                    % а  -  ИЛИ-вершина с двумя преемниками
        а :- с.
                    % b  и  с
        b :- d, е.
                % b - И-вершина с двумя преемниками  d  и  е
        с :- h.
        с :- f, g.
        f :- h, i.
        d.  g.  h.
                % d,  g  и  h - целевые вершины
Для того, чтобы узнать, имеет ли эта задача решение, нужно просто спросить:
?- а.
Получив этот вопрос, пролог-система произведет поиск в глубину в дереве Рисунок 13.4 и после того, как пройдет через все вершины подграфа, соответствующего решающему дереву Рисунок 13.4(b), ответит "да".
Преимущество такого метода программирования И / ИЛИ-поиска состоит в его простоте. Но есть и недостатки:
- Мы получаем ответ "да" или "нет", но не получаем решающее дерево. Можно было бы восстановить решающее дерево при помощи трассировки программы, но такой способ неудобен, да его и недостаточно, если мы хотим иметь возможность явно обратиться к решающему дереву как к объекту программы.
- В эту программу трудно вносить добавления, связанные с обработкой стоимостей.
- Если наш И / ИЛИ-граф - это граф общего вида, содержащий циклы, то пролог-система, следуя стратегии в глубину, может войти в бесконечный рекурсивный цикл.
Попробуем постепенно исправить эти недостатки. Сначала определим нашу собственную процедуру поиска в глубину для И / ИЛИ-графов.
Прежде всего мы должны изменить представление И / ИЛИ-графов. С этой целью введём бинарное отношение, изображаемое инфиксным оператором '--->'. Например, вершина а с двумя ИЛИ-преемниками будет представлена предложением
а ---> или : [b, с].
Оба символа '--->' и ':' - инфиксные операторы, которые можно определить как
        :- ор( 600, xfx, --->).
        :- ор( 500, xfx, :).
Весь И / ИЛИ-граф Рисунок 13.4 теперь можно задать при помощи множества предложений
        а ---> или : [b, с].
        b ---> и : [d, e].
        с ---> и : [f, g].
        е ---> или : [h].
        f ---> или : [h, i].
цель( d). цель( g). цель( h).
Процедуру поиска в глубину в И / ИЛИ-графах можно построить, базируясь на следующих принципах:
Для того, чтобы решить задачу вершины В, необходимо придерживаться приведенных ниже правил:
(1) Если В - целевая вершина, то задача решается тривиальным образом.
(2) Если вершина В имеет ИЛИ-преемников, то нужно решить одну из соответствующих задач-преемников (пробовать решать их одну за другой, пока не будет найдена задача, имеющая решение).
(3) Если вершина В имеет И-преемников, то нужно решить все соответствующие задачи (пробовать решать их одну за другой, пока они не будут решены все).
Если применение этих правил не приводит к решению, считать, что задача не может быть решена.
Соответствующая программа выглядит так:
        решить( Верш) :-
                цель( Верш).
        решить( Верш) :-
                Верш ---> или : Вершины,
                % Верш - ИЛИ-вершина
                принадлежит( Верш1, Вершины),
                                    % Выбор преемника  Верш1  вершины  Верш
        решить( Bepш1).
        решить( Верш) :-
                Верш ---> и : Вершины,
                    % Верш - И-вершина
                решитьвсе( Вершины).
                                    % Решить все задачи-преемники
        решитьвсе( [ ]).
        решитьвсе( [Верш | Вершины]) :-
                решить( Верш),
                решитьвсе( Вершины).
Здесь принадлежит - обычное отношение принадлежности к списку.
Эта программа все еще имеет недостатки:
- она не порождает решающее дерево, и
- она может зацикливаться, если И / ИЛИ-граф имеет соответствующую структуру (циклы).
Программу нетрудно изменить с тем, чтобы она порождала решающее дерево. Необходимо так подправить отношение решить, чтобы оно имело два аргумента:
решить( Верш, РешДер).
Решающее дерево представим следующим образом. Мы имеем три случая:
(1) Если Верш - целевая вершина, то соответствующее решающее дерево и есть сама эта вершина.
(2) Если Верш - ИЛИ-вершина, то решающее дерево имеет вид
Верш ---> Поддерево
где Поддерево - это решающее дерево для одного из преемников вершины Верш.
(3) Если Верш - И-вершина, то решающее дерево имеет вид
Верш ---> и : Поддеревья
где Поддеревья - список решающих деревьев для всех преемников вершины Верш.
line();% Поиск в глубину для И / ИЛИ-графов
% Процедура решить( Верш, РешДер) находит решающее дерево для
% некоторой вершины в И / ИЛИ-графе
        решить( Верш, Верш) :-
        % Решающее дерево для целевой
                цель( Верш).
                   % вершины - это сама вершина
        решить( Верш, Верш ---> Дер) :-
                Верш ---> или : Вершины,        % Верш - ИЛИ-вершина
                принадлежит( Верш1, Вершины),
                                        % Выбор преемника  Верш1  вершины  Верш
                решить( Bepш1, Дер).
        решить( Верш, Верш ---> и : Деревья) :-
                Верш ---> и : Вершины,
             % Верш - И-вершина
                решитьвсе( Вершины, Деревья).
                                         % Решить все задачи-преемники
решитьвсе( [ ], [ ]).
        решитьвсе( [Верш | Вершины], [Дер | Деревья]) :-
                решить( Верш, Дер),
                решитьвсе( Вершины, Деревья).
        отобр( Дер) :-
                                        % Отобразить решающее дерево
                отобр( Дер, 0),  !.
                          % с отступом 0
        отобр( Верш ---> Дер, Н) :-
                                       % Отобразить решающее дерево с отступом Н
                write( Верш), write( '--->'),
                H1 is H + 7,
                отобр( Дер, H1),  !.
        отобр( и : [Д], Н) :-
                                       % Отобразить И-список решающих деревьев
        отобр( Д, Н).
        отобр( и : [Д | ДД], Н) :-
                                       % Отобразить И-список решающих деревьев
                отобр( Д, Н),
                tab( H),
                отобр( и : ДД, Н),  !.
        отобр( Верш, Н) :-
                write( Верш), nl.
