Алгоритм Краскала
Описание
Алгоритм Краскала (или Крускала, Kruskal's algorithm) - это эффективный алгоритм построения минимального остовного дерева взвешенного связного неориентированного графа.
Минимальное остовное (покрывающее) дерево (МОД) в (неориентированном) связном взвешенном графе - это остовное дерево этого графа, имеющее минимальный возможный вес, где под весом дерева понимается сумма весов входящих в него ребер.

Еще один пример графа и его МОД. Каждое ребро помечено весом, который здесь примерно пропорционален длине ребра:

На этом рисунке видно, что в графе может быть более одного МОД.
Принцип работы
В начале текущее множество ребер устанавливается пустым. Затем, пока это возможно, проводится следующая операция: из всех ребер, добавление которых к уже имеющемуся множеству не вызовет появление в нем цикла, выбирается ребро минимального веса и добавляется к уже имеющемуся множеству. Когда таких ребер больше нет, алгоритм завершен. Подграф данного графа, содержащий все его вершины и найденное множество ребер, является его МОД.
Сложность
До начала работы алгоритма необходимо отсортировать ребра по весу, это требует O(E × log(E))
времени, где E
- множество ребер. После чего компоненты связности удобно хранить в виде системы непересекающихся множеств. Все операции в таком случае займут O(E × α(E, V))
, где α
- функция, обратная к функции Аккермана. Поскольку для любых практических задач α(E, V) < 5
, то можно принять ее за константу. Таким образом, общее время работы алгоритма Краскала можно принять за O(E * log(E))
.

Демонстрация работы алгоритма Краскала на основе евклидова расстояния:

Реализация
В нашей реализации используются такие структуры данных, как граф (graph) и система непересекающихся множеств (disjoint set), а также алгоритм быстрой сортировки (quick sort):
// algorithms/graphs/kruskal.js
import DisjoinSet from '../../data-structures/disjoint-set/index'
import Graph from '../../data-structures/graph/index'
import QuickSort from '../sorting/quick-sort'
// Функция принимает граф
export default function kruskal(graph) {
// При передаче направленного графа должно выбрасываться исключение
if (graph.isDirected) {
throw new Error(
'Алгоритм Краскала работает только с ненаправленными графами',
)
}
// Создаем новый граф, который будет содержать
// минимальное остовное дерево исходного графа
const minimumSpanningTree = new Graph()
// Сортируем ребра графа в порядке возрастания веса
const sortingCallbacks = {
compareCallback: (a, b) => {
if (a.weight === b.weight) {
return 0
}
return a.weight < b.weight ? -1 : 1
},
}
const sortedEdges = new QuickSort(sortingCallbacks).sort(graph.getAllEdges())
// Создаем непересекающиеся одноэлементные множества для всех вершин графа
const keyCb = (node) => node.getKey()
const disjointSet = new DisjoinSet(keyCb)
graph.getAllNodes().forEach((node) => disjointSet.makeSet(node))
// Перебираем ребра графа, начиная с минимального, и пытаемся добавить их
// в минимальное остовное дерево. Критерием добавления ребра является
// формирование им цикла (если оно соединяет два узла одного подмножества)
sortedEdges.forEach((edge) => {
// Если добавление ребра не формирует цикл
if (!disjointSet.isSameSet(edge.from, edge.to)) {
// Объединяем два подмножества в одно
disjointSet.union(edge.from, edge.to)
// Добавляем ребро в дерево
minimumSpanningTree.addEdge(edge)
}
})
return minimumSpanningTree
}
Тесты
Запускаем тесты:
npm run test ./algorithms/graphs/__tests__/kruskal
