最小生成樹-普利姆算法lazy實現

算法描述

lazy普利姆算法的步驟

1.從源點s出發,遍歷它的鄰接表s.Adj,將所有鄰接的邊(crossing edges)加入優先隊列Q;
2.從Q出隊最輕邊,將此邊加入MST.
3.考察此邊的兩個端點,對兩個端點重複第1步.

示例

從頂點0開始,遍歷它的鄰接表:邊0-7、0-2、0-4、0-6會被加入優先隊列Q.
頂點0的鄰接表搜索完畢後,邊0-7是最輕邊,所以它會出隊,並加入MST.
如下圖:
這裏寫圖片描述
邊0-7出隊後,開始考察邊的兩個端點:

頂點0已經訪問過了,跳過;
頂點7還未探索,開始探索頂點7.對7的鄰接表進行訪問和第一步類似.
我們找到最輕邊7-1並加入MST

如下圖:

這裏寫圖片描述

對每條邊重複,當所有邊都考察完畢,我們就得到了最小生成樹,如下圖:
這裏寫圖片描述

時間複雜度

掃描所有邊會耗時O(E ).
由於所有的邊都會入隊,優先隊列調整的操作耗時O(logE ).
那lazy方式最差就是O(ElogE ).
其中E 是圖的邊數.

算法實現

算法的第一步,將源點s所有的鄰接的邊加入Q,如下:

    /**
     * 找出從源點出發的所有的crossing edges,並用一個優先隊列維護他們
     *
     * 原理:
     * 將對未訪問的鄰接點進行遍歷當作一次切斷(graph-cut),則源點和鄰接點間的邊就是crossing edge
     * 根據貪心策略求MST的要求,要加入的邊必須是最輕邊(權重最小的邊),
     * 故而將crossing edges加入優先隊列,這樣便可用O(logN)的時間找出最小權重邊
     *
     * @param src 源點
     */
    private void search(int src) {
        visited[src] = true;
        for(Edge e : g.vertices()[src].Adj) {
            WeightedEdge we = (WeightedEdge)e;
            if(!visited[we.to])
                crossingEdges.offer(we);
        }
    }

算法的第二步和第三步如下:

    /**
     * lazy普利姆算法中,從一個源點出發
     *  1:通過對源點的所有鄰接點進行遍歷的方式找出所有crossing edges
     *  2:將crossing edges中最輕的(擁有最小的權重)邊加入MST
     *  3:將最輕邊的另一個頂點作爲源點,重複1-2步.
     *
     *
     * @param src 源點
     */
    private void mst(int src) {
        search(src);
        while (!crossingEdges.isEmpty()) {
            WeightedEdge we = crossingEdges.poll();
            //懶惰方式處理不再候選的邊
            if(visited[we.src] && visited[we.to])
                continue;
            //加入最小生成樹
            mst.offer(we);
            //累積mst的權重
            mstWeight += we.weight;
            //向src的鄰接點方向搜索
            if(!visited[we.src])
                search(we.src);
            //向to的鄰接點方向搜索
            if(!visited[we.to])
                search(we.to);
        }
    }

其中,維護所有crossing edge的,是一個優先隊列:

    /**
     * 優先隊列,用於維護crossing edges
     * 高效返回最輕邊
     */
    protected PriorityQueue<WeightedEdge> crossingEdges;

帶權邊的定義很簡單,像下面這樣:

import java.util.Comparator;

/**
 * Created by 浩然 on 4/19/15.
 * 帶權邊
 */
public class WeightedEdge extends Edge implements Comparable<WeightedEdge> {
    /**
     * 邊的權重
     */
    public Double weight;

    /**
     * 邊的源點
     */
    public int src;

    /**
     * 構造一條帶權邊
     *@param src 源點
     * @param other 目標點
     * @param weight 權重
     */
    public WeightedEdge(int src,int other, Double weight) {
        super(other);
        this.src = src;
        this.weight = weight;
    }

    /**
     * 比較兩條邊的大小,這裏作升序
     * @param to 被比較邊
     * @return 如果權重比被比較的邊小則返回-1,如果大於則返回1,相等則返回0
     */
    @Override
    public int compareTo(WeightedEdge to) {
        if(this.weight < to.weight)
            return -1;
        else if(this.weight > to.weight)
            return 1;
        return 0;
    }
}

public class Edge {
    public int to;
    public Edge(int key) {
        this.to = key;
    }
}

而頂點的定義更簡單,如下:

public class Vertex {
    /**
     * 鄰接表
     */
    public LinkedList<Edge> Adj;
}

完整代碼

public class LazyPrim extends Algorithm {

    /**
     * 優先隊列,用於維護crossing edges
     * 高效返回最輕邊
     */
    protected PriorityQueue<WeightedEdge> crossingEdges;

    public LazyPrim(WeightedUndirectedGraph g) {
        super(g);
    }

    /**
     * lazy普利姆算法求MST或MSF(Minimum Spanning Forest最小生成森林)
     *
     * 算法複雜度:最差O(ElogE)
     */
    public void performMST() {
        resetMemo();
        //對圖中的所有頂點進行遍歷,找出MST或MSF
        for(int  i = 0; i < g.vertexCount();i++){
            if(!visited[i]) {
                mst(i);
            }
        }
    }

    /**
     * lazy普利姆算法中,從一個源點出發
     *  1:通過對源點的所有鄰接點進行遍歷的方式找出所有crossing edges
     *  2:將crossing edges中最輕的(擁有最小的權重)邊加入MST
     *  3:將最輕邊的另一個頂點作爲源點,重複1-2步.
     *
     *
     * @param src 源點
     */
    private void mst(int src) {
        search(src);
        while (!crossingEdges.isEmpty()) {
            WeightedEdge we = crossingEdges.poll();
            //懶惰方式處理不再候選的邊
            if(visited[we.src] && visited[we.to])
                continue;
            //加入最小生成樹
            mst.offer(we);
            //累積mst的權重
            mstWeight += we.weight;
            //向src的鄰接點方向搜索
            if(!visited[we.src])
                search(we.src);
            //向to的鄰接點方向搜索
            if(!visited[we.to])
                search(we.to);
        }
    }

    /**
     * 找出從源點出發的所有的crossing edges,並用一個優先隊列維護他們
     *
     * 原理:
     * 將對未訪問的鄰接點進行遍歷當作一次切斷(graph-cut),則源點和鄰接點間的邊就是crossing edge
     * 根據貪心策略求MST的要求,要加入的邊必須是最輕邊(權重最小的邊),
     * 故而將crossing edges加入優先隊列,這樣便可用O(logN)的時間找出最小權重邊
     *
     * @param src 源點
     */
    private void search(int src) {
        visited[src] = true;
        for(Edge e : g.vertices()[src].Adj) {
            WeightedEdge we = (WeightedEdge)e;
            if(!visited[we.to])
                crossingEdges.offer(we);
        }
    }

    @Override
    protected void resetMemo() {
        super.resetMemo();
        //重置優先隊列
        crossingEdges = new PriorityQueue<>();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章