3581762_PEREBOR_S_VOZVRATOM

advertisement
Прикладные задачи целочисленной оптимизации.
1.Задача о ферзях.
Имеется шахматная доска 8*8, т.е. она содержит 8^{2} клеток. Нам требуется
расположить максимальное число ферзей не бьющих друг друга и указать все
возможные позиции максимального числа ферзей не бьющих друг друга.
Сформулируем математическую модель этой задачи.
Будем рассматривать не шахматную доску 8*8, а доску n*n, n=1,2.. Введем
двоичные переменные x_{i,j}, i=1,2..n, j=1,2..n. x_{i,j}=1 - означает, что в клетке
находящейся на i-ой вертикали и j-ой горизонтали находится ферзь. Tеперь
запишем условие выражающее, что ферзи не бьют друг друга:
на горизонтали j может находится ферзь или не одного, это выражают
неравенства \sum_{i=1}^{n} x_{i,j}\leq 1 j=1,2..n аналогично
записываются условия о вертикалях: \sum_{j=1}^{n} x_{i,j}\leq 1
i=1,2..n Tеперь запишем эти же условия для диагоналей: 1 диагональ
x_{1,1}+x_{2,2}+x_{3,3}+...+x_{n,n}\leq 1 (слева-вниз)
x_{1,2}+x_{2,3}+x_{3,4}+...+x_{n-1,n}\leq 1
x_{2,1}+x_{3,2}+x_{4,3}+...+x_{n,n-1}\leq 1
...
2 диагональ x_{1,n}+x_{2,n-1}+x_{3,n-1}+...+x_{n,1}\leq 1
(справа-вниз)x_{1,n-1}+x_{2,n-2}+x_{3,n-3}+...+x_{n-1,1}\leq 1
...
Целевая функция имеет вид: x_{1,1}+x_{2,1}+...+x_{n,1}+
+x_{1,2}+x_{2,2}+...+x_{n,2}+...+
+x_{1,n}+x_{2,n}+...+x_{n,n}
Нам необходимо максимизировать целевую функцию. В итоге мы получаем
двоичную линейную задачу оптимизации с n^{2} переменными с числом
линейных неравенств 2n+2+4(n-2)=6(n-1), n\geq 2, где каждая
переменная принимает значение либо ноль, либо единица.
Начинаем решать с n=1
Для вычисления решений в дальнейшем используем программу Mathcad и метод
полного или сплошного перебора)
Метод заключается в переборе всех возможных вариантов сочетаний допустимых
значений переменных, проверке выполнения для каждого ограничения и вычислений в
удовлетворительных случаях соответствующих случаях целевой функции. Из
полученного множества значений выбирается максимальное (или минимальное), а набор
значений переменных для него и будет решением задачи. В общем случае число
вычислительных процедур при полном переборе быстро растет и равняется N=2^n*(m+1),
где n- число переменных, m- число ограничений. Метод имеет простой алгоритм и может
быть легко реализован с использованием средств программирования пакета Mathcad.
целевая функция x_{1,1} ограничения x_{1,1}\leq 1
f(x):=x_{1}
1*1
ORIGIN  1
f ( x)  x
1
R 
f0
for x1  0  1
if x1  1
s  x1
if s  f
fs
K  x1
s  0 otherwise
K
R1
т.е.мы можем разместить только одного ферзя
n=2
целевая функция:
x_{1,1}+x_{1,2}+x_{2,1}+x_{2,2}
ограничения:
x_{1,1}+x_{2,1}\leq 1
x_{1,2}+x_{2,2}\leq 1
x_{1,1}+x_{1,2}\leq 1
x_{2,1}+x_{2,2}\leq 1
x_{1,1}+x_{2,2}\leq 1
x_{2,1}+x_{1,2}\leq 1
для вычисления всех возможных расстановок ферзя используем
следующее:
Пусть N^{+}={j\mid j\in N, x^{0}_{j}=1}
N^{-}={j\mid j\in N, x^{0}_{j}=0}
N={1,2..n}
Tогда неравенство
\sum_{j\in N^{+}} x_{j}-\sum_{j\in N^{-}} x_{j}\leq \mid N^{+}\mid - 1 отсекает
точку (вершину гиперкуба) x^{0}, определяющую N^{+} и N^{-}
тогда с учетом выше сказанного для доски 2*2 имеем:
N=(1,2,3,4)
x^{0}=(0,0,0,1)
N^{+}={j \mid j\in N, x^{0}_{4}=1}={4}
N^{-}={1,2,3}
ограничение: x_{4}-(x_{1}+x_{2}+x_{3})\leq 0 т.е. -x_{1,1}-x_{2,1}x_{1,2}+x_{2,2}\leq 0
(в дальнейшем, для удобства, нумерация клеток будет вестись с одним
индексом:
x_{1,1}=x_{1}, x_{2,1}=x_{2}... )
2*2
f ( y)  y  y  y  y
1
R 
2
3
4
f0
for y1  0  1
for y2  0  1
for y3  0  1
for y4  0  1
if ( y1  y2  1)  ( y3  y4  1)  ( y1  y3  1)  ( y2  y4  1)  ( y1  y4  1)  y2  y3  1
s  y1  y2  y3  y4
if s  f
fs
 y1 
y2
K 
 y3 
 y4 
 
s  0 otherwise
K
f ( R)  1
 0 
0
R 
0
1
 
