Графы. Поиск остова минимального веса. Алгоритм Краскала
- Подробности
- Категория: Сортировка и поиск
Алгоритм Крускала (или алгоритм Краскала) — эффективный алгоритм построения минимального остовного дерева взвешенного связного неориентированного графа. Также алгоритм используется для нахождения некоторых приближений для задачи Штейнера. Алгоритм впервые описан Джозефом Крускалом в 1956 году.
Вначале текущее множество рёбер устанавливается пустым. Затем, пока это возможно, проводится следующая операция: из всех рёбер, добавление которых к уже имеющемуся множеству не вызовет появление в нём цикла, выбирается ребро минимального веса и добавляется к уже имеющемуся множеству. Когда таких рёбер больше нет, алгоритм завершён. Подграф данного графа, содержащий все его вершины и найденное множество рёбер, является его остовным деревом минимального веса.
Минимальный остов
Время работы алгоритма Крускала для графа G = (V, Е) зависит от реализации структуры данных для непересекающихся множеств. Будем считать, что лес непересекающихся множеств реализован с эвристиками объединения по рангу и сжатия пути, поскольку асимптотически это наиболее быстрая известная реализация. Инициализация множества А в строке 1 занимает время О (1), а время, необходимое для сортировки множества в строке 4, равно О (E*lgE) (стоимость |V| операций Make_Set в цикле for в строках 2-3 учитывается позже). Цикл for в строках 5-8 выполняет О (Е) операций Find_Set и Union над лесом непересекающихся множеств. Вместе с |V| операциями Make_Set эта работа требует времени О ((V + Е) * a(V)), где а — очень медленно растущая функция. Поскольку мы предполагаем, что G — связный граф, справедливо соотношение |E| >= |V| - 1, так что операции над непересекающимися множествами требуют О (Е *а(V)) времени. Кроме того, поскольку a(|V|) = О(lgV) = O(lgE), общее время работы алгоритма Крускала равно О (E*lgE). Заметим, что |Е| < |V|2, поэтому lg |E| = О (lg V) и время работы алгоритма Крускала можно записать как О (Е * lg V).
Алгоритм Краскала
Еще потеме: Лекция 3: Графы. Задача максимальных или минимальных остовных деревьев
Реализация на различных языках программирования:
Pascal
Первый вариант
const maxn = 10; maxm = 50; type TEdge = record //структура, необходимая для хранения u,v,w:longint; // информации о ребре - соединяемые вершины, вес end; var n,m,i,mid:longint; p,rank:array[1..maxn]of longint; //структуры, необходимые для реализации системы // непересекающихся множеств //p[i] - номер вершины-родителя множества, // в которое входит i-ая вершина // rank[i] - оценка сверху глубины поддерева // с корнем в i-ой вершине e:array[1..maxm]of TEdge; buf:TEdge; procedure qsort(l,r:Longint); //быстрая сортировка var j:longint; begin i:=l; j:=r; mid:=e[(i+j)div 2].w; repeat while e[i].w<mid do inc(i); while e[j].w>mid do dec(j); if i<=j then begin buf:=e[i]; e[i]:=e[j]; e[j]:=buf; inc(i); dec(j); end; until i>j; if i<r then qsort(i,r); if j>l then qsort(l,j); end; //две процедуры для работы с системой непересекающихся множеств function findset(x:longint):longint; begin if x<>p[x] then p[x]:=findset(p[x]); findset:=p[x]; end; procedure union(x,y:longint); begin x:=findset(x); y:=findset(y); if rank[x]>rank[y] then p[y]:=x else p[x]:=y; if rank[x]=rank[y] then inc(rank[y]); end; begin read(n,m); for i:=1 to m do read(e[i].u,e[i].v,e[i].w); qsort(1,m); for i:=1 to n do begin p[i]:=i; rank[i]:=0; end; for i:=1 to m do if findset(e[i].u)<>findset(e[i].v) then begin union(e[i].u,e[i].v); writeln(e[i].u,' ',e[i].v); end; end.
Квасов И.
второй вариант
{ graphs by boris } { minimal spanning tree (Kruskal's algorithm) } const maxn = 100; {} maxm = 10000; {} inf = maxint div 2; {} type edge = record x, y: integer; { вершины ребра } w: integer; { вес ребра } end; var a: array [1..maxm] of edge; { список ребер } s: array [1..maxn] of integer; { размер компонент связности } r: array [1..maxn] of integer; { связи вершин в компонентах связности } n, m: longint; { кол-во вершин и ребер } mst_weight: longint; { вес MST} { инициализация и чтение данных } procedure init; var i, j, x, y, nn, z: longint; begin mst_weight := 0; assign(input, 'mst.in'); { assign(input, 'randw.in');} reset(input); read(n); read(m); for i := 1 to m do begin read(x, y, z); a[i].x := x; a[i].y := y; a[i].w := z; end; end; { обмен двух ребер (для сортировки) } procedure swap(var e1, e2: edge); var e: edge; begin e := e1; e1 := e2; e2 := e; end; { рандомизированная быстрая сортировка } procedure qsort(l, r: integer); var i, j, x: integer; begin i := l; { i - левая граница } j := r; { j - правая граница } x := a[l+random(r-l+1)].w; { x - случайный элемент из интервала } repeat while x > a[i].w do inc(i); { ищем элемент больший барьера } while x < a[j].w do dec(j); { ищем элемент меньший барьера } if i <= j then { указатели не пересекклись } begin swap(a[i], a[j]); { меняем элементы } inc(i); { продвигаем левый указатель } dec(j); { продвигаем правый указатель } end; until i > j; { до пересечения указателей } if l < j then qsort(l, j); { сортируем левый подмассив } if i < r then qsort(i, r); { сортируем правый подмассив } end; { построение mst (алгоритм Крускала) } procedure kruskal; var k, i, p, q: integer; begin qsort(1, m); { сортируем список ребер по неубыванию } for i := 1 to n do { цикл по вершинам } begin r[i] := i; { у вершина своя компонента связности } s[i] := 1; { размер компоненты связности } end; k := 0; { номер первого ребра + 1 } for i := 1 to n - 1 do { цикл по ребрам mst } begin repeat { ищем ребра из разных } inc(k); { компонент связности } p := a[k].x; q := a[k].y; while r[p] <> p do { ищем корень для p } p := r[p]; while r[q] <> q do { ищем корень для q } q := r[q]; until p <> q; writeln(a[k].x, ' ', a[k].y); { вывод ребра } inc(mst_weight, a[k].w); if s[p] < s[q] then { взвешенное объединение } begin { компоненты связности } r[p] := q; s[q] := s[q] + s[p]; end else begin r[q] := p; s[p] := s[p] + s[q]; end; end; end; begin init; writeln('minimal spanning tree (Kruskal''s algorithm)'); kruskal; writeln('mst weight = ', mst_weight); end.
файл ввода mst.in
C++
#include <iostream> #include <cstdio> #include <vector> #include <algorithm> using namespace std; struct edge { int x, y, w; edge(){} edge(int x, int y, int w): x(x), y(y), w(w) {} }; bool cmp(const edge& a, const edge& b) { return a.w < b.w; } vector <int> leader; int getLeader(int x) { if (x == leader[x]) return x; return leader[x] = getLeader(leader[x]); } bool unite(int x, int y) { x = getLeader(x); y = getLeader(y); if (x == y) return false; if (rand() % 2 == 0) swap(x, y); leader[x] = y; return true; } int main() { freopen("input.txt", "rt", stdin); freopen("output.txt", "wt", stdout); int n, m; scanf("%d%d", &n, &m); vector <edge> e(m); for (int i = 0; i < m; i++) { scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].w); e[i].x--; e[i].y--; } sort(e.begin(), e.end(), cmp); leader.resize(n); for (int i = 0; i < n; i++) leader[i] = i; vector <edge> ans; for (int i = 0; i < m; i++) { int x = e[i].x, y = e[i].y; if (unite(x, y)) ans.push_back(e[i]); } int sum = 0; for (int i = 0; i < ans.size(); i++) sum+=ans[i].w; printf("%d\n", sum); for (int i = 0; i < ans.size(); i++) printf("%d %d\n", ans[i].x + 1, ans[i].y + 1); return 0; }
C
первый вариант
#include <stdio.h> #include <stdlib.h> int NV; // Количество вершин в графе int NE; // Количество ребер в графе #define MAX_NODES 100 // Максимальное количество вершин #define MAX_EDGES 10 // Максимальное количество ребер в графе struct edge_t { int n1,n2; // направление int w; // вес ребра } edges[MAX_EDGES]; // Ребра графа int nodes[MAX_NODES]; // Вершины графа. Значение - "верхняя вершина" // Функция "сравнения" двух ребер, используемая для сортировки int cmp(const void *a,const void *b){ edge *c=(edge*)a, *d=(edge*)b; return c->w - d->w; } int last_n; // Функция получает цвет вершины n-й по порядку. // если nodes[n] < 0, то вершина n имеет цвет nodes[n] // если nodes[n] >= 0, то вершина n имеет цвет такой же, // как и вершина с номером nodes[n] int getColor(int n){ int c; if (nodes[n]<0) return nodes[last_n=n]; c = getColor(nodes[n]); nodes[n] = last_n; return c; } int main(){ int i; // Считываем вход scanf ("%d %d", &NV, &NE); for(i = 0; i < N; i++) nodes[i] = -1-i; for(i = 0; i < NE; i++) scanf("%d %d %d", &edges[i].n1, &edges[i].n2, &edges[i].w); // Алгоритм Крускала // Сортируем все ребра в порядке возрастания весов qsort(edges, NE, sizeof(edge_t), cmp); for(i = 0; i < NE; i++){ // пока не прошли все ребра int c2 = getColor(edges[i].n2); if ( getColor (edges[i].n1) != c2 ){ // Если ребро соединяет вершины различных цветов-мы его добавляем // и перекрашиваем вершины nodes [last_n] = edges[i].n2; printf ("%d %d %d\n", edges[i].n1, edges[i].n2, edges[i].w); } } return 0; }
Второй вариант
#include <stdio.h> #include <malloc.h> #include <stdlib.h> typedef struct { int from; // edge start vertex int to; // edge end vertex double w; // edge weight } edge_t; typedef struct set_t { struct set_t *p; // link on parent } set_t; int NS; // number of sets set_t *sets; // array of sets int NE; // number of edges edge_t *E; // array of edges int NV; // number of edges // compare function for sorting edges by weight int cmpw(edge_t *a, edge_t *b) { if(a->w > b->w ) return 1; if(a->w < b->w ) return -1; return 0; } set_t* get_set_id(set_t* s) { if(s == s->p ) return s; else { set_t *p = get_set_id(s->p); s->p = p; return p; } } set_t* join_sets(set_t *a, set_t *b) { a->p = b; return a; } void take_edge(int edge_id) { printf("%d %d %lf\n", E[edge_id].from, E[edge_id].to, E[edge_id].w); } int main() { int i; double W = 0; scanf("%d%d", &NV, &NE); E = (edge_t*) malloc(NE * sizeof(edge_t)); sets = (set_t*) malloc(NV * sizeof(set_t)); for(i = 0; i < NE ; i++) { scanf("%d%d%lf", &E[i].from, &E[i].to, &E[i].w); } // Sort edges by weight qsort(E, NE, sizeof(edge_t), (int (*)(const void*, const void*)) cmpw); // Create set of one-point sets NS = NV; for(i = 0; i < NS ; i++) sets[i].p = &sets[i]; // Extract next edge with minumum weight for(i=0; NS > 1 && i < NE; i++) { // if the edge can't be added to tree, then go o next edge if ( get_set_id ( &sets[E[i].from]) == get_set_id ( &sets[E[i].to]) ) continue; // add the edge to covering tree join_sets ( get_set_id (&sets[E[i].from] ), get_set_id ( &sets[E[i].to]) ); NS--; take_edge(i); W += E[i].w; } if(NS != 1) fprintf(stderr, "warning: Graph is not connected.\n"); printf("Covering tree weight = %lf\n", W); }
java
int mstKruskal(Edge[] edges) { DSF dsf = new DSF(vNum); // СНМ sort(edges); // Сортируем ребра int ret = 0; // результат for (Edge e : edges) // перебираем ребра в порядке возрастания if (dsf.union(e.u, e.v)) // если ребра принадлежат разным компонентам ret += e.w; // добавляем вес ребра к стоимости MST return ret; } // Класс ребра class Edge implements Comparable { int u; int v; int w; // Конструктор Edge(int u, int v, int w) { this.u = u; this.v = v; this.w = w; } // Компаратор @Override public int compareTo(Edge edge) { if (w != edge.w) return w < edge.w ? -1 : 1; return 0; } } // Класс СНМ class DSF { int[] set; // номер множества int[] rnk; // ранг DSF(int size) { set = new int [size]; rnk = new int [size]; for (int i = 0; i < size; i++) set[i] = i; } // Возвращает множество, которому принадлежит x int set(int x) { return x == set[x] ? x : (set[x] = set(set[x])); } // Если u и v лежат в разных множествах, то сливаем их и возвращаем true boolean union(int u, int v) { if ((u = set(u)) == (v = set(v))) return false; if (rnk[u] < rnk[v]) { set[u] = v; } else { set[v] = u; if (rnk[u] == rnk[v]) rnk[u]++; } return true; } }
C#
/***************************************************************** * File : Kruskal.cs * Purpose : Kruskal's algorithm implementation to find the edges and cost of the * minimal spanning tree. * Author : Oleksandr Krakovetskyi * * Website : www.msug.vn.ua * *****************************************************************/ namespace GraphMethods { using System; using System.Collections.Generic; using System.Linq; /// /// Class represents graph edge. /// public class Edge { public int U; public int V; public double Weight; } /// /// Implementation of Kruskal algorithm. /// public class Kruskal { private const int MAX = 100; private int _edgesCount; private int _verticlesCount; private List _edges; private int[,] tree; private int[] sets; public List Edges { get { return _edges; } } public int VerticlesCount { get { return _verticlesCount; } } public double Cost { get; private set; } public Kruskal(string input) { tree = new int[MAX, 3]; sets = new int[MAX]; string[] lines = input.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); _verticlesCount = int.Parse(lines[0]); _edgesCount = int.Parse(lines[1]); _edges = new List(); _edges.Add(null); for (int i = 2; i < lines.Count(); i++) { string[] line = lines[i].Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); _edges.Add(new Edge { U = int.Parse(line[0]), V = int.Parse(line[1]), Weight = double.Parse(line[2]) }); } for (int i = 1; i <= _verticlesCount; i++) sets[i] = i; } private void ArrangeEdges(int k) { Edge temp; for (int i = 1; i < k; i++) { for (int j = 1; j <= k - i; j++) { if (_edges[j].Weight > _edges[j + 1].Weight) { temp = _edges[j]; _edges[j] = _edges[j + 1]; _edges[j + 1] = temp; } } } } private int Find(int vertex) { return (sets[vertex]); } private void Join(int v1, int v2) { if (v1 < v2) sets[v2] = v1; else sets[v1] = v2; } public void BuildSpanningTree() { int k = _verticlesCount; int i, t = 1; this.ArrangeEdges(k); this.Cost = 0; for (i = 1; i <= k; i++) { for (i = 1; i < k; i++) if (this.Find(_edges[i].U) != this.Find(_edges[i].V)) { tree[t, 1] = _edges[i].U; tree[t, 2] = _edges[i].V; this.Cost += _edges[i].Weight; this.Join(_edges[t].U, _edges[t].V); t++; } } } public void DisplayInfo() { Console.WriteLine("The Edges of the Minimum Spanning Tree are:"); for (int i = 1; i < _verticlesCount; i++) Console.WriteLine(tree[i,1] + " - " + tree[i,2]); } } }
На вход алгоритма подадим такое описание графа:
4
6
1 2 1
2 3 3
3 4 2
4 1 5
1 3 4
2 4 6
где 4 - количество вершин, 6 - количество ребер, остальные строки - это номера вершин ребра и его вес.
Функция main консольного приложения имеет вид:
static void Main(string[] args) { Kruskal k = new Kruskal(@"4 6 1 2 1 2 3 3 3 4 2 4 1 5 1 3 4 2 4 6"); k.BuildSpanningTree(); Console.WriteLine("Cost: " + k.Cost); k.DisplayInfo(); Console.WriteLine("Press any key..."); Console.ReadKey(); } }