n个村庄间架设通信线路,每个村庄间的距离不同,如何架设最节省开销?
这个问题中,村庄可以抽象成节点,村庄之间的距离抽象成带权值的边,要求最节约的架设方案其实就是求如何使用最少的边、最小的权值和将图中所有的节点连接起来。 这就是一个最小代价生成树的问题,可以用Prim算法或kruskal算法解决。
算法的目标很明确,就是要在n个节点的图中,找出n-1个节点,并且节点之间连线的权值是最小的。因此总体思路如下:
/**
* @param a:图的邻接矩阵
*/
EdgeSet spanningTree(int[][] a){
// 结果集(边的集合)
EdgeSet solution = new EdgeSet();
// 选出n-1条边
int i = 0;
while( i<n && 还有未检查的边 ){
// 选出一条边
Edge edge = Select(a);
// 判断是否有回路
if ( !hasLoop(edge) ) {
solution.add( edge );
}
}
return solution;
}
上述为最小代价生成树的总体思路,其中选边方式(贪心准则)的不同,就产生不同的最小代价生成树算法。
一个边节点有一条边 和 一个终止节点组成。
/**
* 边节点(由一条边和一个终止节点构成)
*/
class ENode{
int id;// 终止节点的编号
int weight;// 边的权重
}
图用一个Map< String,List>表示,其中String表示节点的编号,List中存储以该节点为起点的所有边节点。
Map<String,List<ENode>>
贪心准则:将整个图分成两部分,一部分已选入生成树,另一部分在生成树之外。每次选的边要求一头在生成树之内,一头在生成树之外,并保证当前边是满足上述条件中最短的一条。重复上述操作,直到选出n-1条边为止。
第一步: 首先初始化数组: 1. mark的值全为false 2. nearest的值全为-1 3. lowcost的值全为Integer.MAX_VALUE。
mark[1] | mark[2] | mark[3] | mark[4] | mark[5] | mark[6] |
---|---|---|---|---|---|
false | false | false | false | false | false |
lowcost[1] | lowcost[2] | lowcost[3] | lowcost[4] | lowcost[5] | lowcost[6] |
---|---|---|---|---|---|
MAX | MAX | MAX | MAX | MAX | MAX |
nearest[1] | nearest[2] | nearest[3] | nearest[4] | nearest[5] | nearest[6] |
---|---|---|---|---|---|
-1 | -1 | -1 | -1 | -1 | -1 |
第二步:
mark[1] | mark[2] | mark[3] | mark[4] | mark[5] | mark[6] |
---|---|---|---|---|---|
true | false | false | false | false | false |
lowcost[1] | lowcost[2] | lowcost[3] | lowcost[4] | lowcost[5] | lowcost[6] |
---|---|---|---|---|---|
MAX | 6 | 1 | 5 | MAX | MAX |
nearest[1] | nearest[2] | nearest[3] | nearest[4] | nearest[5] | nearest[6] |
---|---|---|---|---|---|
-1 | 1 | 1 | 1 | 0 | 0 |
第三步:
/**
* prim算法
* @param graph:图的邻接矩阵
*/
void prim(Map<String,List<Edge>> graph){
// 初始化
Map<String,String> nearest = new HashMap<>();
Map<String,Integer> lowcost = new HashMap<>();
Map<String,Boolean> mark = new HashMap<>();
String k = null;
String end = null;// 记录最后一个节点的id,用于从后向前输出结果
for( String id : graph.keySet() ){
nearest.put( id, null );
lowcost.put( id, Integer.MAX_VALUE );
mark.put( id, false );
k = id;
}
mark.put( id, true );
// 寻找生成树的n-1条边
for(int i=1; i<=graph.size()-1; i++){
// 更新与k相邻的nearest
List<ENode> edges = graph.get( k );
for( ENode edge : edges ){
if ( !mark.get(edge.id) && edge.w < lowcost.get(edge.id) ) {
lowcost.put( edge.id, edge.w );
nearest.put( edge.id, k );
}
}
// 寻找当前lowcost中最短的边
int min = Integer.MAX_VALUE;
for( Map.Entry<String,Integer> entry : lowcost.entrySet() ){
if ( entry.getValue() < min ) {
min = entry.getValue();
k = entry.getKey();
}
}
mark.get( k, true );
end = k;
}
// 输出结果
for ( int i=0; i<graph.size(); i++ ) {
System.out.println( nearest.get(end)+"-"+end+"权值:"+lowcost.get(end) );
end = nearest.get(end);
}
}
若图中共有n个节点,那么Prim算法的时间复杂度为O(n^2)。
贪心准则:将所有的边按照权值递增的顺序排序,每次选一条权值最小的边纳入生成树中,若没有环路则选边成功,若有环路,则选下一条次小的边,直到选满n-1条边为止。