最小生成树 prim算法
在无向加权图中,n个顶点的最小生成树有n-1条边,这些边使得n个顶点之间可达,且总的代价最小。
prim算法是一种贪心算法,将全部的顶点划分为2个集合,每次总在2个集合之间中找最小的一条边,局部最优最终达到全局最优,这正是贪心的思想。
具体的描述参见相关书籍:
描述
从单一顶点开始,普里姆算法按照以下步骤逐步扩大树中所含顶点的数目,直到遍及连通图的所有顶点。
- 输入:一个加权连通图,其中顶点集合为V,边集合为E;
- 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {};
- 重复下列操作,直到Vnew = V:
- 在集合E中选取权值最小的边(u, v),其中u为集合Vnew中的元素,而v则不是(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
- 将v加入集合Vnew中,将(u, v)加入集合Enew中;
- 输出:使用集合Vnew和Enew来描述所得到的最小生成树。
参考:维基百科 prim算法 http://zh.wikipedia.org/wiki/Prim%E6%BC%94%E7%AE%97%E6%B3%95
prim的实现:
1 //prim最小生成树
2
3 public Edge[] getEdges(int position){ //返回从顶点position开始的最小生成树的边数组
4 Edge[] edges = new Edge[size()-1]; //最小生成树里边数为n-1,!!!!size()
5 VNodes[position].setVisited(true); //将原来的遍历中用的标志在这分离集合
6 for(int i = 0;i < edges.length;i++) //找n-1条边出来
7 {
8 Edge edge = getMinEdge(VNodes); //在当前分离的2个集合之间找到最小边
9 edges[i] = edge;
10 VNodes[edge.getEnd()].setVisited(true);//将新添加的边的另一端的顶点分离出来
11 }
12 return edges;
13 }
14
15 private Edge getMinEdge(VNode[] VNodes){ //从分离的2个集合之间求出最小的边
16 return min;
17 }
18
19 private boolean hasEdge(VNode node){ //判断某个标记true的顶点跟另一个集合之间是否有边
20 return false;
21 }
22
23
24 private Edge getMinEdgeFrom(VNode node){ //如果有边(前提),求出这个顶点到对方集合的最小边
25 return min;
26 }
27
28
因为我存的是顶点,所以还要找边,比较麻烦一点,如果在图中记录了一个边得数组,就可以直接在边得数组里面去找最小边。
解决问题的思路是先很容易就可以写出上述最小生成树的逻辑实现,然后去一一实现支持它的方法。
private Edge getMinEdge(VNode[] VNodes){ //从分离的2个集合之间求出最小的边
Edge min = null;
//for(int i = 0;i < VNodes.length;i++)
for(int i = 0;i < size();i++)
{
if(VNodes[i].getVisited() && hasEdge(VNodes[i]))//从true集合向false集合找
{
Edge temp = getMinEdgeFrom(VNodes[i]);
if(min == null)//第一次的初始化min
min = temp;
else if(temp.getLen() < min.getLen())
min = temp;
}
}
return min;
}
private boolean hasEdge(VNode node){ //判断某个标记true的顶点跟另一个集合之间是否有边
if(node.getFirst() == null)
return false;
else
{
Edge temp = node.getFirst();
while(temp != null)
{
int index = temp.getEnd();
if(VNodes[index].getVisited() == false)
return true;
else temp = temp.getNext();
}
}
return false;
}
private Edge getMinEdgeFrom(VNode node){ //如果有边(前提),求出这个顶点到对方集合的最小边
Edge min = null;
Edge temp = node.getFirst();
while(temp != null)
{
int index = temp.getEnd();
if(VNodes[index].getVisited() == false)//说明此时的temp是到对方集合的一条边
{
if(min == null)
min = temp;
else if(temp.getLen() < min.getLen())
min = temp;
}
temp = temp.getNext();
}
return min;
}
然后添加一个图来测试一下:
public static void main(String[] args) {
// TODO Auto-generated method stub
UnDirectedGraph g = new UnDirectedGraph();
for(int i = 1;i < 7;i++)
g.addVNode("V" + i);
g.addEdge(0, 1, 6);
g.addEdge(0, 2, 1);
g.addEdge(0, 3, 5);
g.addEdge(1, 2, 5);
g.addEdge(1, 4, 5);
g.addEdge(2, 3, 5);
g.addEdge(2, 4, 6);
g.addEdge(2, 5, 4);
g.addEdge(3, 5, 2);
g.addEdge(4, 5, 6);//严蔚敏数据结构中的那个图
Edge[] edges = g.getEdges(0);
System.out.println("输出最小生成树的边");
for(int i = 0;i < edges.length;i++)
{
int start = edges[i].getStart();
int end = edges[i].getEnd();
System.out.println("边: " + g.VNodes[start].getVNode() +"---" + g.VNodes[end].getVNode()
+ " 长度:" + edges[i].getLen());
}
}
构造了如下的图(清华《数据结构》 最小生成树的那个图):
结果:
输出最小生成树的边:
边: V1---V3 长度:1
边: V3---V6 长度:4
边: V6---V4 长度:2
边: V3---V2 长度:5
边: V2---V5 长度:5
可以发现这5条边是跟图中生成过程的顺序一样,依次找到放入数组的。