остова
1. Определения.
Тут тоже что-то есть
Взвешенный неориентированный граф G - это граф с множеством вершин V и
множеством неориентированных рёбер E, где у каждого ребра определен вес - это
просто какое-то число. Вес может быть нулевым или даже отрицательным.
Далее мы везде будем работать именно с таким графом.
Остовное дерево/остов графа - это поддерево графа, в которое входят все его
вершины.
2. Алгоритм Прима
Алгоритм Прима
Полная аналогия с алгоритмом Дейкстры.
Алгоритм Прима:
Идея: Начнем строить поддерево и будем по одной добавлять туда вершины, которых
там нет. Будем хранить массив dist - только теперь он будет значить минимальное
ребро, ведущее в построенное поддерево.
Тогда релаксация ребра u->v будет выглядеть так, если u лежит в поддереве:
dist[v] = min(dist[v], w(u, v))
Давайте так же брать вершину с минимальным dist и добавлять ее в поддерево,
потому что это ребро (или равное ему) точно будет в поддереве.
Как писать: dist[всех] = INF
for i in range(n):
берем вершину с минимальным dist (за линию или за логарифм)
помечаем ее как использованную
релаксируем все ребра из нее
добавляем к ответу dist от той вершины
Оценка времени работы: O(V^2), если искать минимум за линию,
O(E log V), если делать как в дейкстре через сет
Как восстанавливать ответ: хранить массив prev[x], при релаксации по ребру u->v
обновляем prev[v] = prev[u]
Давайте хранить каждое множество как подвешенное дерево его элементов. Тогда
get(x) - это просто подняться в корень дерева и вернуть его. А union(x) - это просто
взять корень x, и подвесить его к корню y.
Деревья можно хранить в массиве p[x] - массив предков.
Но тогда мы можем отвечать на запросы очень долго, если деревья будут очень
высокими - вырождаться в бамбук, и отвечать на запросы мы будем за O(n). Что
делать? Надо использовать две эвристики (оптимизации): по отдельности они делают
время работы O(log n), а если применить их одновременно, то O(log* n), и даже за
обратную функцию Аккермана (читайте в статье подробнее).
Ранговая эвристика:
Давайте при объединении вешать меньшее дерево к большему, чтобы не увеличивать
сильно высоту. Что значит большее и меньшее? Будем хранить ранг вершины в r[x].
Грубо говоря - это максимальная высота дерева, которая была из этой вершины.
Тогда нужно просто подвешивать дерево меньшего ранга к большему, а если они
равны, то неважно кого к кому, главное не забыть увеличить ранг.
union(x, y):
x = get(x)
y = get(y)
if x == y
return
if r[x] == r[y]
r[x]++
if r[x] < r[y]
p[x] = y
else
p[y] = x
Вот и все, применяем обе эвристики и работает почти за O(1)
4. Алгоритм Краскала
Алгоритм Краскала
Алгоритм Краскала:
Идея: Давайте строить дерево постепенно, из леса. Будем хранить поддеревья,
которые мы уже включили ответ, и будем их соединять ребрами. Хранить эти
компоненты мы будем в СНМ.
Давайте просто возьмем список всех рёбер, отсортируем его по весу, и будем
добавлять рёбра в ответ, если оно соединяет две вершины из разных компонент. А
если не из разных - пропустим это ребро.
Это работает, потому что всегда выгодно взять ребро минимального веса (рассмотрим
дерево-ответ и наше ребро, если его там нет, то оно образует цикл, а значит оно
наибольшее в этом цикле, а значит если мы его рассматриваем, то все вершины цикла
уже в одной компоненте)
Как писать: Кладем в массив ребра (weight, u, v) и сортируем, после чего
проходимся циклом и пытаемся добавить их, если get(u) 1= get(v): union(u, v), добавить
к ответу это ребро и weight.
Оценка времени работы: Сортировка и запросы в СНМ: O(E log E + E log* V) = O(E
log E)
Как восстанавливать ответ: Мы его явно получаем