最小生成树
1. 是一棵树:无回路,V个顶点一定有V-1条边
2. 是生成树:从原始图中生成的,包含全部顶点
3. 最小: 边的权重和最小
一个图的最小生成树可能不唯一
解决办法:贪心算法
1. 什么是贪:每一步都是最好的
2. 什么是好:权重最小的边
3. 但是不是直接排序去最小的边,还需要约束:只能用图里面有的边,只能正好用掉V-1条边,不能又回路
具体有2种办法:
(1)Prim算法:从一个跟节点开始慢慢长大,每次添加到当前树最近的节点,部分步骤如下
解释:最开始随便选择一个跟节点(因为所有的节点都要收罗进去的嘛,先选谁都没有关系),然后添加该节点发出的最短的边,然后把连接起来的节点看成一个节点(或者说目前形成的树看成一个节点),继续下去
为什么一定是加从当前树发出的最短一条边呢?
证明:用反证法,假设这条最短的边连接的是顶点A,B,如果没有加这条最短的边,那么A,B一定是间接通过其他节点连接在一起的,那我一定可以找到一棵权重更小的树
如果权重最小的边有好几条呢?那就随便选取一条,无非最后的最小生成树有很多中情况而已
可见要想获得最小生成树,贪心是必然的
1. Prim算法和Dijkstra算法是很相似的,每次都是从没有收罗的点钟选择一个distance最小的节点,并收罗进去,区别在于Dijkstra的distance是到源节点的距离,Prim是到目前的树的最小距离
2. 怎么记录最小生成树:数组,记录parent就行
3. 怎么判断是否存在最小生成树(即图是否联通):判断跳出循环后MST中收罗的顶点个数是不是等于V
4. Prim是从顶点突破的,所以适合于稠密图,如果是稀疏图,就用Kruskal算法
(2)Kruskal算法:将森林合并成树,操作的是边
每次都加入权重最小的边(但是不能构成回路,证明也可以用反正法),通过加入这条边把2个不连通的森林合并成一棵树,
算法伪代码如下,可见该算法是适合用边比较少的稀疏图:
例子:
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。
输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。
输入样例:
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
输出样例:
12
Prim版:
package LuluTong;
import java.util.*;
/*
* 考察最小生成树
*/
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
int[][] adj = new int[n+1][n+1];
for(int i=0; i<m; i++) {
int x = sc.nextInt(), y = sc.nextInt(), d = sc.nextInt();
adj[x][y] = d; adj[y][x] = d;
}
// Prim
int rst = prim(adj, n, m);
System.out.println(rst);
}
private static int prim(int[][] adj, int n, int m) {
if(m < n-1) return -1;
int[] dist = new int[1+n];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[1] = 0;
for(int j=1; j<=n; j++)
if(dist[j]!=0 && adj[1][j] != 0) dist[j] = adj[1][j];
int cost = 0;
while(true) {
int minDist = Integer.MAX_VALUE, minVertx = -1;
for(int i=1; i<=n; i++)
if(dist[i]!=0 && dist[i] < minDist) {
minVertx = i;
minDist = dist[i];
}
if(minVertx == -1) break;
cost += minDist;
dist[minVertx] = 0;
for(int i=1; i<=n; i++)
if(dist[i]!=0 && adj[minVertx][i]!=0 && adj[minVertx][i]<dist[i]) {
dist[i] = adj[minVertx][i];
}
}
for(int i=1; i<=n; i++)
if(dist[i] != 0) return -1;
return cost;
}
}
Kruskal版:
package LuluTong;
import java.util.*;
public class l2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
int[][] adj = new int[n+1][n+1];
for(int i=0; i<m; i++) {
int x = sc.nextInt(), y = sc.nextInt(), d = sc.nextInt();
adj[x][y] = d;
}
// Kruskal
int rst = Kruskal(adj, n, m);
System.out.println(rst);
}
private static int Kruskal(int[][] adj, int n, int m) {
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(m, new Comparator<int[]>(){
public int compare(int[] o1, int[] o2) {
return o1[0]-o2[0];
}
});
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(adj[i][j] != 0) pq.add(new int[]{adj[i][j], i, j});
int[] path = new int[1+n];
int cnt = 0, cost = 0;
while(true) {
if(pq.isEmpty()) break;
if(cnt == n-1) break;
int[] min = pq.remove();
int root1 = find(path, min[1]), root2 = find(path, min[2]);
if(root1 != root2 ) {
cnt ++;
cost += min[0];
path[root1] = root2; // 3,6覆盖了3,5
}
}
if(cnt != n-1) return -1;
return cost;
}
private static int find(int[] path, int i) {
while(path[i] != 0) i=path[i];
return i;
}
}