Вы находитесь на странице: 1из 3

Алгоритмы поиска мин.

остова
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]

3. Система непересекающихся множеств


СНМ
В алгоритме Краскала нужен СНМ, так что давайте поймем что это и научимся писать.
Система непересекающихся множеств - это структура, хранящая
непересекающиеся множества (спасибо, капитан), на которых определены следующие
операции:
- union(x, y) - объединить множества, содержащие x и y
- get(x) - узнать в каком множестве лежит x
get(x) == get(y) только тогда, когда x и y лежат в одном множестве.
Изначально будем всегда считать, что все элементы в разных множествах
Как же реализовать эту структуру?

Давайте хранить каждое множество как подвешенное дерево его элементов. Тогда
get(x) - это просто подняться в корень дерева и вернуть его. А union(x) - это просто
взять корень x, и подвесить его к корню y.
Деревья можно хранить в массиве p[x] - массив предков.

Но тогда мы можем отвечать на запросы очень долго, если деревья будут очень
высокими - вырождаться в бамбук, и отвечать на запросы мы будем за O(n). Что
делать? Надо использовать две эвристики (оптимизации): по отдельности они делают
время работы O(log n), а если применить их одновременно, то O(log* n), и даже за
обратную функцию Аккермана (читайте в статье подробнее).

Эвристика сжатия путей:


Давайте при подъёме наверх в get в каждой вершине на пути предком сделаем
корень(раз уж мы узнаем его в ходе этой операции). Это уменьшит высоту нашего
дерева существенно.
get(x):
if p[x] != x
p[x] = get(p[x])
return p[x]

Ранговая эвристика:
Давайте при объединении вешать меньшее дерево к большему, чтобы не увеличивать
сильно высоту. Что значит большее и меньшее? Будем хранить ранг вершины в 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)
Как восстанавливать ответ: Мы его явно получаем