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

# теория · шаг 3 из 7

Рёберная раскраска: расписание уроков, Кёниг и Визинг

Коротко

Рёберная раскраска - цвета РЁБРАМ так, чтобы у рёбер с общей вершиной цвета различались. Это расписание: ребро «учитель - класс» - урок, цвет - время, и ни учитель, ни класс не могут быть на двух уроках сразу. Цветов нужно не меньше Δ - наибольшей степени: у самой загруженной вершины Δ рёбер, и все разного цвета. Теорема Кёнига: в двудольном графе ровно Δ хватает всегда - их даёт перекраска вдоль чередующегося пути из двух цветов. Теорема Визинга: в любом простом графе хватает Δ + 1. Жадная раскраска может потратить до 2Δ − 1, и на расписании 30 учителей и 30 классов она взяла лишний слот.

Если ты знаешь, почему двудольному графу хватает Δ цветов на рёбрах, умеешь перекрашивать чередующийся путь и помнишь границу Визинга, листай до «Теперь сам».

Как думаешь, сколько слотов нужно расписанию

В школе 30 учителей и 30 классов, всего 320 уроков «учитель ведёт в классе». Самый загруженный учитель (или класс) участвует в 15 уроках. Значит, слотов в неделе нужно не меньше 15.

Прежде чем читать дальше, ответь себе: а хватит ли ровно 15? Или неудачное сочетание - этот учитель занят тогда, когда свободен этот класс - заставит добавить слот? В прошлом уроке раскраска ВЕРШИН оказалась трудной уже для трёх цветов. С рёбрами двудольного графа всё иначе.

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

Турнир по шахматам между двумя командами, где каждый играет с некоторыми из чужой команды. Тур - это набор партий, в котором никто не играет дважды. Если у лучшего игрока 15 соперников, туров не меньше 15. Организатор, раздающий партии по турам по порядку, рано или поздно упрётся: пара свободна в разные туры, общего свободного тура нет. Опытный организатор не добавляет тур, а ПЕРЕСТАВЛЯЕТ: меняет местами два тура у цепочки игроков, освобождая нужный тур у нужного человека.

Механизм: свободные цвета и чередующийся путь

у каждой вершины степени < Δ всегда есть свободный цвет из Δ

красим ребро u–v:
    a = цвет, свободный у u;   b = цвет, свободный у v
    если a свободен и у v → красим в a, готово
    иначе: из v идёт путь из рёбер цветов a, b, a, b, ...
           (у каждой вершины не больше одного ребра каждого цвета -
            путь однозначен)
           меняем на нём a ↔ b → a освободился у v
           красим u–v в a

Почему перекраска не испортит u. Путь начинается в v ребром цвета a, и стороны двудольного графа чередуются. На сторону u путь каждый раз приходит ребром цвета a. Если бы путь дошёл до u, то по ребру цвета a - но a у u свободен, такого ребра нет. Значит, путь до u не доходит, и после обмена у u цвет a по-прежнему свободен, а у v он освободился. Так каждое ребро красится в один из Δ цветов - это и есть теорема Кёнига. Время - O(E · V): на ребро не больше одного пути.

В графе с нечётными циклами путь МОЖЕТ вернуться к u, и Δ цветов не всегда хватает: треугольник имеет Δ = 2, а рёбер-то три, и все попарно соседние - нужно три цвета. Визинг доказал, что больше Δ + 1 не нужно никогда (для графов без кратных рёбер), но определить, Δ или Δ + 1, в общем случае так же трудно, как раскрасить вершины тремя цветами.

Правило

Рёберной раскраске нужно не меньше Δ цветов. В двудольном графе ровно Δ хватает всегда (Кёниг): ребро u–v красят цветом a, свободным у u; если a занят у v, на чередующемся пути цветов a/b из v их меняют местами - путь до u не дойдёт. В любом простом графе хватает Δ + 1 (Визинг), но различить Δ и Δ + 1 трудно. Жадная раскраска (наименьший цвет, свободный у обоих концов) гарантирует лишь 2Δ − 1.

Разбор: где жадность тратит лишний цвет

путь x – u – v – y – z, Δ = 2. Рёбра приходят в порядке:
    x–u  → цвет 0
    y–z  → цвет 0
    v–y  → у y занят 0 → цвет 1
    u–v  → у u занят 0, у v занят 1 → ЖАДНО: цвет 2

по Кёнигу для u–v: a = 1 (свободен у u), b = 0 (свободен у v).
    a = 1 занят у v → путь из v по цветам 1, 0, 1...: v –1– y –0– z
    меняем: v–y станет 0, y–z станет 1
    теперь 1 свободен у v → u–v красим в 1. Итого два цвета.

Жадность не ошиблась ни на одном шаге «локально» - она просто не умеет возвращаться. Перекраска вдоль пути - это и есть возврат, но ограниченный одной цепочкой.

Разбор: 320 уроков

Программа строит случайное расписание: 30 учителей, 30 классов, 320 уроков, которые приходят вразнобой. Красит жадно и по Кёнигу, проверяет, что у каждого учителя и класса все уроки в разных слотах, и сравнивает число слотов с Δ.

rebra.py
import random


