Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Комбинаторные задачи.doc
Скачиваний:
37
Добавлен:
25.09.2019
Размер:
720.38 Кб
Скачать

1.4. Организация исчерпывающего перебора

В отличие от большинства задач «непрерывной» математики, где первым шагом в анализе задачи должно быть исследование условий существования решения, для комбинаторных задач решение всегда существует, поскольку множество планов конечно. Полный перебор всех планов позволяет наверняка решить задачу. Другое дело, что для этого может понадобиться неприемлемо большое время. Только поэтому и существует разветвленная теория комбинаторных задач, основная цель которой – разработка и анализ эффективных, т.е. достаточно быстрых алгоритмов для различных частных типов комбинаторных задач. Тем не менее перебор планов остается наиболее универсальным методом решения. Если он не всегда пригоден для практических целей, то очень полезен для исследования задач, для сравнения с приближенными алгоритмами и т.п.

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

Наиболее часто для организации перебора планов используется схема, известная как перебор с возвратами.

Перебор планов задачи можно представить как обход дерева перебора, показанного на рис.1.1. Для упрощения рисунка принято, что множество допустимых значений каждой переменной xi состоит из целых чисел от 1 до mi. Каждая ветвь дерева соответствует плану задачи. Рисунок соответствует задаче с фиксированной размерностью n. Для задачи с нефиксированной размерностью ветви дерева могут иметь неодинаковую длину.

Рис.1.1

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

Размер дерева перебора может быть очень большим. Пусть для простоты все mi = m, тогда число всех планов (не обязательно допустимых) равно mn. Приняв довольно скромное значение m = 10, легко видеть, что при n  3 дерево можно обойти даже вручную, при значениях n от 4 до 7-8 с задачей без труда справится компьютер, но уже для значений n примерно от 11-12 и выше трудно рассчитывать на выполнение перебора с использованием любой существующей техники. Действительно, ведь в данном примере увеличение размерности задачи всего лишь на 1 приводит к увеличению времени решения в 10 раз. Это явление, когда комбинаторная задача для малой размерности решается запросто, но при увеличении размерности быстро становится практически неразрешимой, получило название комбинаторного взрыва.

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

Запишем общую схему перебора с возвратами в виде программы на псевдокоде. Чтобы не привязываться жестко к какому-либо конкретному виду множеств допустимых значений Ui, введем несколько абстрактных функций доступа к элементам этих множеств. Пусть First(U) – первый элемент множества U, Last(U) – его последний элемент, Next(U, x) – элемент, следующий в множестве U за элементом x, а значение Next(U, Last(U)) равно константе NoValue. Множество Ui будем записывать в программе как U[i]. Как известно, задачи обхода дерева удобнее всего программировать с помощью рекурсии. Определим рекурсивную процедуру CompleteSearch(k), которая решает комбинаторную задачу с параметром p и функцией ограничений G(X,p) при условии, что значения переменных x1, x2, …, xk зафиксированы.

procedure CompleteSearch(k: Integer);

begin

if(первые k переменных составляют полный план) then

begin

if G(X,p) then begin

Обработать полученный план X в соответствии

с типом задачи A, B или C;

end;

end

else begin

k:=k+1;

X[k]:=First(U[k]);

repeat

CompleteSearch(k);

X[k]:=Next(U[k],X[k]);

until X[k]=NoValue;

end;

end;

В основной программе вызов рекурсивной процедуры (после выполнения необходимых инициализаций) будет выглядеть так:

CompleteSearch(0);

Поясним фрагменты процедуры, записанные неформально. Условие «первые k переменных составляют полный план» для задачи с фиксированной размерностью n означает просто k = n. Если размерность не фиксирована, то должно быть какое-то правило, позволяющее отличать полный план от частичного. Например, при поиске пути в графе из вершины A в вершину B план полный, если он соединяет точки A и B.

Обработка полученного плана в соответствии с типом задачи означает следующее:

  • Для задачи типа A (поиск): следует прекратить поиск, поскольку найден допустимый план. Это можно запрограммировать, введя булевскую переменную со значением «план найден», проверка которой должна приводить к досрочному выходу из процедуры.

  • Для задачи типа B (перечисление): нужно зафиксировать найденный план (например, напечатать его или записать в файл) и продолжить работу.

  • Для задачи типа C (оптимизация): следует запоминать наилучшее из найденных значений целевой функции (его обычно называют рекордом) и для каждого полученного допустимого плана X сравнивать значение F(X) с рекордом, корректируя при необходимости рекорд.