Чему научитесь
Разберётесь с темами:
- Вершины и рёбра
- Список смежности
- Где встречаются графы
- Степени вершин
Как устроено занятие
Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.
Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.
После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.
Сколько это займёт
Примерно час-полтора вместе с задачами. Сроков нет: можно закрыть вкладку и вернуться когда удобно — прогресс сохранится.
Граф — это не картинка
Граф обычно рисуют кружками и линиями, и от этого кажется, что он про геометрию. Это не так.
Граф — это множество объектов и отношение между ними. Объекты называют вершинами, отношение — рёбрами. Где именно нарисованы кружки, значения не имеет: важно только, кто с кем соединён.
Поэтому графом оказывается почти всё, где есть слово «связаны»:
| задача | вершины | рёбра |
|---|---|---|
| города и дороги | города | дороги |
| знакомства | люди | «знакомы» |
| расписание | предметы | «нельзя в одно время» |
| клетчатое поле | проходимые клетки | соседство по стороне |
Последняя строка важна: поле из прошлого занятия — тоже граф, просто заданный не списком рёбер, а правилом «соседние клетки соединены». Значит всё, что мы дальше научимся делать с графами, будет работать и на поле.
Ориентированный или нет
Если отношение симметрично — «соединены дорогой», «знакомы», — граф неориентированный, и ребро принадлежит обеим вершинам сразу.
Если нет — «ведёт ссылка», «должен быть сдан раньше» — граф ориентированный, и ребро идёт из одной вершины в другую.
Различить их надо до написания кода: от этого зависит, сколько раз ребро попадёт в структуру.
Как хранить граф
Способов три, и выбор между ними — не вкусовщина, а расчёт.
Список рёбер
Просто список пар. Занимает памяти, но ответить «кто соседи вершины 5» можно только полным перебором. Годится, когда рёбра нужны все сразу и по одному разу.
Матрица смежности
Таблица , где на пересечении стоит единица, если ребро есть.
linked = [[0] * (n + 1) for _ in range(n + 1)]
linked[a][b] = linked[b][a] = 1
Проверка «есть ли ребро» — мгновенная. Но памяти нужно : при это сорок миллиардов ячеек, то есть матрица просто не существует. Матрица применима, только когда вершин мало — скажем, несколько сотен.
Список смежности
Для каждой вершины — список её соседей. Памяти , перебрать соседей можно за их количество.
adj = [[] for _ in range(n + 1)]
for _ in range(m):
a, b = map(int, input().split())
adj[a].append(b)
adj[b].append(a) # неориентированный граф — обе стороны!
Это основной способ, и почти все задачи о графах решаются с ним. Обратите внимание на вторую строку внутри цикла: в неориентированном графе ребро надо положить в обе ячейки. Забыть её — самая частая ошибка темы, и программа при этом не падает.
Список делается на ячейку, чтобы вершина с номером имела своё место и не приходилось всюду вычитать единицу.
Проверка: чем хранить
В графе 200 000 вершин и 200 000 рёбер. Каким способом его хранить?
Степени вершин
Степень вершины — количество выходящих из неё рёбер. В списке смежности это просто длина её списка.
Из определения сразу следует утверждение, которое стоит понимать, а не запоминать:
Каждое ребро имеет два конца и потому прибавляет единицу ровно к двум степеням. Значит сумма всех степеней вдвое больше числа рёбер — всегда, в любом графе.
Это отличная проверка себя. Посчитали степени, сложили, получили не — значит где-то ребро учтено один раз вместо двух. Ошибка найдена до отправки решения.
Что видно по степеням
| степень | что это значит |
|---|---|
| 0 | изолированная вершина, ни с кем не связана |
| 1 | висячая вершина, лист |
| соединена со всеми остальными |
Многие вопросы решаются степенями без всякого обхода. Например, сколько в графе путей длины два: такой путь однозначно задаётся своей серединой и парой её соседей, значит через вершину степени их проходит — и ответ получается одной суммой.
Ориентированный случай
Там степени две: исходящая и входящая. Сумма исходящих равна , а не : у ориентированного ребра конец только один — тот, куда оно ведёт.
Проверка: сумма степеней
В неориентированном графе 10 вершин и 15 рёбер, петель нет.
Чему равна сумма степеней всех вершин? Введите целое число.
Петли, кратные рёбра и прочие неприятности
В условии почти всегда написано, чего в графе не бывает. Читать эту строчку надо внимательно: от неё зависит код.
Петля — ребро из вершины в саму себя. К степени оно добавляет двойку, а не единицу: у него два конца, и оба в одной вершине. В списке смежности вершина окажется собственным соседом.
Кратные рёбра — несколько рёбер между одной парой. Список смежности их сохранит, множество пар — нет. Что именно нужно, решает условие.
Если требуется считать пары различными, приводите каждую к одному виду — меньший номер первым:
key = (a, b) if a < b else (b, a)
Без этого ребро и ребро окажутся разными, хотя это одно и то же ребро.
Простой граф — без петель и кратных рёбер. У него число рёбер не превосходит , и это иногда позволяет сразу отбросить невозможные варианты.
Типичные ошибки
Ребро добавлено в одну сторону. Главная ошибка темы. Программа не падает: список смежности получается, просто он описывает другой граф — ориентированный. Все степени выходят вдвое меньше, ответы неверны, а место ошибки ничем себя не выдаёт. Проверяйте суммой степеней.
Матрица смежности при больших . Программа падает по памяти или не запускается вовсе. Считайте до того, как пишете.
Вершина с номером не помещается. Списки заводятся на ячеек вместо , и последняя вершина выходит за границу. Заводите на единицу больше и не думайте об этом.
Забыт случай без рёбер. При строка с рёбрами пуста, и попытка её разобрать даёт пустой список — но некоторые способы чтения на этом спотыкаются.
Изолированные вершины потеряны. Если строить граф только по рёбрам, вершины без рёбер вообще не появятся. Их надо учитывать отдельно — они существуют, просто ни с кем не связаны.
Как проверять себя
- граф без рёбер — ;
- одна вершина — ;
- сумма степеней равна — на любом тесте;
- граф с изолированной вершиной — она должна попасть в ответ.
Практика: пятнадцать задач
Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.
Обходов графа мы ещё не проходили, и ни одна задача занятия их не требует. Всё решается построением графа и подсчётом степеней — это и есть цель: научиться переводить условие в граф раньше, чем начинать что-то с ним делать.
И заведите привычку: построили граф — проверьте сумму степеней.
Ребро в одну сторону
Задача: «дан неориентированный граф из вершин и рёбер без петель и кратных рёбер; посчитайте, сколько в нём вершин ровно с одним соседом».
Ученик написал:
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)
Программа не падает ни на одном тесте и на некоторых даёт правильный ответ.
Постройте вход, на котором она ошибается, объясните, что именно она построила вместо нужного графа, и предложите исправление.