1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8

# теория · шаг 5 из 8

Эйлеров цикл: пройти каждое ребро ровно один раз

Коротко

Эйлеров цикл проходит по каждому РЕБРУ графа ровно один раз и возвращается в начало. Существует он, когда все вершины с рёбрами связаны и у каждой вершины ЧЁТНАЯ степень (в ориентированном графе - входящих рёбер столько же, сколько исходящих). Причина простая: каждый проход через вершину тратит два ребра - одно на вход, одно на выход. Эйлеров путь с разными концами существует, когда нечётных вершин ровно две - они и есть концы. Строит цикл алгоритм Хирхольцера за O(V + E): идти по неиспользованным рёбрам, пока не упрёшься, а упёршись - отступать по стеку и выписывать вершины; застрявший обход сам вклеивает пропущенные петли.

Если ты знаешь условие существования эйлерова цикла и умеешь строить его стеком за O(V + E), листай до «Теперь сам».

Как думаешь, когда можно обойти все улицы ровно по разу

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

Прежде чем читать дальше, попробуй угадать признак по картинке: перекрёсток, где сходятся три улицы. Машина въехала по одной, выехала по другой. Что будет с третьей? Если ответил «на неё придётся заехать ещё раз и выехать по уже пройденной» - ты нашёл условие целиком.

На что это похоже

Рисунок «не отрывая карандаша от бумаги». Конверт с крестом внутри нарисовать одним росчерком нельзя, а «домик» без нижней стороны - можно, но только начав из определённого угла. Каждая точка, через которую карандаш проходит насквозь, получает два штриха: вход и выход. Значит, у точки, через которую только проходят, число штрихов чётно. Нечётным оно может быть только там, где росчерк начинается или кончается.

Механизм: условие и алгоритм Хирхольцера

УСЛОВИЕ (неориентированный граф, все вершины с рёбрами связаны)
    все степени чётны              → эйлеров цикл есть
    ровно две нечётные             → эйлеров путь между ними
    нечётных больше двух           → ни цикла, ни пути
ориентированный: вход = выход у всех → цикл
    (число нечётных вершин всегда чётно: сумма степеней = 2·E)

ХИРХОЛЬЦЕР
стек = [старт];  ответ = []
пока стек не пуст:
    v = вершина на вершине стека
    если у v есть неиспользованное ребро v–u:
        пометить ребро, положить u в стек
    иначе:
        снять v со стека и дописать в ответ
ответ, прочитанный задом наперёд, - эйлеров цикл

Почему это работает. При чётных степенях обход, вышедший из старта, может застрять только в самом старте: в любую другую вершину он вошёл, и раз ребро входа нечётное по счёту, выход ещё есть. Застряв, алгоритм отступает по стеку до вершины, у которой остались рёбра, и оттуда начинает новый замкнутый обход - он вклеится в ответ ровно на месте этой вершины. Каждое ребро кладётся в стек один раз и снимается один раз: O(V + E), если держать для каждой вершины указатель на следующее непросмотренное ребро.

Правило

Эйлеров цикл существует, когда все вершины с рёбрами связаны и все степени чётны (в ориентированном графе - вход равен выходу у каждой вершины); эйлеров путь - когда нечётных вершин ровно две, и начинать надо в одной из них. Алгоритм Хирхольцера идёт по неиспользованным рёбрам, складывая вершины в стек, а упёршись - снимает вершину в ответ; ответ задом наперёд - цикл. Время O(V + E).

Разбор: два треугольника с общей вершиной

рёбра: 1–2, 2–3, 3–1, 3–4, 4–5, 5–3
степени: 1:2  2:2  3:4  4:2  5:2 - все чётные, граф связен

стек 1                  берём 1–2
стек 1 2                берём 2–3
стек 1 2 3              берём 3–1
стек 1 2 3 1            у 1 рёбер нет  → ответ: 1
стек 1 2 3              у 3 есть 3–4   → берём
стек 1 2 3 4            берём 4–5
стек 1 2 3 4 5          берём 5–3
стек 1 2 3 4 5 3        рёбер нет      → ответ: 1 3
                        дальше рёбер нет ни у кого: снимаем всё
ответ: 1 3 5 4 3 2 1    задом наперёд: 1 2 3 4 5 3 1

Первый обход 1 → 2 → 3 → 1 вернулся в старт и застрял, пропустив петлю 3 → 4 → 5 → 3. Отступая, алгоритм нашёл в вершине 3 неиспользованное ребро и прошёл петлю - а в ответе она оказалась вставленной ровно между 2 → 3 и 3 → 1. Никакой отдельной «склейки» в коде нет: её делает стек.

Разбор: кодовый замок и последовательность де Брёйна

Кодовый замок принимает четырёхзначный двоичный код и проверяет ПОСЛЕДНИЕ четыре нажатия. Сколько нажатий нужно, чтобы перебрать все 16 кодов? Если вводить их по отдельности - 64. Но коды могут перекрываться: строка, в которой каждое из 2^k слов длины k встречается подряд ровно один раз, называется последовательностью де Брёйна, и строится она эйлеровым циклом.