def zhadno(rebra):                   # наименьший цвет, свободный у двух
    cvet, zanyato = {}, {}
    for u, v in rebra:
        c = 0
        while c in zanyato.get(u, ()) or c in zanyato.get(v, ()):
            c += 1
        cvet[u, v] = c
        zanyato.setdefault(u, set()).add(c)
        zanyato.setdefault(v, set()).add(c)
    return cvet


def kyonig(rebra, delta):            # двудольный граф: ровно Δ цветов
    po_cvetu = {}                    # (вершина, цвет) -> сосед по ребру
    def svoboden(x):
        return next(c for c in range(delta) if (x, c) not in po_cvetu)
    for u, v in rebra:
        a, b = svoboden(u), svoboden(v)
        if (v, a) in po_cvetu:       # a занят у v: меняем a и b на
            put, x, c = [], v, a     # чередующемся пути из v
            while (x, c) in po_cvetu:
                y = po_cvetu[x, c]
                put.append((x, y, c))
                x, c = y, (b if c == a else a)
            for x, y, c in put:
                del po_cvetu[x, c], po_cvetu[y, c]
            for x, y, c in put:
                d = b if c == a else a
                po_cvetu[x, d], po_cvetu[y, d] = y, x
        po_cvetu[u, a], po_cvetu[v, a] = v, u
    return {(u, v): c for (u, c), v in po_cvetu.items() if u[0] == "у"}


rng = random.Random(0)
uchitelya = [f"у{i}" for i in range(30)]
klassy = [f"к{i}" for i in range(30)]
rebra = sorted({(rng.choice(uchitelya), rng.choice(klassy))
                for _ in range(400)})
rng.shuffle(rebra)                   # уроки приходят вразнобой
stepen = {}
for u, v in rebra:
    stepen[u] = stepen.get(u, 0) + 1
    stepen[v] = stepen.get(v, 0) + 1
delta = max(stepen.values())
for imya, cvet in (("жадно", zhadno(rebra)),
                   ("Кёниг", kyonig(rebra, delta))):
    for x in stepen:                 # проверка: у вершины цвета разные
        u_x = [c for (u, v), c in cvet.items() if x in (u, v)]
        assert len(u_x) == len(set(u_x)) == stepen[x]
    print(f"{imya}: уроков {len(cvet)}, Δ = {delta},"
          f" слотов {max(cvet.values()) + 1}")
↳  выводpython3 rebra.py
жадно: уроков 320, Δ = 15, слотов 16
Кёниг: уроков 320, Δ = 15, слотов 15

Жадность потратила лишний слот, перекраска уложилась ровно в нижнюю границу Δ = 15 - лучше нельзя в принципе. Обе раскраски правильные, проверка прошла у всех шестидесяти вершин.

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

Первая ошибка - решать расписание жадно и считать, что раз слотов вышло Δ + 1, меньше нельзя: для двудольных задач (учитель - класс, станок - деталь, отправитель - получатель) оптимум всегда ровно Δ. Вторая - путать рёберную раскраску с вершинной и бояться трудности там, где её нет. Третья - перекрашивать путь и забыть, что в НЕдвудольном графе он может дойти до u: тогда обмен ломает раскраску у самой вершины, ради которой его затевали. Четвёртая - не учитывать, что Визинг верен для простых графов: при кратных рёбрах (два урока одного учителя в одном классе) цветов может понадобиться до 3Δ/2.

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

Расписания уроков и экзаменов, распределение передач в сети по временным окнам (коммутатор не может одновременно передать из одного порта в два и принять в один порт из двух - это двудольный граф «вход - выход»), назначение частот в сетях, где канал - ребро между узлами. Круговой турнир на n команд (чётное n) - рёберная раскраска полного графа в n − 1 цвет: тур за туром, и его строят «методом круга» - одну команду фиксируют, остальные вращают. Знание, что для двудольного случая хватает Δ, превращает «а нельзя ли уложить в меньше слотов?» из споров в доказательство.

Теперь сам

Двудольный граф: A соединена с 1, 2, 3, B - с 1, 2, C - с 3. Чему равно Δ и как раскрасить рёбра в Δ цветов? Сколько цветов взяла бы жадность при порядке B–1, A–2, B–2, A–1, A–3, C–3?

Ответ. Δ = 3 (у A). Раскраска: A–1:0, A–2:1, A–3:2, B–1:1, B–2:0, C–3:0 - у каждой вершины цвета разные. Жадность: B–1:0, A–2:0, B–2:1 (у B занят 0), A–1:1 (у A занят 0, у 1 занят 0), A–3:2, C–3:0 - тоже три цвета, оптимум. Жадность ошибается не всегда, а лишь иногда, и никогда не говорит, когда.

Главное

Рёберной раскраске нужно не меньше Δ цветов - столько рёбер у самой загруженной вершины. В двудольном графе ровно Δ хватает всегда: обмен двух цветов вдоль чередующегося пути освобождает нужный цвет и не доходит до второго конца. В любом простом графе хватает Δ + 1. Жадность гарантирует лишь 2Δ − 1 и на расписании из 320 уроков взяла лишний слот, а перекраска уложилась в Δ.

Комментарии

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

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

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