Kruskalův algoritmus slouží k nalezení minimální kostry grafu (kostry takové, že součet vah jejích hran je minimální). Algoritmus byl poprvé publikován v roce 1956 Josephem Kruskalem.
Princip
Kruskalův algoritmus nejprve setřídí hrany dle jejich váhy (od nejmenší) a následně přidává hrany do grafu takovým způsobem, aby nevznikl žádný cyklus (tj. procedura terminuje po přidání hran).
K zajištění acykličnosti si algoritmus pomocí datové struktury disjoint set udržuje pro každý uzel informaci o příslušnosti ke komponentě souvislosti. Disjoint set poskytuje dvě operace: union (spojí dvě komponenty souvislosti) a find (zjistí pro daný uzel příslušnost ke komponentě souvislosti).
Asymptotická složitost algoritmu
Časová složitost algoritmu je v případě použití řadicího algoritmu založeného na porovnávání . Pokud jsou hrany již předřazeny, nebo je možno k jejich seřazení použít řadicí algoritmus s lineární složitostí (např.
counting sort), tak je složitost Kruskalova algoritmu rovna
, kde
je inverzní Ackermannova funkce (odpovídá složitosti operací union a find).
Kód
/**
* Kruskaluv algoritmus
* @param graph graf
* @param w vahy jednotlivych hran
* @return minimalni kostra grafu
*/
SpanningTree kruskalAlgorithm(Graph graph, weights w)
SpanningTree tree //kostra
for Node n : graph do
makeSet(n) //kazdy uzel je v samostatnem setu
List edges = sort(graph.getEdges(), w) //seradi hrany podle vah
for Edge e in edges do
if findSet(e.start) != findSet(e.end) //pokud jsou pocatecni a koncovy uzel v ruznych setech (netvorime cyklus)
tree.add(e) //pridej hranu do kostry
union(e.start, e.end) //sety spojime v jeden
if tree.edgeCount() == graph.nodeCount() - 1 //pokud uz je kostra cela
break //konec pridavani hran
return tree
/**
* Kruskaluv algoritmus
* @author Pavel Micka
*/
public class KruskalAlgorithm {
/**
* Nalezne minimalni kostru neorientovaneho grafu o n uzlech (cisla uzlu
* 0, 1, ..., n-1)
* @param edges hrany neorientovaneho grafu
* @param nodeCount pocet uzlu neorientovaneho grafu (n)
* @return minimalni kostra
*/
public static List<Edge> kruskalAlgorithm(List<Edge> edges, int nodeCount) {
DisjointSet ds = new DisjointSet(nodeCount);
List<Edge> spanningTree = new ArrayList<Edge>();
Collections.sort(edges);
int i = 0;
while (i != edges.size() && spanningTree.size() != nodeCount - 1) {
Edge e = edges.get(i);
if(ds.find(e.getFrom()) != ds.find(e.getTo())){
spanningTree.add(e);
ds.union(e.getFrom(), e.getTo());
}
i++;
}
return spanningTree;
}
}
// ***************************** //
// * DATOVE STRUKTURY * //
// ***************************** //
/**
* Hrana neorientovaneho grafu
* @author Pavel Micka
*/
class Edge implements Comparable<Edge> {
private int from; //uzel z
private int to; //uzel do
private int weight; //vaha
public Edge(int from, int to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
/**
* Provede porovnani hran dle vahy (vyssi vaha == vetsi)
* @param o
* @return
*/
public int compareTo(Edge o) {
if (this.getWeight() > o.getWeight()) {
return 1;
} else if (this.getWeight() < o.getWeight()) {
return -1;
} else {
return 0;
}
}
/**
* @return the from
*/
public int getFrom() {
return from;
}
/**
* @param from the from to set
*/
public void setFrom(int from) {
this.from = from;
}
/**
* @return the to
*/
public int getTo() {
return to;
}
/**
* @param to the to to set
*/
public void setTo(int to) {
this.to = to;
}
/**
* @return the weight
*/
public int getWeight() {
return weight;
}
/**
* @param weight the weight to set
*/
public void setWeight(int weight) {
this.weight = weight;
}
}
/**
* Jednoducha implementace Union-Find problemu
* @author Pavel Micka
*/
class DisjointSet {
private Node[] nodes;
public DisjointSet(int nodeCount) {
nodes = new Node[nodeCount];
for (int i = 0; i < nodeCount; i++) {
nodes[i] = new Node();
nodes[i].id = i;
}
}
/**
* Provede sjednoceni komponent, ve kterych jsou uzly "a" a "b"
* Union provadi vzdy do A
* @param a cislo uzlu a
* @param b cislo uzlu b
* @return cislo reprezentanta sjednocene komponenty
*/
public int union(int a, int b) {
Node repA = nodes[find(a)];
Node repB = nodes[find(b)];
repB.parent = repA;
return repA.id;
}
/**
* Vrati reprezentanta zadaneho uzlu
* @param a cislo uzlu, jehoz reprezentanta hledame
* @return cislo reprezentanta uzlu
*/
public int find(int a) {
Node n = nodes[a];
int jumps = 0;
while (n.parent != null) {
n = n.parent;
jumps++;
}
if (jumps > 1) repair(a, n.id);
return n.id;
}
/**
* Provede opravu (compression) cesty
* @param a
* @param rootId
*/
private void repair(int a, int rootId) {
Node curr = nodes[a];
while (curr.id != rootId) {
Node tmp = curr.parent;
curr.parent = nodes[rootId];
curr = tmp;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < nodes.length; i++) {
builder.append(find(i) + " ");
}
return builder.toString();
}
/**
* Uzel n-arniho stromu
*/
private static class Node {
/**
* Rodic
*/
Node parent;
/**
* Identifikator uzlu
*/
int id;
}
}
Literatura
- KOLÁŘ, Josef. Teoretická informatika. 2. vyd. Praha : Česká informatická společnost, 2004. 205 s. ISBN 80-900853-8-5.