8.5.2. Повышение эффективности программы раскраски карты

8.5.2. Повышение эффективности программы раскраски карты

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

Пусть карта задана отношением соседства

соседи( Страна, Соседи)

где Соседи — список стран, граничащих со страной Страна. При помощи этого отношения карта Европы с 20-ю странами будет представлена (в алфавитном порядке) так:

соседи( австрия, [венгрия, запгермания, италия,

 лихтенштейн, чехословакия,

 швейцария, югославия]).

соседи( албания, [греция, югославия]).

соседи( андорра, [испания, франция]).

...

Решение представим в виде списка пар вида

Страна / Цвет

которые устанавливают цвет для каждой страны на данной карте. Для каждой карты названия стран всегда известны заранее, так что задача состоит в нахождении цветов. Таким образом, для Европы задача сводится к отысканию подходящей конкретизации переменных C1, C2, СЗ и т.д. в списке

[австрия/C1, албания/C2, андорра/С3, ...]

Теперь определим предикат

цвета( СписЦветСтран)

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

цвета( []).

цвета( [Страна/Цвет | Остальные] ) :-

 цвета( Остальные),

 принадлежит( Цвет, [желтый, синий, красный, зеленый]),

 not( принадлежит( Страна1/Цвет, Остальные),

 сосед( Страна, Страна1) ).

сосед( Страна, Страна1) :-

 соседи( Страна, Соседи),

 принадлежит( Страна1, Соседи).

Здесь принадлежит( X, L) — как всегда, отношение принадлежности к списку. Для простых карт с небольшим числом стран такая программа будет работать. Что же касается Европы, то здесь результат проблематичен. Если считать, что мы располагаем встроенным предикатом setof, то можно попытаться раскрасить карту Европы следующим образом. Определим сначала вспомогательное отношение:

страна( С) :- соседи( С, _ ).

Тогда вопрос для раскраски карты Европы можно сформулировать так:

?- sеtоf( Стр/Цвет, страна( Стр), СписЦветСтран),

 цвета( СписЦветСтран).

Цель setof — построить "шаблон" списка СписЦветСтран, в котором в элементах вида страна/ цвет вместо цветов будут стоять неконкретизированные переменные. Предполагается, что после этого цель цвета конкретизирует их. Такая попытка скорее всего потерпит неудачу вследствие неэффективности работы программы.

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

Ясно поэтому, что эффективность зависит от порядка раскраски стран. Интуиция подсказывает простую стратегию раскраски, которая должна быть лучше, чем случайная: начать со страны, имеющей иного соседей, затем перейти к ее соседям, затем — к соседям соседей и т.д. В случае Европы хорошим кандидатом для начальной страны является Западная Германия (как имеющая наибольшее количество соседей — 9). Понятно, что при построении шаблона списка элементов вида страна/цвет Западную Германию следует поместить в конец этого списка, а остальные страны - добавлять со стороны его начала. Таким образом, алгоритм раскраски, который начинает работу с конца списка, в начале займется Западной Германией и продолжит работу, переходя от соседа к соседу.

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

Можно было бы построить такой правильно упорядоченный список стран вручную, но в этом нет необходимости. Эту работу выполнит процедура создспис. Она начинает построение с некоторой указанной страны (в нашем случае — с Западной Германии) и собирает затем остальные страны в список под названием Закрытый. Каждая страна сначала попадает в другой список, названный Открытый, а потом переносится в Закрытый. Всякий раз, когда страна переносится из Открытый в Закрытый, ее соседи добавляются в Открытый.

создспис( Спис) :-

 собрать( [запгермания], [], Спис ).

собрать( [], Закрытый, Закрытый).

  % Кандидатов в Закрытый больше нет

собрать( [X | Открытый], Закрытый, Спис) :-

 принадлежит( X | Закрытый), !,

  % X уже собран ?

 собрaть( Открытый, Закрытый, Спис).

  % Отказаться от X

собрать( [X | Открытый], Закрытый, Спис) :-

 соседи( X, Соседи),

  % Найти соседей X

 конк( Соседи, Открытый, Открытый1),

  % Поместить их в Открытый

 собрать( Открытый1, [X | Закрытый], Спис).

  % Собрать остальные

Отношение конк — как всегда — отношение конкатенации списков.