Есть N курсов и M пар зависимостей (i, j): «перед курсом j надо сдать курс i». Определи, можно ли закрыть все курсы.
Зависимости: 0→1, 0→2, 1→3, 2→3
Возможный порядок сдачи: 0 → 1 → 2 → 3
Стрелки всегда идут «вперёд» по этому порядку. Ни один курс не ждёт сам себя.
Зависимости: 0→1, 1→2, 2→0
тупик чтобы начать 0, нужен 2; для 2 нужен 1; для 1 нужен 0…
Круг зависимостей — ни один курс нельзя начать первым. Закрыть всё невозможно.
Ключевая величина — «входящая степень» indeg[v]: сколько ещё несданных предусловий у курса v (сколько стрелок в него входит).
indeg = 0 — это курс без предусловий, его можно взять прямо сейчас.
Когда ты «сдаёшь» курс, ты убираешь его стрелки → у зависящих курсов indeg уменьшается. Кто обнулился — теперь тоже доступен.
indeg всех вершин (по одному проходу по рёбрам).indeg = 0 (доступные сразу).processed += 1), у всех его соседей уменьшить indeg; кто стал 0 — в очередь.processed == N) → можно. Иначе застряли на цикле → нельзя.0→1, 0→2, 1→3, 2→3| шаг | очередь (indeg=0) | сдаём | что меняется | processed |
|---|---|---|---|---|
| старт | [0] | — | indeg: 1→1, 2→1, 3→2 | 0 |
| 1 | [1, 2] | 0 | indeg[1]→0, indeg[2]→0 | 1 |
| 2 | [2, 3?] | 1 | indeg[3]→1 | 2 |
| 3 | [3] | 2 | indeg[3]→0 | 3 |
| 4 | [ ] | 3 | — | 4 |
0→1, 1→2, 2→0У всех трёх вершин indeg = 1 (в каждую входит стрелка). Очередь стартует пустой — ни одного курса с indeg 0. Цикл сразу завершается.
from collections import deque def can_finish(n, deps): # deps: список пар (i, j) — "сначала i, потом j" g = [[] for _ in range(n)] # список смежности indeg = [0] * n # число несданных предусловий for i, j in deps: g[i].append(j) indeg[j] += 1 # все курсы без предусловий — доступны сразу dq = deque(v for v in range(n) if indeg[v] == 0) processed = 0 while dq: v = dq.popleft() processed += 1 # "сдали" курс v for u in g[v]: indeg[u] -= 1 # у зависящего стало на 1 предусловие меньше if indeg[u] == 0: # все предусловия сданы → доступен dq.append(u) return processed == n # сдали все? значит цикла нет
Время O(N + M) Память O(N + M)
Один проход по рёбрам для indeg, потом каждая вершина и каждое ребро обрабатываются ровно один раз.
i → j, и indeg растёт у j. Перепутаешь направление — получишь неверный ответ.processed == n. Частая ошибка — забыть счётчик и пытаться «искать цикл» руками.