EduBrick
классная работа

Граф как модель

Программирование на Python: от нуля до олимпиад

0/18решено 0 из 18до зачёта осталось 10

Чему научитесь

Разберётесь с темами:

  • Вершины и рёбра
  • Список смежности
  • Где встречаются графы
  • Степени вершин

Как устроено занятие

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

Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.

После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.

Сколько это займёт

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

теория

Граф — это не картинка

Граф обычно рисуют кружками и линиями, и от этого кажется, что он про геометрию. Это не так.

Граф — это множество объектов и отношение между ними. Объекты называют вершинами, отношение — рёбрами. Где именно нарисованы кружки, значения не имеет: важно только, кто с кем соединён.

Поэтому графом оказывается почти всё, где есть слово «связаны»:

задача вершины рёбра
города и дороги города дороги
знакомства люди «знакомы»
расписание предметы «нельзя в одно время»
клетчатое поле проходимые клетки соседство по стороне

Последняя строка важна: поле из прошлого занятия — тоже граф, просто заданный не списком рёбер, а правилом «соседние клетки соединены». Значит всё, что мы дальше научимся делать с графами, будет работать и на поле.

Ориентированный или нет

Если отношение симметрично — «соединены дорогой», «знакомы», — граф неориентированный, и ребро принадлежит обеим вершинам сразу.

Если нет — «ведёт ссылка», «должен быть сдан раньше» — граф ориентированный, и ребро идёт из одной вершины в другую.

Различить их надо до написания кода: от этого зависит, сколько раз ребро попадёт в структуру.

теория

Как хранить граф

Способов три, и выбор между ними — не вкусовщина, а расчёт.

Список рёбер

Просто список пар. Занимает O(m)O(m) памяти, но ответить «кто соседи вершины 5» можно только полным перебором. Годится, когда рёбра нужны все сразу и по одному разу.

Матрица смежности

Таблица n×nn \times n, где на пересечении стоит единица, если ребро есть.

linked = [[0] * (n + 1) for _ in range(n + 1)]
linked[a][b] = linked[b][a] = 1

Проверка «есть ли ребро» — мгновенная. Но памяти нужно O(n2)O(n^2): при n=2105n = 2 \cdot 10^5 это сорок миллиардов ячеек, то есть матрица просто не существует. Матрица применима, только когда вершин мало — скажем, несколько сотен.

Список смежности

Для каждой вершины — список её соседей. Памяти O(n+m)O(n + m), перебрать соседей можно за их количество.

adj = [[] for _ in range(n + 1)]
for _ in range(m):
    a, b = map(int, input().split())
    adj[a].append(b)
    adj[b].append(a)      # неориентированный граф — обе стороны!

Это основной способ, и почти все задачи о графах решаются с ним. Обратите внимание на вторую строку внутри цикла: в неориентированном графе ребро надо положить в обе ячейки. Забыть её — самая частая ошибка темы, и программа при этом не падает.

Список делается на n+1n + 1 ячейку, чтобы вершина с номером nn имела своё место и не приходилось всюду вычитать единицу.

тест

Проверка: чем хранить

В графе 200 000 вершин и 200 000 рёбер. Каким способом его хранить?

Войдите, чтобы ответить.
теория

Степени вершин

Степень вершины — количество выходящих из неё рёбер. В списке смежности это просто длина её списка.

Из определения сразу следует утверждение, которое стоит понимать, а не запоминать:

vdeg(v)=2m.\sum_{v} \deg(v) = 2m.

Каждое ребро имеет два конца и потому прибавляет единицу ровно к двум степеням. Значит сумма всех степеней вдвое больше числа рёбер — всегда, в любом графе.

Это отличная проверка себя. Посчитали степени, сложили, получили не 2m2m — значит где-то ребро учтено один раз вместо двух. Ошибка найдена до отправки решения.

Что видно по степеням

степень что это значит
0 изолированная вершина, ни с кем не связана
1 висячая вершина, лист
n1n - 1 соединена со всеми остальными

