# теория · шаг 3 из 11
Гамильтонов цикл и коммивояжёр: трудность порядка
Коротко
Гамильтонов цикл проходит через каждую вершину графа ровно один раз и возвращается в начало. Есть ли
такой цикл - NP-полная задача (к ней сводится 3-SAT): порядков обхода (n − 1)! (начало цикла закреплено), а простого признака,
по которому граф можно было бы проверить, не найдено. Задача коммивояжёра - найти самый короткий обход
всех городов - не легче: если дать рёбрам графа длину 1, а отсутствующим рёбрам длину 2, то обход длины
n существует ровно тогда, когда в графе есть гамильтонов цикл. Это сведение показывает, как в задачу о
порядке добавляются числа. Полный перебор маршрутов справляется с дюжиной городов, таблица по
подмножествам - с двумя десятками; дальше начинаются методы, которые не обещают либо точности, либо скорости.
Если ты определишь гамильтонов цикл, сведёшь его к задаче коммивояжёра и объяснишь, почему отсутствие цикла труднее показать, чем его наличие, - листай до «Теперь сам».
Как думаешь, сколько порядков у двадцати адресов
Курьер выезжает со склада, должен побывать по двадцати адресам и вернуться. Адреса можно объезжать в любом порядке.
Ответь себе до чтения: сколько существует разных маршрутов? Первый адрес выбирается двадцатью способами,
второй - девятнадцатью, и так далее: 20!, около 2,4 · 10¹⁸; если не различать направление обхода -
вдвое меньше. Склад - закреплённое начало, поэтому для двадцати одной точки порядков 20!, а не 21!.
При миллиарде маршрутов в секунду на перебор этой половины ушло бы почти сорок лет. Тридцать
адресов - уже 10³². И это задача, которую логистическая компания решает каждое утро для каждой машины.
На что это похоже
Экскурсия по музею, в которой нужно побывать в каждом зале ровно один раз и выйти там же, где вошёл. Из зала в зал можно попасть только через дверь. Экскурсовод с планом здания в руках не может сказать сразу, возможен ли такой маршрут: приходится пробовать.
И здесь та же несимметричность, что у всех задач из NP. Нашёл маршрут - показал его, и все убедились.
Не нашёл - остаётся вопрос, плохо искал или маршрута нет.
Механизм: цикл, обход и сведение одного к другому
Гамильтонов цикл в графе - цикл, который проходит через каждую вершину ровно один раз. Задача: есть
ли он. Она лежит в NP - сертификат - порядок вершин - и NP-полна: к ней сводится 3-SAT. Сведение
громоздкое, с отдельным устройством на каждую переменную и скобку, и здесь не приводится.
Необходимые условия просты: граф связен, у каждой вершины не меньше двух соседей, удаление одной вершины
не разрывает граф. Достаточные условия тоже есть: например, если вершин n не меньше трёх и у каждой не
меньше n / 2 соседей, цикл существует. Но между «необходимо» и «достаточно» лежит широкая полоса
графов, для которых ни один простой признак ответа не даёт.
Задача коммивояжёра: даны n городов и расстояния между каждыми двумя; найти самый короткий маршрут,
проходящий через все города по одному разу и возвращающийся в исходный. Вариант с ответом «да» или
«нет»: есть ли маршрут длиной не больше B.
Сведение гамильтонова цикла к коммивояжёру. По графу G с n вершинами, где n не меньше трёх
(при двух вершинах маршрут «туда и обратно» имел бы длину 2 без всякого цикла), строим города и расстояния:
города вершины графа
расстояние 1, если вершины соединены ребром; 2, если не соединены
порог B n
Маршрут через n городов состоит из n переездов, каждый длиной 1 или 2. Его длина равна n тогда и
только тогда, когда все переезды имеют длину 1, то есть идут по рёбрам графа, - а это и есть гамильтонов
цикл. Если цикла нет, в любом маршруте есть хотя бы один переезд длины 2, и длина не меньше n + 1.
Перевод занимает n² действий. Вместо двойки подошло бы любое число больше единицы.
В NP задача лежит: сертификат - сам маршрут, его длину легко сложить. Значит, коммивояжёр с порогом
NP-полон. Задача на оптимум - найти сам кратчайший маршрут - не легче:
зная кратчайший маршрут, можно ответить на вопрос о любом пороге.
Что известно о точных методах:
полный перебор (n − 1)! до 12-13 городов
таблица по подмножествам 2ⁿ · n² до 20-25 городов
перебор с оценками в худшем случае (n − 1)!,
на практике - сотни и тысячи городов
Последняя строка не противоречит NP-полноте: гарантии нет, но на входах из практики хорошие оценки отсекают почти всё.
Правило
Гамильтонов цикл проходит через каждую вершину ровно один раз; задача о его существовании NP-полна. Она
сводится к задаче коммивояжёра: расстояние 1 по рёбрам, 2 между несоединёнными вершинами, порог n.
Поэтому коммивояжёр с порогом NP-полон, а поиск кратчайшего маршрута не легче.
Разбор: два графа на десяти вершинах
Перебор с возвратом: начинаем с вершины 0, наращиваем путь по рёбрам в ещё не посещённые вершины; когда в пути все вершины, проверяем, есть ли ребро обратно в начало. Начинать с одной вершины достаточно: цикл через все вершины проходит и через нулевую. Счётчик считает вызовы функции.
Оба графа состоят из двух пятиугольников, внешнего и внутреннего, и пяти спиц между ними. Разница в том,
как соединены вершины внутреннего пятиугольника: по кругу или звездой, через одну. Внешние вершины - от 0
до 4, внутренние - от 5 до 9, спица соединяет вершину i с вершиной i + 5.
def prodolzhit(sosedi, put, v_puti, schyot):
schyot[0] = schyot[0] + 1
if len(put) == len(sosedi):
return put[0] in sosedi[put[-1]]
for u in sosedi[put[-1]]:
if not v_puti[u]:
v_puti[u] = True
put.append(u)
if prodolzhit(sosedi, put, v_puti, schyot):
return True
put.pop()
v_puti[u] = False
return False
def cikl(n, ryobra):
sosedi = []
for v in range(n):
sosedi.append([])
for r in ryobra:
sosedi[r[0]].append(r[1])
sosedi[r[1]].append(r[0])
v_puti = [False] * n
v_puti[0] = True
put = [0]
schyot = [0]
if prodolzhit(sosedi, put, v_puti, schyot):
return [put, schyot[0]]
return ["нет", schyot[0]]
prizma = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 0],
[5, 6], [6, 7], [7, 8], [8, 9], [9, 5]]
zvezda = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 0],
[5, 7], [7, 9], [9, 6], [6, 8], [8, 5]]
spicy = [[0, 5], [1, 6], [2, 7], [3, 8], [4, 9]]
print(cikl(10, prizma + spicy))
print(cikl(10, zvezda + spicy))
[[0, 1, 2, 3, 4, 9, 8, 7, 6, 5], 10]
['нет', 274]
В первом графе цикл нашёлся за десять вызовов, без единого возврата: обойти внешний пятиугольник, спуститься по спице, обойти внутренний в обратную сторону, подняться по спице в начало.
Второй граф известен как граф Петерсена. У него те же десять вершин и пятнадцать рёбер, у каждой вершины три соседа, все необходимые условия выполнены - а гамильтонова цикла нет. Перебор сделал 274 вызова и исчерпал все пути из вершины 0. Ответ «нет» здесь - не «не нашёл», а «проверил всё»; но проверить всё удалось только потому, что граф мал. Короткого сертификата отсутствия, который проверялся бы быстрее перебора, в общем случае не известно.
Разбор: тот же вопрос, заданный расстояниями
Функция svesti строит таблицу расстояний по графу, как в сведении (на диагонали остаётся 2: расстояние
от города до самого себя программа не использует). Функция luchshij_obhod перебирает
все маршруты из города 0 и возвращает длину кратчайшего и число вызовов. Начальный рекорд n * 10 -
просто число больше любого маршрута; аргументы idti - где стоим, сколько городов посетили и пройденная
длина.
def luchshij_obhod(ves):
n = len(ves)
rekord = [n * 10]
v_puti = [False] * n
v_puti[0] = True
schyot = [0]
idti(ves, 0, 1, 0, v_puti, rekord, schyot)
return [rekord[0], schyot[0]]
def idti(ves, gde, posetili, dlina, v_puti, rekord, schyot):
schyot[0] = schyot[0] + 1
if posetili == len(ves):
if dlina + ves[gde][0] < rekord[0]:
rekord[0] = dlina + ves[gde][0]
return
for u in range(len(ves)):
if not v_puti[u]:
v_puti[u] = True
idti(ves, u, posetili + 1, dlina + ves[gde][u],
v_puti, rekord, schyot)
v_puti[u] = False
def svesti(n, ryobra):
ves = []
for a in range(n):
ves.append([2] * n)
for r in ryobra:
ves[r[0]][r[1]] = 1
ves[r[1]][r[0]] = 1
return ves
s_ciklom = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5],
[5, 6], [6, 0], [0, 3], [2, 5]]
bez_cikla = []
for a in [0, 1, 2]:
for b in [3, 4, 5, 6]:
bez_cikla.append([a, b])
print(luchshij_obhod(svesti(7, s_ciklom)))
print(luchshij_obhod(svesti(7, bez_cikla)))
[7, 1957]
[8, 1957]
Первый граф - семиугольник с двумя хордами; гамильтонов цикл в нём есть, и кратчайший обход имеет длину 7, по единице на переезд. Второй граф двудольный: вершины 0, 1, 2 соединены только с вершинами 3, 4, 5, 6. Цикл через все вершины в нём невозможен - он должен чередовать доли, а в долях три и четыре вершины. Кратчайший обход - 8: один переезд пришлось сделать не по ребру.
Оба запуска сделали по 1957 вызовов: 1 + 6 + 30 + 120 + 360 + 720 + 720 - число путей всех длин
из города 0. Перебор расстояний не обращает внимания на то, что перед ним: он проходит всё. Для семи
городов это ничто, для двадцати адресов и склада - десятки лет, как в начале урока.
Что ломается без этого
Гамильтонов цикл путают с эйлеровым. Пройти по каждому ребру один раз - лёгкая задача с простым признаком. Пройти через каждую вершину - NP-полная. Этой паре посвящён урок о лёгком рядом с труднымРазбирается в разделе 21, глава «Каталог NP-полных задач и как им пользоваться»: Лёгкое рядом с трудным: пары задач, которые легко перепутать.
«Едем каждый раз в ближайший непосещённый город». Жадный маршрут может оказаться намного длиннее наилучшего: последние переезды приходится делать через всю карту.
Таблицу по подмножествам считают спасением. 2ⁿ · n² при n = 30 - около 10¹² действий и
гигабайты памяти; при n = 40 - 10¹⁵. Она отодвигает стену с тринадцати городов до двадцати пяти.
«Маршрут оптимален» принимают без проверки. Проверить, что маршрут проходит все города и имеет данную длину, легко. Проверить, что короче нет, - задача того же порядка трудности, что и поиск.
Зачем это в работе
Маршруты: доставка, сбор заказов на складе, обход точек контроля, движение сверла над платой. Везде нужен порядок посещения, и везде это коммивояжёр с дополнениями - окнами времени, грузоподъёмностью, несколькими машинами.
Порядок операций. Переналадка станка между изделиями стоит по-разному для разных пар; очерёдность изделий с наименьшей суммарной переналадкой - тот же маршрут, только «города» - изделия; если возвращать станок к первому изделию не нужно, это задача о пути без возврата, и она так же трудна.
Трезвая оценка. NP-полнота не помешала найти доказанно кратчайшие маршруты для десятков тысяч городов: перебор с очень хорошими оценками. Но такие результаты - месяцы вычислений над одним входом; для ежедневной работы берут методы, дающие маршрут на несколько процентов длиннее наилучшего за секунды.
Теперь сам
- Сколько разных замкнутых маршрутов через 6 городов, если начало закреплено, а направление не важно?
- В графе есть вершина с одним соседом. Может ли в нём быть гамильтонов цикл?
- Граф с 12 вершинами сведён к задаче коммивояжёра. Кратчайший обход имеет длину 14. Что это значит?
- Почему в двудольном графе с долями из 3 и 4 вершин нет гамильтонова цикла?
- Что служит сертификатом в задаче «есть ли маршрут не длиннее
B»?
Ответ. 1. 5! / 2 = 60.
-
Нет: в цикл вершина входит по одному ребру и выходит по другому, нужно не меньше двух соседей.
-
Гамильтонова цикла в графе нет; любой обход всех вершин делает не меньше двух переездов не по рёбрам.
-
Цикл чередует вершины двух долей, поэтому в долях должно быть поровну вершин.
-
Сам маршрут - порядок городов. Проверка: каждый город один раз, сумма расстояний не больше
B.
Главное
Задачи о порядке трудны не числом порядков - у сортировки их столько же, - а тем, что по локальным свойствам графа нельзя понять, сложатся ли они в целое. Сведение гамильтонова цикла к коммивояжёру показывает типичный ход: структурный запрет «здесь нет ребра» превращается в число «здесь дорого», а вопрос «существует ли» - в вопрос «уложимся ли в порог».
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий