算法描述
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<>();
}
}