Prim算法的第一个实现 Lazy Prim

java实现pmt公式 java prim算法_最小生成树

             

java实现pmt公式 java prim算法_java实现pmt公式_02

  从0开始:

java实现pmt公式 java prim算法_权值_03

此时与0相连的边就放入最小堆中。

思想:例如从0开始寻找最小切分边,此时的与7连接的边就是最小生成边,所以就将7这个节点变为和0一样的颜色:

java实现pmt公式 java prim算法_最小堆_04

此时就产生了新的横切边(蓝色部分):

java实现pmt公式 java prim算法_最小生成树_05

将新的横切边放入最小堆中,从堆中找出最小的权值:与7连接的1的权值为0.19

java实现pmt公式 java prim算法_权值_06

      此时新加入了1节点,所以此时就有了新的横切边:     

java实现pmt公式 java prim算法_最小堆_07

此时再我们堆中去寻找最小边的存在,此时最小边为0和2的边,权值为0.26。

java实现pmt公式 java prim算法_最小堆_08

              

java实现pmt公式 java prim算法_最小生成树_09

当把2节点加入红色区后,连接1和2,与连接2和7的这两条边就不是横切边了。此时这两条边就不是最小生成树的候选边了。

依次类推。

java实现pmt公式 java prim算法_权值_10

  

java实现pmt公式 java prim算法_java实现pmt公式_11

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放入堆中:

java实现pmt公式 java prim算法_java实现pmt公式_12

      此时与0相连的边权值就放进堆中:    

java实现pmt公式 java prim算法_最小堆_13

 

此时就能找出最小生成边为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替换进去:

java实现pmt公式 java prim算法_权值_14

以此类推,直至所有节点都被访问。

java实现pmt公式 java prim算法_java实现pmt公式_15

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();
    }
}