7.9. Поиск по графу
7.9. Поиск по графу
Граф – это сеть, состоящая из узлов, соединенных дугами. Например, географическую карту можно рассматривать как граф, где узлами являются населенные пункты, а дугами, соединяющие их дороги. Если вы хотите найти кратчайший маршрут между двумя населенными пунктами, вам предстоит решить задачу нахождения кратчайшего пути между двумя узлами графа.
Проще всего описать граф в базе данных с помощью фактов, представляющих дуги между узлами графа. На рис, 7.3 приведен пример графа и его представления с помощью фактов. Чтобы пройти от узла g к узлу а, мы можем пойти по пути g, d, e, а или по одному из многих других возможных путей. Если мы представляем ориентированный граф, то предикат а следует понимать так, что а(Х, Y) означает, что существует дуга из X в Y, но из этого не следует существование дуги из Y в X. В данном разделе мы будем иметь дело только с неориентированными графами, у которых все дуги двунаправленные. Это допущение совпадает с тем, которое мы делаем в разд. 7.2 при поиске в лабиринте.
Простейшая программа поиска по графу, представленному так, как указано выше, выглядит следующим образом:
переход(Х,X).
переход(Х,Y):- (a(X,Z);a(Z,X)), переход(Z,Y).
К сожалению, эта программа может зацикливаться. Поэтому, как и раньше, мы используем список Т для хранения перечня тех узлов, в которых мы уже побывали в какой-либо рекурсии предиката.
переход(Х,Х,Т).
переход(Х,Y,T):- (a(X,Z);a(Z,X)), not (принадлежит(Z, Т)),переход(Z, Y,[Z|T]).
Эта программа, разработанная в разд. 7.2, осуществляет так называемый поиск «вглубь», поскольку вначале рассматривается только один из соседей узла по графу, Другие же соседи игнорируются до тех пор, пока неудачные попытки согласовать цели в рекурсивных вызовах не возвратят Пролог к рассмотрению данного узла.
Теперь давайте рассмотрим такой поиск по графу, который мог бы быть полезен на практике. Как быть, если мы должны спланировать маршрут поездки из одного города Северной Англии в другой? Для этого потребуется база данных с информацией о дорогах между городами в Северной Англии и их протяженности:
а(ньюкасл,карлайл,58).
а(карлайл,пенрит,23).
а(дарлингтон,ньюкасл,40).
а(пенрит, дарлингтон,52).
а(уэркингтон,карлайл,33).
а(уэркингтон,пенрит,39).
На некоторое время мы можем забыть о расстояниях и определить новый предикат:
a(X,Y):- a(X,Y,Z).
С помощью этого определения предиката а уже имеющаяся программа поиска по графу (переход) будет находить пути, по которым можно переезжать из одного места на графе в любое другое. Однако программа переход имеет недостаток: когда она успешно завершается, мы не знаем, какой путь она нашла. По меньшей мере мы вправе ожидать от программы переход выдачи нам в нужном порядке списка мест, которые придется посетить. Тем более, что в программе имеется перечень этих мест, правда, в порядке, обратном тому, какой нам нужен. Чтобы получить правильный список, мы можем воспользоваться программой обр, определенной в разд. 7.5. Тогда мы получим новое определение программы переход, которая возвращает найденный маршрут через свой третий аргумент:
переход(Старт,Цель,Путь):- переход0(Старт,Цель,[],R),обр(R, Путь).
переход0(Х,Х,Т,[Х|Т]).
переход0(Место,Y,Т,R):-следузел(Место,Т,Сосед),переход0(Сосед,Y,[Место|T],R).
следузел(Х,Бывали,Y):- (a(X,Y); a(Y,X)),not (принадлежит(Y,Бывали)).
Заметим, что предикат следузел позволяет получать для узла X «правильный» узел Y, т. е. такой, к которому можно непосредственно перейти от узла X. Ниже приводится пример работы этой программы при поиске маршрута из Дарлингтона в Уэркингтон:
?- переход(дарлингтон,уэркингтон,Х)
Х=[дарлингтон,ньюкасл,карлайл,пенрит,уэркингтон]
Это не самый лучший маршрут, однако, программа найдет другие маршруты если мы инициируем процесс возврата.
У этой программы много недостатков. Она совершенно не управляет выбором следующего участка пути, поскольку у нее нет доступа к полному набору возможных вариантов, а те выборы, которые у программы имеются, не представлены явно в виде структуры, которая может анализироваться программой, а неявно предопределены схемой работы механизма возврата.
Ниже приведен переработанный вариант программы, который отличается большей универсальностью. В дальнейшем мы увидим, как с помощью простых изменений в этой программе можно получить разнообразные методы поиска.
переход(Старт,Цель,Путь):- переход1([[Старт]],Цель,R),обр(R, Путь).
переход1([Первый|Ост],Цель,Первый):- Первый =[Цель|_].
переход1([[Послед|Бывали]|Прочие],Цель,Путь):-найтивсе([Z, Послед|Бывали], следузел(Послед, Бывали,Z), Список), присоединить(Список, Прочие, НовПути), переход1(НовПути,Цель,Путь).
Предикат следузел остается прежним. Предикату переход1 передается список рассматриваемых путей вместе с конечным пунктом, и в последнем аргументе он возвращает удачный путь. Список рассматриваемых путей – это просто все дороги, начинающиеся в начальной точке, которые мы уже рассмотрели. Мы надеемся, что одна из них при продлении даст путь, который приведет нас в конечный пункт. Все пути представлены в виде обратных списков населенных пунктов, так что они могут также выполнять функции перечня мест, где мы уже бывали.
В самом начале имеется только один возможный путь, который можно пытаться продлить. Это просто путь, который начинается в исходном пункте и никуда не ведет. Если мы стартуем из Дарлингтона, то это будет [дарлингтон]. Если теперь исследовать пути ведущие из Дарлингтона в соседние города, то можно обнаружить, что имеются два возможных пути [ньюкасл, дарлингтон] и [пенрит, дарлингтон]. Поскольку Уэркингтон не встречается ни на одном из этих путей, необходимо решить, какой из этих путей следует продолжить. Если принято решение продлить первый путь, то мы обнаружим, что существует всего один доступный узел – последний город на этом пути. Итак, кроме пути Дарлингтон – Пенрит у нас есть новый путь: [карлайл, ньюкасл, дарлингтон].
Наш «изыскатель», переход1 ведет полный список путей, по которым, может быть, стоит двигаться. Как же он решает какой из путей следует рассмотреть первым? Он просто выбирает первый попавшийся. Затем он ищет все возможные способы продления этого пути до следующего населенного пункта (используя найтивсе для построения списка всех таких продленных путей) и помещает получившиеся пути в начало списка для рассмотрения их на следующем уровне рекурсии.
В результате, переход1 ведет себя таким образом, что он попробует все возможные способы продления первого пути прежде чем будет рассматривать альтернативные пути. Такая стратегия поиска является одним из вариантов поиска вглубь. Между прочим, переход1 рассматривает пути совершенно в том же порядке, что и переход0. Быть может вам будет интересно выяснить, почему это так.
Если нас интересует кратчайший путь от Дарлингтона до Уэркингтона, то имеющаяся программа для этого не подходит. Первое найденное ею решение – это не кратчайший путь, а наоборот, самый длинный (в данном случае). Нам нужно изменить программу таким образом, чтобы она строила пути в порядке возрастания их длины. Если мы изменим ее так, чтобы она всегда продлевала более короткие пути, прежде чем рассматривать более длинные, то она будет вынуждена находить вначале кратчайшие пути (если измерять длину пути числом городов на нем). Полученная программа будет осуществлять поиск вширь. Единственное, что нужно сделать для этого – это вставлять новые альтернативы в конец всего списка возможностей, а не в начало, как в последнем примере. Мы просто исправим второе утверждение в определении переход1, чтобы он выглядел следующим образом:
переход1([[Послед|Бывали]|Прочие],Цель,Путь):-найтивсе([Z,Послед|Бывали], следузел(Послед, Бывали,Z),Список), присоединить(Прочие,Список,НовПути), переход1(НовПути,Цель, Путь).
Теперь исправленная программа находит возможные пути из Дарлингтона в Уэркингтон в следующем порядке:
[дарлингтон,пенрит,уэркингтон]
[дарлингтон,ньюкасл, карлайл,уэркингтон]
[дарлингтон,пенрит,карлайл,уэркингтон]
[дарлингтон,ньюкасл,карлайл,пенрит,уэркингтон]
Мы можем значительно упростить эту программу, если уверены, что ответ на вопрос всегда существует и если нам нужно только первое решение. В этом случае отпадает необходимость в проверке на зацикливание. Попробуйте самостоятельно выяснить, почему это так.
К сожалению, путь через наименьшее число городов не обязательно будет самым кратчайшим по километражу. До сих пор мы не принимали во внимание информацию о расстояниях, имеющуюся в нашем графе. Если же мы добавим к нашему графу несколько фиктивных городов, чтобы получить:
а(ньюкасл,карлайл,58).
а(карлайл,пенрит,23).
а(городБ,городаА,15).
а(пенрит, дарлингтон,52).
а(городБ,городВ,10).
а(уэркингтон, карлайл, 33).
а(уэркингтон,городВ,5).
а(уэркингтон,пенрит,39).
а(дарлингтон,городА,25).
то путь, кратчайший по километражу, фактически будет построен последним, поскольку он проходит через большое число городов. С каждым путем, который может быть продолжен, нам нужно связать и поддерживать в процессе работы программы указатель текущей длины этого пути. Тогда программа будет всегда продлевать путь с наименьшим километражем. Такая стратегия называется поиском по критерию первый-лучший. Будем теперь представлять путь в списке альтернативных путей в виде структуры г(М, П), где М – общая длина пути в километрах, а П – список мест, где мы уже побывали. Модифицированный предикат переходЗ находит кратчайший путь в списке альтернатив. Предикат кратчайший выделяет кратчайший путь в отдельный список, а остальные пути – в другой список. Предикат продлить находит все допустимые продолжения текущего кратчайшего пути и добавляет их к списку. Это в свою очередь требует новой версии предиката следузел, которая прибавляет расстояние до следующего города к уже вычисленному расстоянию. В целом программа выглядит так:
переходЗ (Пути,Цель,Путь):-кратчайший (Пути,Кратчайший,ОстПути), продлить(Кратчайший,Цель,ОстПути,Путь).
продлить(г(Расст,Путь),Цель,_,Путь):- Путь = [Цель|_].
продлить(г(Расст,[Послед| Бывали]),Цель,Пути,Путь):-найтивсе(г(D1,[Z,Послед|Бывали]),следузел(Послед,Бывали,Z,Расст,D1),Список), присоединить(Список,Пути,НовПути), переходЗ(НовПути,Цель,Пути).
кратчайший([Путь[Пути],Кратчайший,[ПутьЮст]):-кратчайший(Пути,Кратчайший,Ост), короче(Кратчайший,Путь),!.
кратчайший(Путь|Ост],Путь,Ост). короче(г(М1,_),г(М2, _):- M1 ‹ М2.
следузел(Х,Бывали,Y,Расст,НовРасст):-(a(X,Y,Z); a(Y,X,Z)),not(принадлежит(Y,Бывали)),НовРасст is Расст+Z.
Чтобы использовать эту программу, необходимо задать вопрос, содержащий предикат переход, определенный следующим образом:
переход (Старт,Цель,Путь):-переход3([г(0,[Старт])],Цель,R), обр(R,Путь).
Эта новая программа успешно строит возможные пути в по-рядке возрастания их фактической протяженности. Может быть, вам захочется изменить ее так, чтобы вместе с ответами она печатала длины различных путей.
Мы лишь затронули вопрос о возможных способах организации поиска по графу. Сведения о том, как осуществлять поиск по графу с использованием более эффективных критериев, чем «первый лучший», можно найти в литературе по искусственному интеллекту. Например: Nilsson N. Principles of Artificial Intelligence, Springer-Verlag, 1982[10] и Winstone P. Artificial Intelligence, (second edition), Addison-Wesley, 1984.[11]
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Поиск
Поиск Элемент input со значением “search” в атрибуте type будет вести себя примерно так же, как элемент ввода со значением “text” атрибута type:<label for="query">Поиск</label><input id="query" name="query" type="search">Единственная разница между “text” и “search” состоит в том, что браузер может
Поиск
Поиск Поскольку каналы, на которые вы подписаны, в некотором роде являются вашей базой знаний, в ней нужно уметь эффективно ориентироваться – упорядочивать новости по типам, выделять запомнившиеся сообщения, искать нужные записи. Для этого Reader предлагает сразу
Поиск
Поиск С количеством фотографий, производимых современными обладателями цифровиков, работа по разбору и классификации фотографий превращается в нетривиальное занятие: легкость съемки и дешевизна карт памяти привела к тому, что мы практически не удаляем снимки, даже
Поиск
Поиск В процессе работы в компьютере накапливается большое количество файлов, и зачастую сориентироваться в них самостоятельно и найти нужный оказывается затруднительно. В этом случае вам на помощь придет система поиска. В Windows Vista она была значительно
Поиск на научных сайтах с использованием платформы Flexum «Поиск по научным сайтам»
Поиск на научных сайтах с использованием платформы Flexum «Поиск по научным сайтам» Тема научного поиска не прошла мимо разработчиков персональных поисковиков. Подробному рассказу о возможностях таких поисковых систем посвящена отдельная глава нашей книги (см. главу 6).
RSS-поиск
RSS-поиск Пополнять список своего RSS-агрегатора можно различными способами. Первый и наиболее распространенный – простой поиск сайтов по интересующим темам, а затем подписка на их RSS-ленты, если, конечно таковые имеются. Способ несложный, однако на редкость медленный и
Поиск
Поиск Если вы хотите удалить пункт Поиск (Найти) из меню кнопки Пуск, то откройте разделHKEY_CURRENT_USER SoftwareMicrosoftWindowsCurrentVersionPoliciesExplоrer и создайте параметр NoFind типа DWORD со значением, равным 1.После перезагрузки пункт Поиск исчезнет из меню кнопки Пуск, а также исчезнет команда
Поиск
Поиск Классический видЧтобы использовать классический вид поиска файлов без анимированного персонажа, то присвойте строковому параметру Use Search Asst значение no в разделе HKCUSoftwareMicrosoftWindowsCurrentVersionExplorerCabinetStateОчистка истории раннее вводимых словЕсли вы часто пользуетесь
Поиск
Поиск Строка поискаЧтобы скрыть строку поиска из IE7, в разделе HKCUSoftwarePoliciesMicrosoftInternet ExplorerInfoDeliveryRestrictionsсоздайте параметр типа DWORD ·NoSearchBox· со значением 1. Перезапустите IE7, чтобы изменения вступили в силу. Кнопка Поиск (IE6)Чтобы изменить адрес поисковика, который у вас
Поиск
Поиск На всех страницах сайта обязательно должно быть поле для поиска товаров. Удивительно, но в некоторых интернет-магазинах отсутствует поддержка поиска по сайту. Убедитесь, что на вашем сайте есть поиск. Более того, сделайте функцию поиска максимально заметной. Чаще
Поиск
Поиск Чтобы начать поиск, следует выполнить команду меню Search ? Find/Replace (Поиск ? Найти/Заменить) или нажать сочетание клавиш Ctrl+F. В нижней части окна программы появится панель поиска.В поле Find (Найти) необходимо указать искомое слово (или выражение), а затем нажать кнопку Find All
Яндекс. Поиск – быстрый поиск документов
Яндекс. Поиск – быстрый поиск документов Документы, как известно, имеют премерзкое свойство накапливаться. И чем больше документов, тем труднее в их залежах найти нужный. Электронные документы здесь не слишком отличаются от бумажных. Проблема места для хранения, правда,
Поиск
Поиск Поиск величины при вводе Каким способом можно производить поиск подходящих величин в момент ввода? Табличный курсор (визуально) должен перемещаться к наиболее подходящему значению при добавлении пользователем новых символов водимой величины.Первоначально код
Поиск
Поиск Управление отображением команды Поиск, которая также по умолчанию входит в состав меню кнопки Пуск, осуществляется в системном реестре в разделе HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionPoliciesExplorer с помощью REG_DWORD-параметра NoFind. Чтобы удалить данную функцию, следует присвоить
Глава 12 Поиск с предпочтением: эвристический поиск
Глава 12 Поиск с предпочтением: эвристический поиск Поиск в графах при решении задач, как правило, невозможен без решения проблемы комбинаторной сложности, возникающей из-за быстрого роста числа альтернатив. Эффективным средством борьбы с этим служит эвристический