Prim算法的第一个实现 Lazy Prim
从0开始:
此时与0相连的边就放入最小堆中。
思想:例如从0开始寻找最小切分边,此时的与7连接的边就是最小生成边,所以就将7这个节点变为和0一样的颜色:
此时就产生了新的横切边(蓝色部分):
将新的横切边放入最小堆中,从堆中找出最小的权值:与7连接的1的权值为0.19
此时新加入了1节点,所以此时就有了新的横切边:
此时再我们堆中去寻找最小边的存在,此时最小边为0和2的边,权值为0.26。
当把2节点加入红色区后,连接1和2,与连接2和7的这两条边就不是横切边了。此时这两条边就不是最小生成树的候选边了。
依次类推。
import sun.jvm.hotspot.asm.Arithmetic;
import java.util.Vector;
// 使用Prim算法求图的最小生成树
public class LazyPrimMST<Weight extends Number & Comparable> {
private WeightedGraph<Weight> G; // 图的引用
private MinHeap<Edge<Weight>> pq; // 最小堆, 算法辅助数据结构
private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问
private Vector<Edge<Weight>> mst; // 最小生成树所包含的所有边
private Number mstWeight; // 最小生成树的权值
// 构造函数, 使用Prim算法求图的最小生成树
public LazyPrimMST(WeightedGraph<Weight> graph){
// 算法初始化
G = graph;
pq = new MinHeap<Edge<Weight>>(G.E());
marked = new boolean[G.V()];
mst = new Vector<Edge<Weight>>();
// Lazy Prim
visit(0);
while( !pq.isEmpty() ){
// 使用最小堆找出已经访问的边中权值最小的边
Edge<Weight> e = pq.extractMin();
// 如果这条边的两端都已经访问过了, 则扔掉这条边
if( marked[e.v()] == marked[e.w()] ){
continue;
}
// 否则, 这条边则应该存在在最小生成树中
mst.add( e );
// 访问和这条边连接的还没有被访问过的节点
if( !marked[e.v()] ){
visit( e.v() );
}else{
visit( e.w() );
}
}
// 计算最小生成树的权值
mstWeight = mst.elementAt(0).wt();
for( int i = 1 ; i < mst.size() ; i ++ ){
mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
}
}
// 访问节点v
private void visit(int v){
assert !marked[v];
marked[v] = true;
// 将和节点v相连接的所有未访问的边放入最小堆中
for( Edge<Weight> e : G.adj(v) ){
if( !marked[e.other(v)] ){
pq.insert(e);
}
}
}
// 返回最小生成树的所有边
Vector<Edge<Weight>> mstEdges(){
return mst;
};
// 返回最小生成树的权值
Number result(){
return mstWeight;
};
}
对于Lazy Prim,当ipq不为空是,所有元素e都会入栈,extractMin操作为O(logE),visit操作在邻接矩阵中的为O(v^2)。insert为O(logE)。
所以Lazy Prim的时间复杂度为O(ElogE)。
优化Lazy Prim为O(ElogV):
思想:虽然横切边有很多,所以只要找到每一个节点的最小横切边即可。
所以此时需要一个数据结构来存储一个节点最小的横切边,而且还要可以更新每个节点的最小的最小横切边,所以就要使用到最小索引堆IndexMinHeap这样的数据结构。
思路分析:对于堆只要开辟节点这么多的空间即可。
1.从0节点开始,将0放入堆中:
此时与0相连的边权值就放进堆中:
此时就能找出最小生成边为0.16,此时的7就可以加入0的红色阵营中。
2.再将7相连的横切边加入堆中,首先从7的邻边1开始看,由于1没有被遍历所以将权值0.19放入堆中,如此类推,如果遇到例如和7相连的4节点的权值为0.37,此时堆中已经有0和4相连的邻边,不过0.37小于0.38,随意此时的0.38的这条边就不是横切边了,将0.37替换进去:
以此类推,直至所有节点都被访问。
import java.util.Vector;
// 使用优化的Prim算法求图的最小生成树
public class PrimMST<Weight extends Number & Comparable> {
private WeightedGraph G; // 图的引用
private IndexMinHeap<Weight> ipq; // 最小索引堆, 算法辅助数据结构
private Edge<Weight>[] edgeTo; // 访问的点所对应的边, 算法辅助数据结构
private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问
private Vector<Edge<Weight>> mst; // 最小生成树所包含的所有边
private Number mstWeight; // 最小生成树的权值
// 构造函数, 使用Prim算法求图的最小生成树
public PrimMST(WeightedGraph graph){
G = graph;
assert( graph.E() >= 1 );
ipq = new IndexMinHeap<Weight>(graph.V());
// 算法初始化
marked = new boolean[G.V()];
edgeTo = new Edge[G.V()];
for( int i = 0 ; i < G.V() ; i ++ ){
marked[i] = false;
edgeTo[i] = null;
}
mst = new Vector<Edge<Weight>>();
// Prim
visit(0);
while( !ipq.isEmpty() ){
// 使用最小索引堆找出已经访问的边中权值最小的边
// 最小索引堆中存储的是点的索引, 通过点的索引找到相对应的边
int v = ipq.extractMinIndex();
assert( edgeTo[v] != null );
mst.add( edgeTo[v] );
visit( v );
}
// 计算最小生成树的权值
mstWeight = mst.elementAt(0).wt();
for( int i = 1 ; i < mst.size() ; i ++ ){
mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
}
}
// 访问节点v
void visit(int v){
assert !marked[v];
marked[v] = true;
// 将和节点v相连接的未访问的另一端点, 和与之相连接的边, 放入最小堆中
for( Object item : G.adj(v) ){
Edge<Weight> e = (Edge<Weight>)item;
int w = e.other(v);
// 如果边的另一端点未被访问
if( !marked[w] ){
// 如果从没有考虑过这个端点, 直接将这个端点和与之相连接的边加入索引堆
if( edgeTo[w] == null ){
edgeTo[w] = e;
ipq.insert(w, e.wt());
}
// 如果曾经考虑这个端点, 但现在的边比之前考虑的边更短, 则进行替换
else if( e.wt().compareTo(edgeTo[w].wt()) < 0 ){
edgeTo[w] = e;
ipq.change(w, e.wt());
}
}
}
}
// 返回最小生成树的所有边
Vector<Edge<Weight>> mstEdges(){
return mst;
}
// 返回最小生成树的权值
Number result(){
return mstWeight;
}
// 测试 Prim
public static void main(String[] args) {
String filename = "testG1.txt";
int V = 8;
SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);
// Test Prim MST
System.out.println("Test Prim MST:");
PrimMST<Double> primMST = new PrimMST<Double>(g);
Vector<Edge<Double>> mst = primMST.mstEdges();
for( int i = 0 ; i < mst.size() ; i ++ ){
System.out.println(mst.elementAt(i));
}
System.out.println("The MST weight is: " + primMST.result());
System.out.println();
}
}