主要思想
按照边的权重顺序(从小到大)处理它们,将边加入到最小生成树中,加入的边不会和已经加入的边构成环,直到树中V-1条边为止,这些一开始并不一定是互相连接的,但是后面会慢慢逐渐由一片森林组成一颗树,也就是图的最小生成树
定理: Kruskal算法能够计算任意加权连通图的最小生成树
证明: 因为下一条被加入的边不会与最小生成树中已经存在的边构成环,那么它就跨越了和树中顶点相邻的顶点组成的集合的补集所构成的一个切分。因为加入的这条边不会构成果环、它是目前唯一已知的横切边切是按照权重选择的边,所以它必然是最小的横切边。 因此,该算法能够连续选择权重最小的横切边。
实现
Prim算法是一条边一条边的来构造最小生成树,每一步都为一颗树添加一条边;Kruskal算法也是一条一条边的构造,但是在构造的时候它寻找的边会连接一片森林中的两棵树。 我们从一片由V颗顶点组成的森林中不断用可以找到的最小的边将两棵树合并,直到只剩下一棵树,它就是最小生成树
简单的说就是两个步骤:
将边按权重排序,然后再逐渐找出合适的边
代码实现
public class KruskallMST {
private Queue<Edge> mst;
public KruskallMST(EdgeWeightGraph G) {
mst = new LinkedList<Edge>();
MinPQ<Edge> pq = new MinPQ<Edge>();
for(Edge e:G.edges()) {
pq.insert(e);
}
QuickUnion uf = new QuickUnion(G.V());
while(!pq.isEmpty()&&mst.size()<G.V()-1) {
Edge e = pq.delMin();
System.out.println("删除最小节点"+e);
int v = e.either(),w = e.other(v);
if(uf.connected(v, w))
continue;
uf.union(v, w);
System.out.println("添加边:"+e);
mst.add(e);
}
}
public Iterable<Edge> edges(){
return mst;
}
}
联合查找,用于判断两个元素是否属于同一子集这里用的是quick-find算法
public class UF {
private int[] id; //分量id
private int count; //分量数量
public UF(int n) {
count = n;
id = new int[n];
for(int i = 0;i<n;i++) {
id[i] = i;
}
}
/**
*
* @param p
* @param q
* 连接两个节点 把index较小的作为flag
*/
public void union(int p, int q) {
int pID = find(p);
int qID = find(q);
if(pID == qID) return;
for(int i = 0;i<id.length;i++) {
if(id[i]==pID)
id[i] = qID;
}
count--;
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public int find(int a) {
return id[a];
}
public int count() {
return count;
}
}
最小优先队列,用于保存所有的边,再通过删除最小边来得到权重小的横切边
public class MinPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int N = 0;
public MinPQ(int initCapacity) {
pq = (Key[]) new Comparable[initCapacity + 1];
}
public MinPQ() {
this(1);
}
public void insert(Key key) {
if (N == pq.length - 1) {
resize(2 * pq.length);
}
N++;
pq[N] = key;
swim(N);
}
/**
* @param i
*/
private void resize(int i) {
Key[] temp = (Key[]) new Comparable[i];
for (int j = 0; j < pq.length; j++) {
temp[j] = pq[j];
}
pq = temp;
}
public Key delMin() {
exch(pq, 1, N);
Key min = pq[N];
pq[N] = null;
N--;
skin(1);
return min;
}
private void swim(int i) {
while (i > 1 && greater(i / 2, i)) {
exch(i, i / 2);
i = i / 2;
}
}
private void exch(int i, int j) {
Key temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
}
/**
* @param i
*/
private void skin(int i) {
while (2 * i <= N) {
int k = 2 * i;
if (k < N && greater(k, k + 1)) { // 比较右节点小 取右节点
k++;
}
if (greater(k, i)) { // 如果孩子节点大 不需要调整了
break;
}
exch(pq, i, k);
i = k;
}
}
private boolean greater(int i, int k) {
return pq[i].compareTo(pq[k]) > 0;
}
/**
* @param k
* @param i
*/
private static void exch(Comparable[] v, int k, int i) {
Comparable temp = v[i];
v[i] = v[k];
v[k] = temp;
}
public static void show(Comparable[] a) {
for (int i = 1; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
/**
* @param i
* @param k
* @return
*/
private static boolean greater(Comparable i, Comparable k) {
// TODO Auto-generated method stub
System.out.println(i + " " + k);
return i.compareTo(k) > 0;
}
public int size() {
return N;
}
public boolean isEmpty() {
if (N == 0) {
return true;
}
return false;
}
}
测试
public class Test {
public static void main(String[] args) {
EdgeWeightGraph graph = new EdgeWeightGraph(8);
graph.addEdge(new Edge(4, 5, 0.35));
graph.addEdge(new Edge(4, 7, 0.37));
graph.addEdge(new Edge(5, 7, 0.28));
graph.addEdge(new Edge(0, 7, 0.16));
graph.addEdge(new Edge(1, 5, 0.32));
graph.addEdge(new Edge(0, 4, 0.38));
graph.addEdge(new Edge(2, 3, 0.17));
graph.addEdge(new Edge(1, 7, 0.19));
graph.addEdge(new Edge(0, 2, 0.26));
graph.addEdge(new Edge(1, 2, 0.36));
graph.addEdge(new Edge(1, 3, 0.29));
graph.addEdge(new Edge(2, 7, 0.34));
graph.addEdge(new Edge(6, 2, 0.40));
graph.addEdge(new Edge(3, 6, 0.52));
graph.addEdge(new Edge(6, 0, 0.58));
graph.addEdge(new Edge(6, 4, 0.93));
Queue<Edge> q = new LinkedList<Edge>();
KruskallMST km = new KruskallMST(graph);
q = (Queue<Edge>) km.edges();
System.out.println("最小生成树:");
for (Edge edge : q) {
System.out.println(edge);
}
}
}
结果
最小生成树:
0-7 0.16
2-3 0.17
1-7 0.19
0-2 0.26
5-7 0.28
4-5 0.35
6-2 0.40
实际Kruskal算法比Prim算法要慢一点,因为它在处理两个算法都要完成的优先队列之外还要进行connect()操作
Prim算法链接:https://blog.csdn.net/qq_38262968/article/details/93165671