- Представлять граф матрицей или списками смежности и обосновывать выбор
- Вручную трассировать BFS, DFS и алгоритм Дейкстры
- Находить топологический порядок и минимальное остовное дерево
Навигатор за секунду находит кратчайший путь из Баку в Шеки, социальная сеть предлагает «людей, которых вы можете знать», а университет выстраивает порядок курсов так, чтобы никто не записался на «Алгоритмы» раньше «Программирования». Три разные задачи — одна модель: граф. Граф описывает объекты (вершины) и связи между ними (рёбра) и является одной из самых употребительных моделей информатики.
Графы и их представление
Множество вершин V и множество рёбер E (пар вершин). Рёбра бывают неориентированными (дружба) и ориентированными (A → B, «подписан на»); если у каждого ребра есть вес (расстояние, цена, время), граф называется взвешенным. Степень вершины — число инцидентных ей рёбер.
- deg(v)степень вершины v
- |V|, |E|число вершин и рёбер
«Лемма о рукопожатиях»: каждое неориентированное ребро добавляет к сумме степеней 2 — по одному на каждом конце. Второе неравенство — максимальное число рёбер в простом неориентированном графе.
| Свойство | Матрица смежности | Списки смежности |
|---|---|---|
| память | O(V²) | O(V + E) |
| есть ли ребро (u, v)? | O(1) | O(deg u) |
| обойти всех соседей u | O(V) | O(deg u) |
| когда выбирать | плотный граф (E ≈ V²) | разреженный граф (дороги, соцсети) |
Весь урок мы будем пользоваться одним графом-примером. Шесть вершин A–F и взвешенные неориентированные рёбра: A–B 4, A–C 2, B–C 1, B–D 5, C–D 8, C–E 10, D–E 2, D–F 6, E–F 3. Представь, что веса — это километры.
Поиск в ширину (BFS) и в глубину (DFS)
BFS расходится от стартовой вершины «волной»: сначала все соседи, потом соседи соседей. Он использует очередь и в невзвешенном графе даёт кратчайшие пути по числу рёбер. DFS идёт по одному пути как можно глубже и возвращается назад в тупиках; он работает со стеком (или рекурсией) и применяется для проверки связности, поиска циклов и топологической сортировки. Оба просматривают каждую вершину и каждое ребро один раз: O(V + E).
Не учитывая веса, выполни BFS и DFS из F на графе-примере. Соседей бери по алфавиту: A: B, C; B: A, C, D; C: A, B, D, E; D: B, C, E, F; E: C, D, F; F: D, E.
Показать решениеСкрыть решение
Уровни: F | D, E | B, C | A.
DFS (рекурсия): F → D (первый сосед F) → B (первый непосещённый сосед D) → A → C (сосед A) → E (непосещённый сосед C).
Порядок: F, D, B, A, C, E — DFS добрался до A за 4 шага, а BFS нашёл кратчайший путь (3 ребра).
from collections import deque
graph = {'A': ['B', 'C'], 'B': ['A', 'C', 'D'], 'C': ['A', 'B', 'D', 'E'],
'D': ['B', 'C', 'E', 'F'], 'E': ['C', 'D', 'F'], 'F': ['D', 'E']}
def bfs(start):
dist = {start: 0}
queue = deque([start])
while queue:
v = queue.popleft()
for w in graph[v]:
if w not in dist:
dist[w] = dist[v] + 1
queue.append(w)
return dist
def dfs(v, seen):
seen.append(v)
for w in graph[v]:
if w not in seen:
dfs(w, seen)
return seen
print('BFS from F:', bfs('F'))
print('DFS from F:', dfs('F', []))▸ Ожидаемый результат
BFS from F: {'F': 0, 'D': 1, 'E': 1, 'B': 2, 'C': 2, 'A': 3}
DFS from F: ['F', 'D', 'B', 'A', 'C', 'E']Алгоритм Дейкстры: кратчайшие пути во взвешенном графе
Если у рёбер есть веса, BFS не подходит: три коротких ребра могут оказаться дешевле одного длинного. Алгоритм Дейкстры (для неотрицательных весов) на каждом шаге выбирает среди ещё не окончательных вершин ту, у которой расстояние наименьшее (очередь с приоритетом), объявляет её окончательной и обновляет расстояния соседей релаксацией.
- dist[v]найденное на данный момент кратчайшее расстояние от источника до v
- w(u, v)вес ребра u–v (≥ 0)
- Tвремя работы с двоичной кучей
| Шаг: зафиксирована | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| 0: начало | 0 | ∞ | ∞ | ∞ | ∞ | ∞ |
| 1: A (0) | 0 | 4 | 2 | ∞ | ∞ | ∞ |
| 2: C (2) | 0 | 3 | 2 | 10 | 12 | ∞ |
| 3: B (3) | 0 | 3 | 2 | 8 | 12 | ∞ |
| 4: D (8) | 0 | 3 | 2 | 8 | 10 | 14 |
| 5: E (10) | 0 | 3 | 2 | 8 | 10 | 13 |
| 6: F (13) | 0 | 3 | 2 | 8 | 10 | 13 |
По таблице найди кратчайший путь из A в F и его длину.
Показать решениеСкрыть решение
С конца назад: F ← E ← D ← B ← C ← A.
Путь: A → C → B → D → E → F, длина 2 + 1 + 5 + 2 + 3 = 13.
Заметь: очевидный на вид путь A → B → D → F равен 4 + 5 + 6 = 15.
import heapq
edges = [('A', 'B', 4), ('A', 'C', 2), ('B', 'C', 1), ('B', 'D', 5), ('C', 'D', 8),
('C', 'E', 10), ('D', 'E', 2), ('D', 'F', 6), ('E', 'F', 3)]
adj = {}
for u, v, w in edges:
adj.setdefault(u, []).append((v, w))
adj.setdefault(v, []).append((u, w))
def dijkstra(src):
dist, prev, pq = {src: 0}, {}, [(0, src)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, w in adj[u]:
if d + w < dist.get(v, float('inf')):
dist[v], prev[v] = d + w, u
heapq.heappush(pq, (d + w, v))
return dist, prev
dist, prev = dijkstra('A')
path = ['F']
while path[-1] != 'A':
path.append(prev[path[-1]])
print(dict(sorted(dist.items())))
print(' -> '.join(reversed(path)), '=', dist['F'])▸ Ожидаемый результат
{'A': 0, 'B': 3, 'C': 2, 'D': 8, 'E': 10, 'F': 13}
A -> C -> B -> D -> E -> F = 13heapq. Вершина может попасть в очередь несколько раз; устаревшие записи отбрасывает условие d > dist[u] («ленивое удаление»).Топологическая сортировка и минимальное остовное дерево
Топологическая сортировка упорядочивает вершины ориентированного ациклического графа (DAG) так, чтобы для каждого ребра u → v вершина u шла раньше v: пререквизиты курсов, порядок сборки модулей программы, порядок пересчёта ячеек в Excel. Алгоритм Кана: помести вершины с входящей степенью 0 в очередь, извлеки одну, удали её рёбра и добавь в очередь вершины, чья входящая степень упала до 0. Если вышло меньше V вершин, в графе есть цикл. Время: O(V + E).
from collections import deque
courses = {'Programming': ['DataStructures', 'Databases'], 'Math': ['Algorithms'],
'DataStructures': ['Algorithms'], 'Databases': ['AI'],
'Algorithms': ['AI'], 'AI': []}
def topo_sort(g):
indeg = {v: 0 for v in g}
for v in g:
for w in g[v]:
indeg[w] += 1
queue = deque(v for v in g if indeg[v] == 0)
order = []
while queue:
v = queue.popleft()
order.append(v)
for w in g[v]:
indeg[w] -= 1
if indeg[w] == 0:
queue.append(w)
return order if len(order) == len(g) else None
print(topo_sort(courses))
print(topo_sort({'X': ['Y'], 'Y': ['Z'], 'Z': ['X']}))▸ Ожидаемый результат
['Programming', 'Math', 'DataStructures', 'Databases', 'Algorithms', 'AI'] None
None — при таких требованиях ни один курс начать нельзя.Множество рёбер, которое соединяет все вершины связного взвешенного графа, не содержит циклов и имеет наименьший возможный суммарный вес. В нём всегда ровно V − 1 рёбер. Применения: соединить сёла минимальным количеством кабеля, проектирование электро- и водопроводных сетей, кластеризация.
Найди минимальное остовное дерево графа-примера алгоритмом Краскала: бери рёбра по возрастанию веса и оставляй те, что не образуют цикла.
Показать решениеСкрыть решение
B–C 1 ✓ → {B, C}
A–C 2 ✓ → {A, B, C}
D–E 2 ✓ → {D, E}
E–F 3 ✓ → {D, E, F}
A–B 4 ✗ — A и B уже в одной компоненте (замкнулся бы цикл A–C–B)
B–D 5 ✓ → все вершины соединены; 5 = V − 1 рёбер, стоп.
MST: {B–C, A–C, D–E, E–F, B–D}, суммарный вес 1 + 2 + 2 + 3 + 5 = 13.
edges = [('A', 'B', 4), ('A', 'C', 2), ('B', 'C', 1), ('B', 'D', 5), ('C', 'D', 8),
('C', 'E', 10), ('D', 'E', 2), ('D', 'F', 6), ('E', 'F', 3)]
parent = {v: v for v in 'ABCDEF'}
def find(v):
while parent[v] != v:
v = parent[v]
return v
mst = []
for u, v, w in sorted(edges, key=lambda e: e[2]):
ru, rv = find(u), find(v)
if ru != rv:
parent[ru] = rv
mst.append((u, v, w))
print(mst)
print('total weight:', sum(w for _, _, w in mst))▸ Ожидаемый результат
[('B', 'C', 1), ('A', 'C', 2), ('D', 'E', 2), ('E', 'F', 3), ('B', 'D', 5)]
total weight: 13find находит «представителя» компоненты, и если у двух вершин он общий, ребро замкнуло бы цикл. Время Краскала определяется сортировкой: O(E log E). Алгоритм Прима же выращивает дерево из одной вершины (похоже на Дейкстру, с очередью с приоритетом).Главное
- Граф G = (V, E); для разреженных графов — списки смежности (O(V + E)), для плотных — матрица (O(V²)).
- BFS идёт по уровням с очередью и даёт кратчайшие пути без весов; DFS идёт вглубь со стеком; оба — O(V + E).
- Дейкстра работает с неотрицательными весами, очередью с приоритетом и релаксацией за O((V + E) log V).
- Топологический порядок существует только в DAG; алгоритм Кана заодно обнаруживает циклы.
- В MST V − 1 рёбер; Краскал сортирует рёбра по весу и избегает циклов с помощью union–find.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.