Задача коммивояжера, ДП по подмножествам — различия между версиями
Shersh (обсуждение | вклад) м (→Оптимизация решения) |
(→Динамическое программирование по подмножествам (по маскам)) |
||
Строка 20: | Строка 20: | ||
вершин, пронумерованных от <tex>0</tex> до <tex>N-1</tex> и каждое ребро <tex>(i, j) \in E </tex> имеет некоторый вес <tex> w(i,j)</tex>. Необходимо найти гамильтонов цикл, сумма весов по ребрам которого минимальна. | вершин, пронумерованных от <tex>0</tex> до <tex>N-1</tex> и каждое ребро <tex>(i, j) \in E </tex> имеет некоторый вес <tex> w(i,j)</tex>. Необходимо найти гамильтонов цикл, сумма весов по ребрам которого минимальна. | ||
− | + | [[ Гамильтоновы графы | Подробнее можно прочитать в этой статье.]] | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
==== Оптимизация решения ==== | ==== Оптимизация решения ==== |
Версия 20:01, 9 января 2016
Задача: |
Задача о коммивояжере (англ. Travelling salesman problem, TSP) — задача, в которой коммивояжер должен посетить | городов, побывав в каждом из них ровно по одному разу и завершив путешествие в том городе, с которого он начал. В какой последовательности ему нужно обходить города, чтобы общая длина его пути была наименьшей?
Содержание
Варианты решения
NP-полнота задач о гамильтоновом цикле и пути в графах
Так вот задача о коммивояжере относится к классу NP-полных задач. Поэтому, рассмотрим два варианта решения с экспоненциальным временем работы.
Перебор перестановок
Можно решить задачу перебором всевозможных перестановок. Для этого нужно сгенерировать все
всевозможных перестановок вершин исходного графа, подсчитать для каждой перестановки длину маршрута и выбрать минимальный из них. Но тогда задача оказывается неосуществимой даже для достаточно небольших . Сложность алгоритма .Динамическое программирование по подмножествам (по маскам)
Задача о коммивояжере представляет собой поиск кратчайшего гамильтонова цикла в графе.
Смоделируем данную задачу при помощи графа. При этом вершинам будут соответствовать города, а ребрам — дороги. Пусть в графе
вершин, пронумерованных от до и каждое ребро имеет некоторый вес . Необходимо найти гамильтонов цикл, сумма весов по ребрам которого минимальна.Подробнее можно прочитать в этой статье.
Оптимизация решения
Пусть
содержит булево значение — существует ли в подмножества гамильтонов путь, заканчивающийся в вершине .Сама динамика будет такая:
Это решение требует
памяти и времени. Эту оценку можно улучшить, если изменить динамику следующим образом.Пусть теперь
хранит маску подмножества всех вершин, для которых существует гамильтонов путь в подмножестве , заканчивающихся в этой вершине. Другими словами, сожмем предыдущую динамику: будет равно . Для графа выпишем масок , для каждой вершины задающие множество вершин, которые связаны ребром в данной вершиной. То есть .Тогда динамика перепишется следующим образом:
Особое внимание следует уделить выражению
. Первая часть выражения содержит подмножество вершин, для которых существует гамильтонов путь, заканчивающихся в соответствующих вершинах в подмножестве без вершины , а вторая — подмножество вершин, связанных с ребром. Если эти множества пересекаются хотя бы по одной вершине (их не равен ), то, как нетрудно понять, в существует гамильтонов путь, заканчивающийся в вершине .Окончательная проверка состоит в сравнении
c .Это решение использует
памяти и имеет асимптотику .Реализация
Прежде чем писать код, скажем пару слов о порядке обхода состояний. Обозначим за
количество единиц в маске (иначе говоря количество пройденных вершин не считая текущей). Тогда, поскольку при рассмотрении состояния мы смотрим на состояния, и , то состояния с большим должны быть посещены позже, чтобы к моменту вычисления текущего состояния были вычислены все те, которые используются для его подсчёта. Однако если использовать рекурсию, об этом можно не беспокоиться (и сэкономить немало кода, времени и памяти).
//Все переменные используются из описания алгоритма, function findCheapest(i, mask): if d[i][mask] != = бесконечность return d[i][mask] for j = 0 .. n - 1 if w(i, j) существует and j-ый бит mask == 1 d[i][mask] = min(d[i][mask], findCheapest(j, mask - 2 ** j) + w(i, j)) return d[i][mask] for i = 0 .. n - 1 for mask = 0 .. 2 ** n - 1 d[i][mask] = d[0][0] = 0; ans = findCheapest(0, 2 ** n - 1) if ans == exit
Дальше ищем сам путь:
i = 0 mask = 2 ** n - 1 path.push(0) while mask != 0 for j = 0 .. n - 1 if w(i, j) существует and j-ый бит mask == 1 and d[i][mask] == d[j][mask - 2 ** j] + w(i, j) path.push(j) i = j mask = mask - 2 ** j continue
См. также
- Кратчайший путь в ациклическом графе
- Задача о наибольшей общей подпоследовательности
- Задача о наибольшей возрастающей подпоследовательности
- Задача о рюкзаке
- Алгоритм нахождения Гамильтонова цикла в условиях теорем Дирака и Оре
- Гамильтоновы графы
Источники информации
- Романовский И. В. Дискретный анализ. СПб.: Невский Диалект; БХВ-Петербург, 2003. ISBN 5-7940-0114-3
- Кормен Т., Лейзерсон Ч., Ривест Р., Штайн К. Алгоритмы: построение и анализ, 2-е издание. М.: Издательский дом "Вильямс", 2005. ISBN 5-8459-0857-4