вершины - слова длины k − 1; из вершины w два ребра:
    w → (w без первой цифры) + 0     и     w → (w без первой цифры) + 1
ребро = слово длины k; у каждой вершины 2 входа и 2 выхода → цикл есть
пройти все рёбра по разу = выписать все k-значные слова, перекрывая их
ejler.py
def ejlerov_cikl(sosedi, start):     # Хирхольцер, ориентированный граф
    sled = {v: 0 for v in sosedi}    # сколько рёбер вершины уже взято
    stek, cikl, shagi = [start], [], 0
    while stek:
        v = stek[-1]
        if sled[v] < len(sosedi[v]):  # есть неиспользованное ребро
            u = sosedi[v][sled[v]]
            sled[v] += 1
            stek.append(u)
            shagi += 1
        else:                         # тупик - вершина в ответ
            cikl.append(stek.pop())
    return cikl[::-1], shagi


def de_brejn(k):                      # каждое k-значное двоичное слово
    m = 2 ** (k - 1)                  # вершины - слова длины k − 1
    sosedi = {v: [(2 * v + b) % m for b in (0, 1)] for v in range(m)}
    cikl, shagi = ejlerov_cikl(sosedi, 0)
    # ребро v → u дописывает последнюю цифру u
    stroka = "0" * (k - 1) + "".join(str(u % 2) for u in cikl[1:])
    return stroka, shagi


s, shagi = de_brejn(4)
print("k=4:", s, "длина", len(s))
for k in (4, 10, 16):
    s, shagi = de_brejn(k)
    vse = {s[i:i + k] for i in range(len(s) - k + 1)}
    print(f"k={k}: рёбер {shagi}, разных слов {len(vse)}"
          f" из {2 ** k}, длина {len(s)}")
↳  выводpython3 ejler.py
k=4: 0000100110101111000 длина 19
k=4: рёбер 16, разных слов 16 из 16, длина 19
k=10: рёбер 1024, разных слов 1024 из 1024, длина 1033
k=16: рёбер 65536, разных слов 65536 из 65536, длина 65551

19 нажатий вместо 64 - и это минимум: строка длины L содержит L − k + 1 слов подряд, а слов нужно 16. Для шестнадцатизначного кода - 65551 нажатие вместо миллиона с лишним. Число пройденных рёбер ровно равно числу рёбер графа: каждое ребро взято один раз, работа линейна.

Что ломается без этого

Первая ошибка - проверить чётность степеней и забыть связность: два отдельных треугольника имеют все степени по два, а единого цикла нет. Вторая - для пути начинать не с нечётной вершины: обход застрянет во второй нечётной, оставив рёбра непройденными. Третья - строить цикл «жадно» (идти, пока идётся, и считать результат ответом): первый замкнутый обход может пропустить петли, как 3 → 4 → 5 → 3 в разборе, и без стека их не вклеить. Четвёртая - на каждом шаге искать неиспользованное ребро с начала списка соседей: на вершине высокой степени это квадрат; нужен указатель «следующее непросмотренное ребро» для каждой вершины.

Зачем это в работе

Маршруты по рёбрам: снегоуборка, полив улиц, сбор мусора, обход линий электропередачи, проверка всех переходов в схеме состояний программы - везде, где «посетить» нужно связь, а не точку. Если у графа есть нечётные вершины, это задача китайского почтальона: какие улицы пройти дважды, чтобы лишний путь был минимальным. Когда нечётных вершин две - дублируют кратчайший путь между ними (о кратчайших путях - следующий раздел); в общем случае нечётные вершины разбивают на пары паросочетанием минимальной стоимости. Последовательности де Брёйна сидят в сборке генома из коротких прочтений (там граф строят из перекрывающихся кусков ДНК), в генераторах тестов и в позиционных кодах для роботов.

Теперь сам

Граф: рёбра A–B, B–C, C–A, C–D, D–B. Есть ли эйлеров цикл? Эйлеров путь? Если путь есть - откуда начинать и какой он?

Ответ. Степени: A:2, B:3, C:3, D:2. Нечётных ровно две - B и C, значит цикла нет, а путь есть, с концами в B и C. Например: B → A → C → B → D → C - рёбра B–A, A–C, C–B, B–D, D–C, каждое по разу. Начав из A, путь не построить: обход застрянет, оставив рёбра.

Главное

Эйлеров цикл проходит каждое ребро ровно раз; он есть, когда граф с рёбрами связен и все степени чётны, путь - когда нечётных вершин ровно две. Причина - каждый проход через вершину тратит два ребра. Алгоритм Хирхольцера строит цикл стеком за O(V + E), сам вклеивая пропущенные петли; на графе де Брёйна он даёт строку, перебирающую все 2^k кодов за 2^k + k − 1 нажатий.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.