при добавлении ограничений получаем следующий результат:
x^{0}=(0,0,1,0) N^{+}={3} N^{-}=(1,2,4) x_{3}-x_{1}-x_{2}-x_{4}\leq 0
x^{0}=(0,1,0,0) N^{+}={2} N^{-}=(1,3,4) x_{2}-x_{1}-x_{3}-x_{4}\leq 0
x^{0}=(1,0,0,0) N^{+}={1} N^{-}=(2,3,4) x_{1}-x_{2}-x_{3}-x_{4}\leq 0
т.е. на доске размером 2*2 мы можем поставить только одного ферзя в 4
различных позиции.
n=3
программа-функция имеет вид:
(смотреть в приложении 1)
т.е. мы можем расставить двух ферзей, первая из позиций клеток - 6,7
N=(1,2,3,4,5,6,7,8,9)
x^{0}=(0,0,0,0,0,1,1,0,0)
N^{+}={6,7} N^{-}={1,2,3,4,5,8,9}
-x_{1}-x_{2}-x_{3}-x_{4}-x_{5}+x_{6}+x_{7}-x_{8}-x_{9}\leq 1
x^{0}=(0,0,0,1,0,0,0,0,1)
N^{+}={4,9} N^{-}={1,2,3,5,6,7,8}
-x_{1}-x_{2}-x_{3}+x_{4}-x_{5}-x_{6}-x_{7}-x_{8}+x_{9}\leq 1
x^{0}=(0,0,1,0,0,0,0,1,0)
N^{+}={3,8} N^{-}={1,2,3,4,5,8,9}
-x_{1}-x_{2}+x_{3}-x_{4}-x_{5}-x_{6}-x_{7}+x_{8}-x_{9}\leq 1
------------------------------------------------------x^{0}=(0,0,0,0,0,1,1,0,0)
N^{+}={6,7} N^{-}={1,2,3,4,5,8,9}
-x_{1}-x_{2}-x_{3}-x_{4}-x_{5}+x_{6}+x_{7}-x_{8}-x_{9}\leq 1
x^{0}=(0,0,0,0,0,1,1,0,0)
N^{+}={6,7} N^{-}={1,2,3,4,5,8,9}
-x_{1}-x_{2}-x_{3}-x_{4}-x_{5}+x_{6}+x_{7}-x_{8}-x_{9}\leq 1
x^{0}=(0,0,0,0,0,1,1,0,0)
N^{+}={6,7} N^{-}={1,2,3,4,5,8,9}
-x_{1}-x_{2}-x_{3}-x_{4}-x_{5}+x_{6}+x_{7}-x_{8}-x_{9}\leq 1
x^{0}=(0,0,0,0,0,1,1,0,0)
N^{+}={6,7} N^{-}={1,2,3,4,5,8,9}
-x_{1}-x_{2}-x_{3}-x_{4}-x_{5}+x_{6}+x_{7}-x_{8}-x_{9}\leq 1
x^{0}=(0,0,0,0,0,1,1,0,0)
N^{+}={6,7} N^{-}={1,2,3,4,5,8,9}
-x_{1}-x_{2}-x_{3}-x_{4}-x_{5}+x_{6}+x_{7}-x_{8}-x_{9}\leq 1
т.е. больше вариантов расстановки двух ферзей нет. Количество расстановок
двух ферзей на доске 3*3 равно 8.
n=4
целевая функция:
ограничения:
программа-функция имеет вид:
т.е. мы можем расставить четырех ферзей, первая из позиций клеток - 3,5,12,14.
N^{+}={3,5,12,14}
N^{+}={2,8,9,15}
N^{+}={8,9,15} т.е. на доске 4*4 мv можем расставить 4 ферзей двумя
различными способами.
Tак как нас интересует только максимальное число ферзей, позиции
расстановки для трех и менее ферзей мы искать не будем.
n=5
При вычислении максимального числа расстановок на доске 5*5 возникла
проблема затраты времени компьютером на вычисление результатов и
потребовался новый алгоритм для решения задачи.
Алгоритм перебора с возвращениями.
Для решения этой задачи решено было использовать алгоритм перебора с
возвращениями.
Это рекурсивный алгоритм, рассмотрим его более подробно. Первого ферзя
можно поставить куда угодно, после этого второго ферзя надо ставить так,
чтобы он не угрожал первому, затем третьего и т. д. Если на каком-то этапе
окажется, что очередного ферзя поставить не удастся, то надо вернуться на шаг
назад и переставить предыдущего ферзя на следующую допустимую позицию и
затем повторить попытку. Если предыдущего ферзя переставить некуда, либо
после его перестановки очередная попытка снова оказалась неудачной,
необходимо вернуться еще на один шаг назад и т. д. вплоть до самого первого
ферзя. В случае с ферзями, количество переборов можно сократить, заметив,
что очередного ферзя надо ставить на новую горизонталь (или вертикаль).
Эта часть алгоритма была проверена вручную и была найдена следующая
шахматная позиция для доски 8*8:
A8, b4, c1, d3, e6, f2,g7,h5
На нахождение этой позиции было затрачено 1,5 часа.
Перебор с возвратом (backtracking)
Во многих задачах из различных областей знания ставятся вопросы-задания
типа: “Сколько существует способов …”, “Подсчитайте количество элементов
…”, “Перечислите все возможные варианты …”, “Есть ли способ …”,
“Существует ли объект…” и т. п. Ответы на них, как правило, требуют
исчерпывающего поиска в некотором множестве M всех возможных
вариантов, среди которых находятся решения конкретной задачи. Существуют
два общих метода организации исчерпывающего поиска: перебор с возвратом
(backtracking) и его естественное логическое дополнение  метод решета.
Решение задачи методом перебора с возвратом строится конструктивно
последовательным расширением частичного решения. Если на конкретном шаге
такое расширение провести не удается, то происходит возврат к более
короткому частичному решению, и попытки его расширить продолжаются. Для
ускорения перебора с возвратом вычисления всегда стараются организовать так,
чтобы была возможность отметать как можно раньше и как можно больше
заведомо неподходящих вариантов M. Незначительные модификации метода
перебора с возвратом, связанные с представлением данных или особенностями
реализации, имеют и иные названия: метод ветвей и границ (branch and bound),
поиск в глубину (depth first search), метод проб и ошибок и т. д. Перебор с
возвратом практически одновременно и независимо был изобретен многими
исследователями еще до его формального описания. Он находит применение
при решении различных комбинаторных задач в области искусственного
интеллекта.
При использовании метода решета вместо конструктивного построения
решений задачи из множества возможных вариантов M исключаются все
элементы, не являющиеся решениями. Методы решета нашли широкое
применение в теоретико-числовых задачах.
И метод перебора с возвратом, и метод решета, вообще говоря, не являются ни
методами, ни алгоритмами решения задач. На них следует смотреть лишь как на
некоторые общие схемы, которые применяются для решения той или иной
задачи. Реализация этих схем в виде конкретных алгоритмов часто требует
значительных дополнительных усилий и большой изобретательности, особенно
в представлении данных.
Ниже мы будем вести речь только о методе перебора с возвратом. Соединение
его с рекурсией определяет специфический способ реализации рекурсивных
вычислений и заслуживает своего отдельного названия. Этот союз двух
эффективных методов формирования ценнейшего сочетания умений,
включающих в себя творчество, способность к анализу и синтезу, мы будем
называть возвратной рекурсией.
Заметим, что при использовании возвратной рекурсии отпадает необходимость
непосредственно организовывать возвраты и отслеживать правильность их
осуществления. Они, как правило, становятся встроенной частью механизма
выполнения рекурсивных вызовов. Этот факт наглядно демонстрируется на
приведенных ниже примерах: расстановка ферзей на шахматной доске, задача
коммивояжера, задача о назначениях, задача о рюкзаке.
Опишем некоторую совокупность  комбинаторных задач, к которым заведомо
применим алгоритм перебора с возвратом.
Пусть M0, M1, ..., Mn-1 - n конечных линейно упорядоченных множеств и G совокупность ограничений (условий), ставящих в соответствие векторам вида v
= (v0, v1, ..., vk)T (vj Mj; j = 0, 1, ... k; kn-1), булево значение G(v)  {истина,
ложь}. Векторы v = (v0, v1, ..., vk)T, для которых G(v)  истина, назовем
частичными решениями. Пусть, далее, существует конкретное правило P, в
соответствии с которым некоторые из частичных решений могут объявляться
полными решениями. Тогда возможна постановка следующих поисковых
задач:
Найти все полные решения или установить отсутствие таковых.
Найти хотя бы одно полное решение или установить его отсутствие.
Наиболее часто подобные задачи формулируются и решаются при следующем
задании правила P на частичных решениях :
Общий метод решения приведенных задач состоит в последовательном
покомпонентном наращивании вектора v слева направо, начиная с v0, и
последующих испытаниях его ограничениями G и правилом P.
Ниже на некотором паскалеподобном псевдокоде приведены три схемы
решения задач методом перебора с возвратом: нерекурсивный вариант поиска
всех решений (схема 1), рекурсивный вариант поиска всех решений (схема 2),
рекурсивный вариант поиска одного решения (схема 3).
По схеме 1 находятся все решения задачи, если они есть, и работа завершается
при j=0. Её рекурсивный вариант, представленный на схеме 2, существенно
более прост и очевиден.
Схема 1. Нерекурсивный вариант схемы перебора с возвратом для
нахождения всех решений
Генерирование всех решений по схеме 2, если они есть, организуется вызовом
backtracking(0). Нахождение одного решения по схеме 3, если оно есть,
проводится вызовом: backtracking(0,’решение не найдено’). Обратите
внимание, что в схемах 2 и 3 возврат не появляется в явном виде, будучи
естественной частью реализации механизма рекурсии.
Схема 2. Рекурсивный вариант схемы перебора с возвратом для
нахождения всех решений
Схема 3. Рекурсивный вариант схемы перебора с возвратом для
нахождения одного решения
В дальнейшем рассмотрены конкретные задачи, решаемые методом перебора с
возвратом. Заметим, что в общем случае этот метод приводит к алгоритмам с
экспоненциальной временной сложностью, а применяется он в основном к
классу так называемых Np-полных задач (задача коммивояжера, задача о
рюкзаке и т. д.) . Задачи этого класса эквивалентны друг другу в том смысле,
что все они разрешимы недетерминированными алгоритмами полиномиальной
сложности. Далее, для них известно, что либо все они разрешимы, либо ни одна
из них не разрешима детерминированными алгоритмами полиномиальной
сложности. Иными словами, если хотя бы для одной из этих задач не
существует детерминированного алгоритма, имеющего в худшем случае
полиномиальную трудоемкость, то такие алгоритмы не должны существовать и
для остальных задач этого класса. Наоборот, если хотя бы для одной из этих
задач удалось найти детерминированный алгоритм, имеющий в худшем случае
полиномиальную трудоемкость, то подобные алгоритмы существовали бы и для
остальных задач этого класса и, более того, их можно было бы построить.
Несколько слов об упомянутых выше недетерминированных алгоритмах.
Пояснить смысл этого понятия можно так. Пусть алгоритм выполняется до тех
пор, пока не доходит до места, с которого должен быть сделан выбор из
нескольких альтернатив. Детерминированный алгоритм однозначно осуществит
выбор конкретной альтернативы и продолжит работать в соответствии с эти
выбором. Недетерминированный алгоритм исследует все возможности
одновременно, как бы копируя себя для реализации вычислений по всем
альтернативам одновременно (оператор выбор). Далее все копии работают
независимо друг от друга и по мере необходимости продолжают создавать
новые копии. Копия, сделавшая неправильный или безрезультатный выбор
прекращает свою работу (оператор неуспех). Копия, нашедшая решение задачи,
объявляет об этом (оператор успех), давая тем самым сигнал другим копиям о
прекращении вычислений. Недетерминированные алгоритмы, являясь весьма
полезной и продуктивной абстракцией, рекурсивны по сути, ибо при реализации
оператора выбора фактически обращаются сами к себе.
Ниже речь будет идти только о детерминированных рекурсивных алгоритмах.
Сразу же оговоримся, что для многих из рассмотренных задач существуют
более эффективные способы решения по сравнению с приведенными. Однако
дидактическая ценность метода перебора с возвратом в соединении с рекурсией
неоспорима. Во-первых, программы решения многих задач строятся по единой
схеме, а во-вторых, они компактны и тем самым просты для понимания и
усвоения соответствующих идей.
Задача 1.1. Расстановки ферзей. Составить рекурсивную программу-функцию,
находящую все возможные расстановки n ферзей на шахматной доске размером
nn так, чтобы они не били друг друга.
Решение. В соответствии с описанной ранее общей схемой 2 алгоритма
перебора с возвратом предложенную задачу можно было бы решать по
приведенному ниже алгоритму “Все расстановки”. По нему ферзи
расставляются последовательно на вертикалях с номерами от нуля и далее. В
процессе выполнения предписания возможны снятия ферзей с доски (возвраты).
Алгоритм “Все расстановки”
1. Полагаем D = , j = 0 (D  множество решений, j  текущий столбец для
очередного ферзя).
2. Пытаемся в столбце j продвинуть вниз по вертикали или новый (если
столбец j пустой), или уже имеющийся там ферзь на ближайшую
допустимую строку. Если это сделать не удалось, то переходим к пункту
4.
3. j j+1. Если j < n-1, то переходим к пункту 2. В противном случае j = n-1,
то есть все вертикали уже заняты. Найденное частичное решение
запоминаем в множестве D и переходим к пункту 2.
4. j  j-1, то есть снимаем ферзь со столбца j и переходим к предыдущему
столбцу. Если j 0, то выполняем пункт 2. Иначе вычисления
прекращаем. Решения задачи находятся в множестве D, которое, вообще
говоря, может быть и пустым.
Никто и ничто не мешает нам при небольших n пытаться выполнить этот
алгоритм “вручную”. Но чтобы идти дальше и пытаться переложить эти заботы
на компьютер, необходимо остановиться на каком-либо представлении для
данных.
Введем в рассмотрение четыре вспомогательных вектора: pos, ho, dd и du c
длинами n, n, 2n-1 и 2n-1 соответственно. Использовать их будем следующим
образом (см. рис.1):
hoi = 1, если на горизонтали с номером i (i = 0, 1, …, n-1) имеется ферзь, и hoi
= 0 - в противном случае;
dus = 1, если на диагонали с номером s (s = 0, 1, …, 2n-2), идущей слева
направо и снизу вверх, имеется ферзь, и dus = 0 - в противном случае;
dds = 1, если на диагонали с номером s (s = 1, 2, …, 2n-1), идущей слева
направо и сверху вниз, имеется ферзь, и dds+1 = 0 - в противном случае;
posj = i , если в позиции (i, j) (i, j = 0, 1, …, n-1) стоит ферзь.
Использование этих соглашений позволяет получить такие утверждения:
В позицию (i, j) можно поставить ферзь, если hoi + dui+j +j + ddn+i-j+i-j = 0.
Поставить ферзь в позицию (i, j) равносильно присваиваниям: hoi := 1, dui+j :=
1, ddn+i-j := 1.
Убрать ферзь из позиции (i, j) равносильно присваиваниям: hoi := 0, dui+j := 0,
ddn+i-j := 0.
С учетом сказанного достаточно компактный рекурсивный вариант алгоритма
решения задачи 1 можно было бы задать следующими двумя функциями:
Дадим краткое описание функций queen() и qu().
Головная функция queen(n) организует обращение к рекурсивной функции qu(),
передавая ей в качестве фактических параметров:
n  размер доски;
ho, du, dd, pos  начальные значения вспомогательных векторов (с нулевыми
компонентами);
j = 0  начальное значение номера столбца текущей позиции (i, j) ферзя;
ot  вспомогательный вектор (с нулевыми компонентами), к которому
последовательно будут присоединяться найденные решения.
Возвращает функция queen() матрицу ot, столбцы которой, кроме нулевого,
содержат решения задачи. Если (i0, i1, ..., in-1)T один из таких столбцов, то
решением являются позиции ферзей: (i0, 0), (i1, 1), ..., (in-1, n-1).
Функция qu() в рекурсивном варианте реализует алгоритм “Все расстановки”.
Нелишне отметить, что рекурсия здесь осуществляется по не совсем
стандартной схеме. В каждом рекурсивном вызове глубины j делается попытка
поместить ферзь в некоторую позицию i столбца j (i, j = 0, 1, …, n-1), а сам
вызов соответствует переходу от работы с текущим столбцом к работе со
следующим столбцом. При этом в начале вычислений и при переходах к
любому последующему рекурсивному вызову параметр i меняется от нуля и
далее с шагом, равным единице, пытаясь принять значение наименьшего номера
поля, допустимого для установки ферзя. При переходах к любому
предыдущему рекурсивному вызову параметр i продолжает изменяться от
своего текущего на данном уровне значения с шагом, равным единице, также
пытаясь принять значение наименьшего номера поля, допустимого для
установки ферзя. Если в текущем столбце ферзь установить уже не удается, то
создавшуюся ситуацию (позицию) назовем тупиком. Попадание в тупик
приводит к завершению текущего рекурсивного вызова, то есть к возврату к
предыдущему столбцу и продолжению работы с ним. Иных случаев завершения
рекурсивных вызовов не существует. Поэтому базой рекурсии мы должны
считать совокупность всех тупиков. Заметим, что в данном случае элементы
базы заранее до вычислений неизвестны.
После установки ферзя в одну из строк i последнего столбца j = n-1
формируется одно из решений задачи. Вычисления прекращаются, когда мы
попадаем в тупик при работе со столбцом 0. Полученные решения задачи, если
они есть, возвращаются в виде столбцов матрицы ot, начиная от первого и
далее.
Задача 1.2. Количество расстановок. Составить рекурсивную программуфункцию, находящую количество возможных расстановок n ферзей на
шахматной доске размером nn так, чтобы они не били друг друга.
Решение. Предложенную задачу можно решать по приведенным в задаче 1
функциям, упростив их следующим образом. Вместо запоминания найденных
решений будем подсчитывать в переменной ot их количество. В этом случае
становится ненужным и вспомогательный вектор pos для формирования
очередного решения. Эта модификация функций qu() и queen() приводит нас
соответственно к функциям qun() и queennum():
Рекурсия в qun() организована точно так же, как и в функции qu().
Контрольные примеры.
Просчеты по программе queennum(n) при разных значениях n приводят к
следующим результатам:
n
Число
расстановок
1 2
3
4
1 0
0
2 92 352
2. Задача коммивояжера.
8
9
10
11
12
13
724
2680
14200
73712
Рассмотрим такую задачу. Коммивояжер (агент по сбыту), отправляясь из
своего населенного пункта, должен кратчайшим маршрутом ровно по одному
разу посетить n-1 других населенных пунктов и вернуться назад. Это
оптимизационная задача, и её различные модификации возникают не только при
доставке товаров на дом, но и в ситуациях иного характера. Математические
модели задачи коммивояжера содержат большое количество переменных и
ограничений. Поэтому для их решения общие методы линейного
программирования обычно не используются. Для небольших значений n мы
попробуем справиться с задачей коммивояжера, используя рекурсию и метод
перебора с возвратом.
Задача 2.1. Один маршрут коммивояжера. Пусть имеется n пунктов,
пронумерованных числами 0,1,…,n-1, и задана матрица U = (Ui,j) (i,j = 0,1,…,n1), где Ui,j - расстояние между i и j. Написать рекурсивную программу-функцию
нахождения одного решения задачи коммивояжера методом перебора с
возвратом, если вояж начинается из пункта с номером k{0,1,…,n}.
Решение. Пусть матрица расстояний U определена вне программы, то есть
искомые функции могут рассматривать её как глобальный параметр. Числа Ui,j и
Uj,i, вообще говоря, не обязательно должны совпадать. При отсутствии дороги
между пунктами i и j будем считать, что она есть, но бесконечной длины: Ui,j=.
Решением задачи могут служить пара функций commi(n, ne, po, ot, j) и macom(n,
k):
Головная функция macom(n, k) по заданному количеству пунктов n и номеру
пункта отправления k подготавливает фактические аргументы для рекурсивной
функции commi():
ne = (k, 0 ,…, 0, k, 0)  вектор длины n+2 для формирования маршрута и
соответствующего ему расстояния;
po  нулевой вектор меток длины n для указания уже пройденных пунктов в
формируемом маршруте;
ot = (0, 0, …, 0,  )  вектор длины n+2 для запоминания кратчайшего на
текущий момент маршрута и соответствующего ему расстояния;
j = 0  номер рекурсивного вызова.
Параметры функции commi(n, ne, po, ot, j) имеют следующий смысл:
n  количество пунктов;
ne = (k, ne1, ..., nen-1, k, nen+1)T  вектор, в котором последовательно
формируется очередной маршрут: kne1...nen-1k, а затем вычисляется
его длина nen+1;
po  вектор меток. Если пункт i в j-м рекурсивном вызове подключается к
формируемому маршруту (neji), то в векторе меток этот факт фиксируется
следующим образом: poi1. При переходе к предыдущему рекурсивному
уровню или завершению построения одного из возможных путей последний
пункт из маршрута удаляется: poi=0;
ot  вектор, в котором при otn+1>nen+1 запоминаются очередной найденный
маршрут и соответствующее ему расстояние: ot ne;
j  уровень рекурсивного вызова.
Рис. 6. Опорная схема для описания рекурсии в задаче коммивояжера
Остановимся подробнее на алгоритме, реализуемом функцией commi(), и
динамике формирования текущего маршрута ne. Прежде всего отметим, что
рекурсия организована по j  порядковому номеру (от нуля и далее)
подключаемого к маршруту пункта (см. рис.6). Пусть мы находимся в j-м
рекурсивном вызове, то есть уже сформирован путь kne0...nej-1. В каждом
вызове глубины j делается попытка нарастить текущий путь за счет i-го пункта
(i, j = 0, 1, …, n-1), а сам вызов соответствует переходу от работы с текущим
пунктом к работе со следующим пунктом. При этом в начале вычислений и при
переходах к любому последующему рекурсивному вызову параметр i меняется
от нуля и далее с шагом, равным единице, пытаясь принять значение
наименьшего номера пункта, допустимого для наращивания пути. При
переходах к любому предыдущему рекурсивному вызову параметр i
продолжает изменяться от своего текущего на данном уровне значения с шагом,
равным единице, также пытаясь принять значение наименьшего номера пункта,
допустимого для наращивания пути. Если в текущем рекурсивном вызове путь
нарастить уже нельзя, то создавшуюся ситуацию (отрезок сформированного
пути) назовем тупиком. Попадание в тупик приводит к завершению текущего
рекурсивного вызова, то есть к возврату к предыдущему пункту и продолжению
работы с ним. Иных случаев завершения рекурсивных вызовов не существует.
Поэтому базой рекурсии мы должны считать совокупность всех тупиков.
Заметим, что в данном случае элементы базы заранее, до вычислений,
неизвестны.
После наращивания пути в последнем рекурсивном вызове (j = n-1) завершается
формирование одного из возможных маршрутов. Для него с помощью матрицы
расстояний подсчитывается длина пути. Если она оказывается меньше длины
ранее запомненного маршрута (otn+1>nen+1), то в векторе ot запоминается
последний маршрут и его длина (otne). Затем вычисления продолжаются в
текущем рекурсивном вызове и т.д., пока мы не попадаем в тупик при работе с
начальным пунктом k. Здесь вычисления прекращаются и решение задачи
возвращается в виде вектора ot, у которого первые n+1 компонентов задают
маршрут, а последний компонент  его длину.
Контрольный пример. Пусть рассматривается матрица расстояний U,
приведенная перед функцией commi(). Найти одно из решений задачи
коммивояжера для начального пункта 0 можно так:
macom(U,0)T = [0 2 1 4 3 6 5 0 30].
Первые восемь компонентов полученного вектора определяют маршрут коммивояжера:
02143650, а последняя компонента равна длине этого маршрута.
Замечание. Некоторого улучшения быстродействия можно добиться
незначительной модификацией функции commi(), если одновременно с
наращиванием пути подсчитывать и его текущую длину. Именно так и устроены
функции commi1() и macom1():
Задача 2.2. Все маршруты коммивояжера. Пусть имеется n пунктов,
пронумерованных числами
0,1,…,n-1, и задана матрица U = (Ui,j) (i,j = 0,1,…,n-1), где Ui,j - расстояние
между i и j. Написать рекурсивную программу-функцию нахождения всех
возможных решений задачи коммивояжера методом перебора с возвратом, если
вояж начинается из пункта с номером k{0,1,…,n}.
Решение. Функции commi2() и macom2() решают поставленную задачу. Их
отличие от функций commi1() и macom1() состоит в следующем. Параметр ot
здесь не вектор, а построчно наращиваемая матрица. К первому найденному
маршруту (нулевая строка ot) в виде последующих строк подсоединяются все
другие маршруты с той же самой длиной пути. Если найден маршрут с более
коротким путем, то матрица ot начинает формироваться заново. Рекурсия в
commi2() и commi1() устроена совершенно одинаково.
Контрольный пример. Пусть рассматривается матрица расстояний U,
приведенная перед функцией commi(). Найти все решения задачи коммивояжера
для начального пункта 0 можно так:
Каждое из пяти решений занимает в полученной матрице одну строку, в
которой первые восемь элементов  это маршрут, а последний элемент  его
длина.
3.Задача о назначениях.
Рассмотрим такую задачу. Фирме необходимо заполнить m вакантных
должностей, на которые имеются n претендентов. Каждый из них может занять
любую, но одну из предлагаемых должностей. Пусть претенденты и должности
пронумерованы соответственно последовательными числами от 0 до n-1 и от 0
до m-1 (см. рис.7). В силу многих обстоятельств (способности, образование,
опыт, коммуникабельность и т.п.) полезность каждого кандидата для фирмы
зависит от должности, на которую он будет назначен. Пусть возможный доход
фирмы за конкретный промежуток времени при принятии претендента j
(j=0,1,…,n-1) на должность i (i=0,1,…,m-1) известен и равен Ui,j. Матрицу U = ||
Ui,j || (i = 0,1,…,m-1; j = 0,1,…,n-1) назовем матрицей доходов. Если n<m, то при
любых назначениях m-n должностей останутся нераспределенными. Если n>m,
то n-m претендентов работу не получат. Определить такое назначение
работников на должности, при котором фирма будет иметь наибольший доход.
Подобное назначение называют оптимальным, а саму задачу  задачей о
назначении [53, с.219-223]. Ясно, что оптимальное решение может оказаться не
единственным.
Задача 3.1. Одно решение задачи о назначениях. Написать рекурсивную
программу-функцию, находящую одно из оптимальных решений задачи о
назначениях при n претендентах, m должностях и матрице доходов U = || Ui,j || (i
= 0,1,…,m-1; j = 0,1,…,n-1).
Решение. Пусть матрица доходов U определена вне программы, то есть
искомые функции могут считать её глобальным параметром.
Рассмотрим сначала случай nm. Решением задачи могут служить функция
assign() с рекурсией по номерам работников и программа-константа assign.
Поскольку в данной ситуации и рекурсия, и возвраты назад реализуются по той
же самой схеме, что и в предыдущих задачах, ограничимся лишь описанием
параметров функции assign() и их начальных значений, подготавливаемых
программой-константой assign. Решение задачи возвращается в виде вектора ot
= (ot0, ot1 ,..., otn-1, otn)T, где otj  номер должности для работника с номером j (j
= 0, 1, …, n-1), а otn доход от данного оптимального назначения.
Рис. 7. Опорная схема для описания рекурсии в задаче о назначениях
Головная программа assign по размеру матрицы доходов U подготавливает
фактические аргументы для рекурсивной функции assign():
n, m  количество претендентов (n) и должностей (m);
ne = (0, 0, …, 0)  нулевой вектор длины n+1 для формирования конкретного
назначения и соответствующего ему дохода;
po = (0, 0 , …, 0)  нулевой вектор меток длины n для указания в процессе
формирования назначения уже занятых должностей;
ot = (0, 0, …, 0,   )  вектор длины n+1 для запоминания оптимального на
текущий момент назначения;
j = 0  номер рекурсивного вызова.
Параметры функции assign(n, m, ne, po, ot, j) имеют следующий смысл:
n, m  количество претендентов (n) и должностей (m);
ne = (ne0, ne1 ,..., nen-1, nen)T  вектор, в котором последовательно формируется
очередное назначение: nej номер должности работника с номером j (j = 0, 1,
…, n-1), nen - доход фирмы от данного назначения;
po  вектор меток. Если должность i в j-м рекурсивном вызове назначается
работнику с номером j, то в векторе меток этот факт фиксируется следующим
образом: poi1. При переходе к предыдущему рекурсивному уровню или
завершению построения одного из возможных назначений последняя из
рассмотренных должностей высвобождается: poi=0;
ot  вектор, в котором при otn<nen запоминается очередное найденное
назначение и соответствующий ему доход: otne;
j  уровень рекурсивного вызова.
Для решения задачи о назначениях при n>m можно предложить любой из
следующих двух способов.
Ввести n-m дополнительных фиктивных должностей с номерами m+1, m+2,
…, n-1 и положить
Ui,j = -c
(i = m+1, m+2,…,n-1; j = 0,1,…,n-1)
где с>0  достаточно большое число: c>max(Ui,j). Решив вновь полученную
“квадратную” задачу, получим назначение m претендентов на реальные m
должностей.
Решить с помощью функции assign() и программы-константы assign задачу о
назначениях для матрицы U = UT. Фактически рекурсия будет организована не
по номерам претендентов, а по номерам должностей. Поэтому полученный
ответ необходимо будет интерпретировать так: otj номер работника с
номером должности j (j = 0, 1, …, m-1), nem доход фирмы от данного
назначения.
Контрольный пример. Для приведенной выше матрицы доходов U поиск какого-
либо одного из оптимальных решений задачи о назначениях проводится так:
assignT = [1 0 4 5 3 2 93]. Иными словами, получено следующее
назначение c доходом 93:
Номер
претендента:
Номер должности:
0
1
2
3
4
5
1
0
4
5
3
2
Задача 3.2. Все решения задачи о назначениях. Написать рекурсивную
программу-функцию, находящую все оптимальные решения задачи о
назначениях при n претендентах, m должностях и матрице доходов U = || Ui,j || (i
= 0,1,…,m-1; j = 0,1,…,n-1).
Решение. Пусть, как и раньше, матрица доходов U определена вне программы,
то есть искомые функции могут рассматривать её как глобальный параметр.
Пусть вначале nm.
Решением задачи могут служить рекурсивная функция assi() и программаконстанта assi, являющиеся аналогами соответствующих программ предыдущей
задачи. Более того, здесь те же самые параметры и их начальные значения. При
вычислениях по assi() в матрице ot в виде столбцов запоминаются все
найденные назначения с одним и тем же доходом. При получении назначения с
большим доходом матрица ot начинает формироваться заново.
Решение задачи о назначениях при n>m в данном случае проводится точно так
же, как и в предыдущей задаче.
Контрольный пример. Для приведенной выше матрицы доходов U поиск всех
оптимальных решений задачи о назначениях проводится так:
4. Задача о рюкзаке
Рассмотрим еще одну задачу, решаемую схемой возвратной рекурсии, для
которой заранее не известна длина получаемого решения.
Задача 4.1. Задача о рюкзаке. Имеются m предметов с номерами от 0 до m-1,
для каждого из которых известна масса в килограммах pj и стоимость cj (j =
0,1,…,m-1). Определить, какие предметы необходимо положить в рюкзак, чтобы
их общая масса не превышала Q килограммов, а общая стоимость была
максимальной.
Решение. Пусть массы и стоимости предметов заданы соответственно
элементами нулевой и первой строк матрицы PC:
Решением задачи может служить пара функций Ks(PC, Q, ne, pos, ot, j) и
Ksack(PC, Q):
Головная функция Ksack(PC, Q) по матрице PC и величине Q подготавливает
фактические аргументы для рекурсивной функции Ks():
ne = (-1, -1, …, -1, 0, 0)  вектор длины m+2 для записи частичного или
окончательного решения задачи (последовательности предметов, их общей
массы и стоимости);
pos  нулевой вектор меток длины m для указания уже отобранных для
рюкзака предметов;
ot = (0, 0, …, 0, 0, 0)  вектор длины m+2 для записи из ne текущего
частичного или окончательного решения задачи;
j = 0  номер рекурсивного вызова.
Параметры функции Ks(PC, Q, ne, pos, ot, j) имеют следующий смысл:
PC  матрица масс и стоимостей предметов;
Q  верхняя граница общей массы предметов, размещаемых в рюкзаке;
ne  вектор длины m+2 для записи номеров формируемой для решения
последовательности предметов (компоненты от 0 до m-1), а также их общей
массы (компонент m) и стоимости (компонент m+1);
pos  вектор меток. Если предмет i в j-м рекурсивном вызове подключается к
решению (neji), то в векторе меток этот факт фиксируется следующим
образом: poi1. При переходе к предыдущему рекурсивному уровню или к
рассмотрению следующего предмета единичная метка удаляется: poi=0;
ot  вектор, в который при otm+1<nem+1 из ne переписывается текущее
частичное или окончательное решение задачи (при i = otj > 0 (j = 0, 1, …, m-1)
предмет i включен в решение, при i=-1  нет);
j  уровень рекурсивного вызова.
Остановимся вкратце на алгоритме, реализуемом функцией Ks(), и динамике
формирования последовательности предметов для рюкзака. Рекурсия
организована по j  порядковому номеру (от нуля и далее) укладываемого в
рюкзак предмета (см. рис.8). В каждом вызове глубины j делается попытка
расширить частичное решение за счет i-го предмета (i, j = 0, 1, …, m-1). При
этом параметр i меняется от нуля и далее с шагом, равным единице, пытаясь
принять значение наименьшего номера предмета, допустимого для расширения
частичного решения. При переходах к любому предыдущему рекурсивному
вызову параметр i продолжает изменяться от своего текущего на данном уровне
значения с шагом, равным единице, также пытаясь принять значение
наименьшего номера предмета, допустимого для расширения частичного
решения. Если в текущем рекурсивном вызове построенное частичное решение
изменить уже нельзя, то происходит возврат к предыдущему вызову. При
возврате из уровня j=0 вычисления прекращаются, но к этому моменту решение
задачи уже получено и находится в векторе ot.
Рис. 8. Опорная схема для описания рекурсии в задаче о рюкзаке
Контрольные примеры. Пусть матрица масс предметов и их стоимостей имеет
следующий вид:
На рисунке 9 приведены результаты вычислений по функции Ksack(M, Q) при
Q=7 и Q=21 и дана их интерпретация.
Рис. 9. Интерпретация результата вычислений в задаче о рюкзаке
Замечание. Функция Ksack(), кроме решения, выдает некоторый “мусор” 
минус единицы. Они специально не удалены из решения из дидактических
соображений, ибо возвращаемый в таком виде вектор несет в себе “следы
работы алгоритма”, что, несомненно, способствует его лучшему пониманию.
Существенного ускорения вычислений можно достичь, если снабдить функцию
Ks() дополнительным параметром be и использовать его в рекурсивных вызовах
следующим образом. При начальном обращении к Ks() считать be=0. Далее,
если на некотором рекурсивном вызове в частичное решение включен предмет i,
то при переходе к следующему рекурсивному уровню необходимо положить
be=i+1. Именно это значение (а не нуль) и использовать в качестве начального
номера предмета при попытке дальнейшего расширения частичного решения.
Заметим, что в этом случае становится совершенно ненужным вектор меток pos,
в котором ранее отмечались номера подключаемых к частичному решению
предметов. Осуществление указанных преобразований функций Ks() и Ksack()
приводит к такой их модификации:
Контрольный пример. Пусть матрица масс предметов и их стоимостей имеет
следующий вид:
Тогда
В теории алгоритмов NP-полные задачи — это класс задач, лежащих в классе NP (то есть
для которых пока не найдено быстрых алгоритмов решения, но проверка того, является ли
данное решение правильным, проходит быстро), к которым сводятся все задачи класса
NP. Это означает, что если найдут быстрый алгоритм для решения любой из NP-полных
задач, любая задача из класса NP сможет быть решена быстро.
Формальное определение
Назовём языком множество слов над алфавитом Σ. Задачей здесь является определение
того, принадлежит данное слово языку или нет. Язык L1 называется сводимым (по
Карпу) к языку L2, если существует функция,
, вычислимая за
полиномиальное время, обладающая следующим свойством: f(x) принадлежит L2 тогда и
только тогда, когда x принадлежит L1. Язык L2 называется NP-трудным, если любой язык
из класса NP сводится к нему. Язык называют NP-полным, если он NP-труден и при этом
сам лежит в классе NP. Таким образом, если будет найден алгоритм, решающий хоть одну
NP-полную задачу за полиномиальное время, все NP-задачи будут лежать в классе P.
Гипотеза P ≠ NP
Равенство классов P и NP уже более 30 лет является открытой проблемой. Научное
сообщество склоняется к отрицательному решению этого вопроса — в этом случае за
полиномиальное время решать NP-полные задачи не удастся.
В теории алгоритмов классом P (от англ. polynomial) называют множество алгоритмов,
время работы которых не слишком сильно зависит от размера входных данных (не
превосходит многочлена от размера данных). Алгоритмы, принадлежащие классу P,
считаются быстрыми. Класс P включён в более широкие классы сложности алгоритмов.
Алгоритм отождествляется с детерминированной машиной Тьюринга, которая вычисляет
ответ по данному на входную ленту слову из входного алфавита Σ. Временем работы
алгоритма TM(x) при фиксированном входном слове x называется количество рабочих
тактов машины Тьюринга от начала до остановки машины. Сложностью функции
, вычисляемой некоторой машиной Тьюринга, называется функция
, зависящая от длины входного слова и равная максимуму времени работы
машины по всем входным словам фиксированной длины:
.
Если для функции f существует машина Тьюринга M такая, что CM(n) < nc для некоторого
числа c и достаточно больших n, то говорят, что она принадлежит классу P, или
полиномиальна по времени.
Согласно тезису Чёрча — Тьюринга, любой мыслимый алгоритм можно реализовать на
машине Тьюринга. Для любого языка программирования можно определить класс P
подобным образом (заменив в определении машину Тьюринга на реализацию языка
программирования). Если компилятор языка, на котором реализован алгоритм, замедляет
исполнение алгоритма полиномиально (то есть время выполнения алгоритма на машине
Тьюринга меньше некоторого многочлена от времени выполнения его на языке
программирования), то определения классов P для этого языка и для машины Тьюринга
совпадают. Код на ассемблере допускает преобразование в машину Тьюринга с
небольшим полиномиальным замедлением, а поскольку все существующие языки
допускают компиляцию в ассемблер (опять же, с полиномиальным замедлением), то
определения класса P для машин Тьюринга и для любого существующего языка
программирования совпадают.
Более узкое определение
Иногда под классом P имеют в виду более узкий класс функций, а именно класс
предикатов (функций
). В таком случае языком L, который
распознаёт данный предикат, называется множество слов, на которых предикат равен 1.
Языками класса P называются языки, для которых существуют распознающие их
предикаты класса P. Очевидно, что если языки L1 и L2 лежат в классе P, то и их
объединение, пересечение и дополнения также лежат в классе P.
Включения класса P в другие классы
Класс P является одним из самых узких классов сложности. Алгоритмы, принадлежащие
ему, принадлежат также классу NP, классу BPP (как допускающие полиномиальную
реализацию с нулевой ошибкой), классу PSPACE (т.к. зона работы на машине Тьюринга
всегда меньше времени), классу P/Poly (для доказательства этого факта используется
понятие протокола работы машины, который переделывается в булеву схему
полиномиального размера).
Уже более 30 лет остаётся нерешённой задача о равенстве классов P и NP. Если они
равны, то любую задачу из класса NP можно будет решить быстро (за полиномиальное
время). Однако научное сообщество склоняется в сторону отрицательного ответа на этот
вопрос. Кроме того, не доказана и строгость включения в более широкие классы,
например, в PSPACE, но равенство P и PSPACE выглядит на данный момент очень
сомнительно.
Примеры алгоритмов класса P
Примерами алгоритмов класса P являются стандартные алгоритмы целочисленного
сложения, умножения, деления, взятия остатка от деления, перемножения матриц,
выяснение связности графов и некоторые другие.
Задачи, для которых не найден полиномиальный
алгоритм
Существует много задач, для которых не найдено полиномиального алгоритма, но не
доказано, что его не существует. Соответственно, неизвестно, принадлежат ли такие
задачи классу P. Вот некоторые из них:
1. Задача коммивояжёра (а также все остальные NP-полные задачи).
2. Разложение на множители составного числа.
3. Дискретное логарифмирование числа a по простому модулю p (a<p, p — число
длиной n бит).
4. Задача о скрытой подгруппе с n образующими.
5. Вычисление дискретного логарифма в аддитивной группе эллиптической кривой.
Задача коммивояжёра (коммивояжёр — бродячий торговец) заключается в отыскании
самого выгодного маршрута, проходящего через указанные города хотя бы по одному
разу. В условиях задачи указываются критерий выгодности маршрута (кратчайший, самый
дешёвый, совокупный критерий и т. п.) и соответствующие матрицы расстояний,
стоимости и т. п. Как правило указывается, что маршрут должен проходить через каждый
город только один раз, в таком случае выбор осуществляется среди гамильтоновых
циклов.
Существует масса разновидностей обобщённой постановки задачи, в частности
геометрическая задача коммивояжёра (когда матрица расстояний отражает расстояния
между точками на плоскости), треугольная задача коммивояжёра (когда на матрице
стоимостей выполняется неравенство треугольника), симметричная и асимметричная
задачи коммивояжёра.
Простейшие методы решения задачи коммивояжёра: полный лексический перебор,
жадные алгоритмы (метод ближайшего соседа, метод включения ближайшего города,
метод самого дешёвого включения), метод минимального остовного дерева. На практике
применяются различные модификации более эффективных методов: метод ветвей и
границ и метод генетических алгоритмов, а так же алгоритм муравьиной колонии.
Все эффективные (сокращающие полный перебор) методы решения задачи коммивояжёра
— методы эвристические. В большинстве эвристических методов находится не самый
эффективный маршрут, а приближённое решение. Зачастую востребованы так называемые
any-time алгоритмы, то есть постепенно улучшающие некоторое текущее приближенное
решение.
Задача коммивояжёра есть NP-полная задача. Часто на ней проводят обкатку новых
подходов к эвристическому сокращению полного перебора.
Метод ветвей и границ — общий алгоритмический метод для нахождения оптимальных
решений различных задач оптимизации, особенно дискретной и комбинаторной
оптимизации. По существу, метод является комбинаторным (алгоритм перебора) с
отсевом подмножеств множества допустимых решений, не содержащих оптимальных
решений. Метод был впервые предложен Ленд и Дойг в 1960 г. для решения задач
линейного программирования.
Общее описание
Общая идея метода может быть описана на примере поиска минимума и максимума
функции f(x) на множестве допустимых значений x. Функция f и x могут быть
произвольной природы. Для метода ветвей и границ необходимы две процедуры:
ветвление и нахождение оценок (границ).
Процедура ветвления состоит в разбиении области допустимых решений на подобласти
меньших размеров. Процедуру можно рекурсивно применять к подобластям. Полученные
подобласти образуют дерево, называемое деревом поиска или деревом ветвей и границ.
Узлами этого дерева являются построенные подобласти.
Процедура нахождения оценок заключается в поиске верхних и нижних границ для
оптимального значения на подобласти допустимых решений.
В основе метода ветвей и границ лежит следующая идея (для задачи минимизации): если
нижняя граница для подобласти A дерева поиска больше, чем верхняя граница какой-либо
ранее просмотренной подобласти B, то A может быть исключена из дальнейшего
рассмотрения (правило отсева). Обычно, минимальную из полученных верхних оценок
записывают в глобальную переменную m; любой узел дерева поиска, нижняя граница
которого больше значения m, может быть исключен из дальнейшего рассмотрения.
Если нижняя граница для узла дерева совпадает с верхней границей, то это значение
является минимумом функции и достигается на соответствующей подобласти.
Реку́рсия — метод определения класса объектов или методов предварительным заданием
одного или нескольких (обычно простых) его базовых случаев или методов, а затем
заданием на их основе правила построения определяемого класса.
Другими словами, рекурсия — частичное определение объекта через себя, определение
объекта с использованием ранее определённых. Рекурсия используется, когда можно
выделить самоподобие задачи.
Related documents
Download