Многие вопросы решаются степенями без всякого обхода. Например, сколько в графе путей длины два: такой путь однозначно задаётся своей серединой и парой её соседей, значит через вершину степени dd их проходит Cd2C_d^2 — и ответ получается одной суммой.

Ориентированный случай

Там степени две: исходящая и входящая. Сумма исходящих равна mm, а не 2m2m: у ориентированного ребра конец только один — тот, куда оно ведёт.

расчёт

Проверка: сумма степеней

В неориентированном графе 10 вершин и 15 рёбер, петель нет.

Чему равна сумма степеней всех вершин? Введите целое число.

Войдите, чтобы ответить.
теория

Петли, кратные рёбра и прочие неприятности

В условии почти всегда написано, чего в графе не бывает. Читать эту строчку надо внимательно: от неё зависит код.

Петля — ребро из вершины в саму себя. К степени оно добавляет двойку, а не единицу: у него два конца, и оба в одной вершине. В списке смежности вершина окажется собственным соседом.

Кратные рёбра — несколько рёбер между одной парой. Список смежности их сохранит, множество пар — нет. Что именно нужно, решает условие.

Если требуется считать пары различными, приводите каждую к одному виду — меньший номер первым:

key = (a, b) if a < b else (b, a)

Без этого ребро (3,7)(3, 7) и ребро (7,3)(7, 3) окажутся разными, хотя это одно и то же ребро.

Простой граф — без петель и кратных рёбер. У него число рёбер не превосходит Cn2C_n^2, и это иногда позволяет сразу отбросить невозможные варианты.

теория

Типичные ошибки

Ребро добавлено в одну сторону. Главная ошибка темы. Программа не падает: список смежности получается, просто он описывает другой граф — ориентированный. Все степени выходят вдвое меньше, ответы неверны, а место ошибки ничем себя не выдаёт. Проверяйте суммой степеней.

Матрица смежности при больших nn. Программа падает по памяти или не запускается вовсе. Считайте n2n^2 до того, как пишете.

Вершина с номером nn не помещается. Списки заводятся на nn ячеек вместо n+1n + 1, и последняя вершина выходит за границу. Заводите на единицу больше и не думайте об этом.

Забыт случай без рёбер. При m=0m = 0 строка с рёбрами пуста, и попытка её разобрать даёт пустой список — но некоторые способы чтения на этом спотыкаются.

Изолированные вершины потеряны. Если строить граф только по рёбрам, вершины без рёбер вообще не появятся. Их надо учитывать отдельно — они существуют, просто ни с кем не связаны.

Как проверять себя

  • граф без рёберm=0m = 0;
  • одна вершинаn=1n = 1;
  • сумма степеней равна 2m2m — на любом тесте;
  • граф с изолированной вершиной — она должна попасть в ответ.
теория

Практика: пятнадцать задач

Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.

Обходов графа мы ещё не проходили, и ни одна задача занятия их не требует. Всё решается построением графа и подсчётом степеней — это и есть цель: научиться переводить условие в граф раньше, чем начинать что-то с ним делать.

И заведите привычку: построили граф — проверьте сумму степеней.

лестница задач
развёрнутый ответ

Ребро в одну сторону

Задача: «дан неориентированный граф из nn вершин и mm рёбер без петель и кратных рёбер; посчитайте, сколько в нём вершин ровно с одним соседом».

Ученик написал:

n, m = map(int, input().split())
flat = list(map(int, input().split()))

adj = [[] for _ in range(n + 1)]
for i in range(0, len(flat), 2):
    adj[flat[i]].append(flat[i + 1])

count = 0
for v in range(1, n + 1):
    if len(adj[v]) == 1:
        count += 1

print(count)

Программа не падает ни на одном тесте и на некоторых даёт правильный ответ.

Постройте вход, на котором она ошибается, объясните, что именно она построила вместо нужного графа, и предложите исправление.

Войдите, чтобы ответить.