无向图最小生成树的Prim算法实现
前言
本文讲解最小生成树的定义及实现原理,并根据最小生成树原理介绍贪心算法,以及讲解在贪心算法基础上延伸出来的Prim算法的思想及代码实现。
零、无向图的约定
为了更好理解最小生成树,假设采用的图是连通图,并且图中所有边的权重不一样。
一、生成树定义
图的生成树是它的一棵含有其所有顶点的无环连通子图,一幅图的生成树可以有很多。
二、最小生成树定义
最小生成树是图的所有生成树中的一棵权值(树中所有边的权重之和)最小的生成树
三、生成树性质
- 用一条边接树中的任意两个顶点都会产生一个新的环
- 从树中删除任意一条边,将会得到两棵独立的树
四、最小生成树切分定理
切分定理用于找出一幅连通图中的最小生成树,任何最小生成树的方法都是基于切分定理。
切分定理包含两个概念:
- 切分:将图的所有顶点按照某些规则分为两个非空且没有交集的集合。
- 横切边:连接两个属于不同集合的顶点的边称之为横切边。
- 切分定理就是在一副加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图中的最小生成树。
因此切分定理包括两个关键点:1. 如何切分? 2.找到权重最小的横切边
五、最小生成树贪心算法
根据切分定理找到图中的最小生成树就是贪心算法,它使用切分定理找到最小生成树的一条边,不断的重复直到找到最小生成树的所有边。如果图有V个顶点,那么需要找到V-1条边,就可以表示该图的最小生成树。
贪心算法只是一种思想,在这里可以理解为它用来更好的解释切分定理而已。通过找到每个顶点的局部最优解,然后逐步贪心寻找,最终由局部最优推出全局最优解,即最小生成树。
六、Prim算法
Prim算法始终将图中的顶点切分成两个集合,最小生成树顶点和非最小生成树顶点,通过不断的重复做某些操作,可以逐渐将非最小生成树中的顶点加入到最小生成树中,直到所有的顶点都加入到最小生成树中。
1.切分定理的其中一个关键点是如何切分?Prim算法给出切分方法如下:
Prim算法首先任意选择一个顶点P作为一个集合(也是最小生成树的第一个顶点),其余顶点作为另一个集合;然后根据切分定理,获得与顶点P形成边的另一个顶点M(与顶点P相连的所以顶点中,与顶点M组成的边的权值最小);然后把M顶点加入P顶点的集合,剩余顶点成为另一个集合,再根据切分定理进行找最小生成树的其它顶点,如此往复,直至最后所有顶点都在一个集合中,这时最小生成树完成。
2.Prim算法代码实现
/**
* 基于Prim算法实现加权无向图中最小生成树的查找。
*/
public class PrimMST {
private Edge[] edgeTo;//索引代表顶点,值表示当前顶点和最小生成树之间的最短边
private double[] distTo;//索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重
private boolean[] marked;//索引代表顶点,如果当前顶点已经在最小生成树中,则值为true,否则为false
private IndexMinPriorityQueue<Double> pq;//存放树中顶点与非树中顶点之间的有效横切边(索引代表顶点,值代表权重)
//根据一副加权无向图,创建最小生成树计算对象;
public PrimMST(EdgeWeightedGraph G)
{
this.edgeTo=new Edge[G.V()];
this.distTo=new double[G.V()];
for (int i = 0; i < distTo.length; i++) {
distTo[i]=Double.POSITIVE_INFINITY;
}
this.marked=new boolean[G.V()];
this.pq=new IndexMinPriorityQueue<Double>(G.V());
//初始将顶点0作为最小生成树的结点
distTo[0]=0.0;
pq.insert(0,0.0);
//遍历索引最小优先队列,将最小权重对应的顶点加入最小生成树中
while (!pq.isEmpty())
{
visit(G,pq.delMin());
}
}
//将顶点v添加到最小生成树中,并且更新数据
private void visit(EdgeWeightedGraph G, int v)
{
//将顶点v添加到最小生成树中
marked[v]=true;
//更新数据
//1.查找当前顶点v邻接表中所有顶点,找到权重最小的边
for (Edge edge : G.adj(v)) {
//查找邻接表中的顶点是否已近在最小生成树中,如果在,则不再处理该边
int w = edge.other(v);
if (marked[w])
continue;
//判断边的权重是否小于 顶点w与最小生成树中顶点相连的边
if (edge.weight()<distTo[w])
{
//如果已经存在顶点与v顶点相连,则需要比较它的权重是否小于顶点w与v的权重,如果是,则替换顶点w的权重与边
distTo[w]=edge.weight();
edgeTo[w]=edge;
//如果顶点w与顶点v之间已经存在权重值,则修改权重值为当前边的权重值,否则,插入当前边的权重值
if (pq.contains(w))
pq.changeItem(w,edge.weight());
else
pq.insert(w,edge.weight());
}
}
}
//获取最小生成树的所有边
public Queue<Edge> edges()
{
Queue<Edge> edges = new Queue<>();
for (int i = 0; i < edgeTo.length; i++) {
if (edgeTo[i]!=null)
edges.enqueue(edgeTo[i]);
}
return edges;
}
}