Блог пользователя dmkz

Автор dmkz, история, 6 лет назад, По-русски

Понимание того, как простым конструктивом построить гамильтонов цикл в строго связном турнире, основываясь на его свойствах, было критично на региональном этапе всероссийской олимпиады школьников по информатике в сезоне 2014/2015 для получения полного балла по задаче Магические порталы.

Здесь небольшой оффтоп

Пусть дан ориентированный граф, в котором между любыми двумя вершинами есть направленная дуга, а также для любой пары вершин u и v можно дойти из u в v по ориентированным дугам, а затем успешно вернуться также по ориентированным дугам обратно ( строго связный турнир ). Пример такого графа:

Турнир

В таком графе есть гамильтонов путь — путь, проходящий по всем вершинам (например: ), и гамильтонов цикл — гамильтонов путь, в котором из конца есть дуга в начало (например: ). Рассмотрим алгоритм нахождения и того, и того за O(n2).

Гамильтонов путь в строго связном турнире

Удобнее всего строить по индукции. Пусть уже есть путь из k вершин. Надо добавить к нему новую вершину uk + 1. Делаем следующее:

  1. Находим максимальный номер i такой, что из любой вершины u1, u2, ..., ui ведет ребро в uk + 1.

  2. Вставляем новую вершину после ui, получая путь:

Здесь активно используется тот факт, что граф у нас — строго связный турнир, а это значит, что если из u в v нет дуги, то точно есть дуга из v в u.

Начиная с пустого пути, добавляем в него все вершины по очереди, каждая операция вставки требует O(n) действий, всего нужно вставить n вершин, следовательно, спустя O(n2) действий получим гамильтонов путь.

Можно сделать это за — подробнее в прикрепленных источниках.

Гамильтонов цикл в строго связном турнире

Стратегия следующая: раз мы уже умеем находить гамильтонов путь, то сперва найдем его за O(n2), а затем уже преобразуем в гамильтонов цикл за O(n2) действий (можно сделать это за O(n) — подробнее в прикрепленных источниках).

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

Пойдем по всем вершинам пути u3, u4, ..., uk до тех пор, пока есть ребро из uk в u1. Если дошли до конца пути, то цикл найден — это весь путь, иначе есть некий цикл и остаток пути . Далее будем по индукции расширять цикл, добавляя вершины из пути. Рассмотрим два случая:

  1. Если в цикле есть вершина, в которую идет ребро из добавляемой вершины uk + 1, то находим первую такую вершину ui. Теперь мы можем составить новый цикл , а путь будет выглядеть как .

  2. Если в цикле не нашлось вершины, в которую бы шло ребро из добавляемой вершины uk + 1, пройдем по остальным вершинам пути, пока не найдем вершину uj, для которой такая вершина ui все же найдется (такая вершина должна быть, иначе этот граф не строго связный турнир, так как мы нашли способ его разбиения на две компоненты строгой связности). Тогда все вершины из начала пути до uj добавляем в цикл, перестраивая его следующим образом: . От пути либо ничего не остается, либо остается .

На каждой итерации мы увеличиваем длину цикла как минимум на 1, выполняя при этом O(n) операций. Итоговая асимптотика алгоритма O(n2).

Что почитать?

В приведенных источниках есть способ нахождения гамильтонова пути и цикла за со структурой данных Semi-Heap, который можно применять на строго связных турнирах, состоящих из порядка 200.000 вершин, а направление ребер в которых определяется некоторой функцией f(u, v) от номеров вершин:

  1. On Finding a Hamiltonian Path in Tournament Using Semi-Heap (Part 1: Sequential Solution) — Jie Wu

  2. A linear-time algorithm for finding Hamiltonian cycles in tournaments — Y.Manoussakis

  • Проголосовать: нравится
  • +38
  • Проголосовать: не нравится

»
4 года назад, # |
  Проголосовать: нравится +8 Проголосовать: не нравится

Найти гамильтонов путь в турнире можно обычной сортировкой слиянием (aka merge sort, aka stable_sort в C++), где a < b, если есть ребро a → b.
Это будет работать, поскольку начинаем мы с массивов длины 1, которые, очевидно, корректно отсортированы. На каждом же слиянии результат слияния будет состоять из непрерывных чередующихся отрезков из первого и второго массивов. Внутри самих отрезков все неравенства выполняются, а на границе отрезков из разных массивов мы спросили у графа, какой элемент поставить первым, опираясь на ориентацию ребра, поэтому результат слияния тоже корректен.

А еще не понятно доказательство существования гамильтонова пути в турнире. Кажется можно делать так (та же индукция): добавим в путь u1, u2, ..., uk вершину uk+1. Для этого найдем наименьший i, такой что uk+1 < ui, тогда, очевидно ui-1 < uk+1 (иначе i-1 был бы меньшим номером), если i-1, конечно, существует, ну а тогда можно разместить uk+1 перед ui: u1, u2, ..., ui-1, uk+1, ui, ..., uk. Если же нет подходящего i, то просто поставим uk+1 в конец.
Или же можно искать наибольший i, такой что ui < uk+1, а если такого нет, то ставить uk+1 в начало нового пути.

»
4 недели назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Забыли два момента в поиске цикла в строго связном турнире:

1) во время перестроения цикла очень много неприятных граничных случаев может быть, поэтому самое простое что можно сделать, чтобы не учитывать их все — делать сдвиг элементов уже найденного цикла так, чтобы было ребро из $$$u_1$$$ в $$$u_{k+1}$$$ (оно точно есть).

2) когда изначально строим цикл у вершины $$$u_3$$$ может не быть ребра в $$$u_1$$$))), но мы можем найти первую такую вершину $$$u_l$$$, такую что есть ребро из $$$u_l$$$ в $$$u_1$$$ (это будет цикл) и дальше делать вышеописанный алгоритм не с $$$u_3$$$, а с $$$u_l$$$