一、问题
最小生成树解决的问题如下所示:
假设要在 n 个城市之间建立通讯联络网,则连通 n 个城市只需要修建 n-1条线路,如何在最节省经费的前提下建立这个通讯网?
使用Prime算法构造最小生成树,将生成树上的边的权值累加即可得到最小值。
二、Prime算法核心思想
取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。在添加的顶点 w 和已经在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。
动态图演示:
三、算法实现(Java)
import java.util.Scanner; public class Prime { private final static int maxNodeValue=(1<<31)-1; public static void main(String[] args){ Scanner scanner=new Scanner(System.in); int[][] map=new int[101][101]; while(scanner.hasNext()){ init(map); int nodeCount=scanner.nextInt(); int edgeCount=scanner.nextInt(); for(int i=0;i<edgeCount;i++){ int node1=scanner.nextInt(); int node2=scanner.nextInt(); int edgeValue=scanner.nextInt(); map[node1][node2]=edgeValue; map[node2][node1]=edgeValue; } int cost=prime(nodeCount,map); System.out.println(cost); } } public static int prime(int nodeCount,int[][] map){ int[] lowcost=new int[101]; int cost=0; lowcost[1]=-1; for(int i=2;i<=nodeCount;i++){ lowcost[i]=map[1][i]; } for(int i=1;i<=nodeCount-1;i++){ int min=maxNodeValue; int k=0; for(int j=1;j<=nodeCount;j++){ if(min>lowcost[j]&&lowcost[j]!=-1){ min=lowcost[j]; k=j; } } cost+=min; lowcost[k]=-1; for(int j=1;j<=nodeCount;j++){ if(lowcost[j]!=-1&&lowcost[j]>map[k][j]){ lowcost[j]=map[k][j]; } } } return cost; } public static void init(int[][] map){ for(int i=0;i<map.length;i++){ for(int j=0;j<map[i].length;j++){ map[i][j]=maxNodeValue; } } } }
这里使用lowcost数组保存非生成树上的节点到生成树的最小值,每次添加一个节点到生成树,都需要刷新lowcost数组。
四、测试数据
输入
6 10
1 2 6
2 5 3
5 6 6
6 4 2
4 1 5
3 1 1
3 2 5
3 5 6
3 6 4
3 4 5
输出
15
6 10
1 2 6
2 5 3
5 6 6
6 4 2
4 1 5
3 1 1
3 2 5
3 5 6
3 6 4
3 4 5
输出
15
五、ACM
题目链接:http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2144
AC代码:
import java.util.Scanner; import java.util.Map; import java.util.HashMap; public class Main { private final static int maxNodeValue=(1<<31)-1; public static void main(String[] args){ Scanner scanner=new Scanner(System.in); int[][] map=new int[101][101]; while(scanner.hasNext()){ init(map); int nodeCount=scanner.nextInt(); int edgeCount=scanner.nextInt(); for(int i=0;i<edgeCount;i++){ int node1=scanner.nextInt(); int node2=scanner.nextInt(); int edgeValue=scanner.nextInt(); if(map[node1][node2]>edgeValue){ map[node1][node2]=edgeValue; map[node2][node1]=edgeValue; } } int cost=prime(nodeCount,map); System.out.println(cost); } } public static int prime(int nodeCount,int[][] map){ int[] lowcost=new int[101]; int cost=0; lowcost[1]=-1; for(int i=2;i<=nodeCount;i++){ lowcost[i]=map[1][i]; } for(int i=1;i<=nodeCount-1;i++){ int min=maxNodeValue; int k=0; for(int j=1;j<=nodeCount;j++){ if(min>lowcost[j]&&lowcost[j]!=-1){ min=lowcost[j]; k=j; } } cost+=min; lowcost[k]=-1; for(int j=1;j<=nodeCount;j++){ if(lowcost[j]!=-1&&lowcost[j]>map[k][j]){ lowcost[j]=map[k][j]; } } } return cost; } public static void init(int[][] map){ for(int i=0;i<map.length;i++){ for(int j=0;j<map[i].length;j++){ map[i][j]=maxNodeValue; } } } }
这道题目出的实际上并不好,有默认的规则:
- 顶点的个数=最大值,顶点值一定从0开始,依次递增到最大值(顶点的个数)
- 默认从0开始加入生成树
- 默认有可能出现输入相同边两次的情况,这时候取最小值