Coursera Algorithm, Part2 Week2: Minimum Spanning Trees & Shortest Paths

這周的課程學得真是糾結,因爲有考試的原因,lecture的前幾部分學得還是挺順利的,後面就給耽擱了。最蛋疼的是一到每個week新的lecture發佈,coursera官網就和翔一樣慢。最後還有課程作業,就不吐槽了,不知道花了多長時間,目前作業部分還有很多的問題。但確實也是學到很多,也讓基礎更紮實了。


這一週研究圖的邊都是帶權重,之前的針對節點的鄰接表和無向圖、有向圖的數據結構已經不夠用了,所以需要建立新的數據結構,該數據結構是用來描述邊的性質的。

1.1 帶權重的邊

需要描述一條邊的起點、終點和權重,注意:一定是邊作爲中心,可以通過邊描述邊的性質(起點、終點和權重)。

public class Edge implements Comparable<Edge> {
	
	private final int v, w;
	private final double weight;
	
	// constructor
	public Edge(int v, int w, double weight) {
		this.v = v;
		this.w = w;
		this.weight = weight;
	}
	
	// either endpoint
	public int either() {
		return v;
	}
	
	// other endpoint
	public int other(int vertex) {
		
		if (vertex == v) return w;
		return v;
	}
	
	// compare edges by weight
	public int compareTo(Edge that) {
		
		if (this.weight < that.weight) return -1;
		else if (this.weight > that.weight) return 1;
		else return 0;
	}
}

1.2 Kruskal's algorithm

考慮所有的邊按權重非降排序;然後按所排順序將每條邊加入到樹中除非添加的這條邊後會構成環,那麼就選擇下一條邊,直到邊總數爲V-1。

用優先隊列/最小堆存放所有的邊,逐個刪除優先隊列根節點得到最小權重邊,並維護優先隊列;路徑壓縮/並查集判定是否構成環(一條邊的兩個節點若在同一並查集中則會構成環)。

public class KruskalMST {
	
	private Queue<Edge> mst = new Queue<Edge>();
	
	public KruskalMST(EdgeWeightedGraph G) {
		
		MinPQ<Edge> pq = new MinPQ<Edge>();
		// build priority queue
		for (Edge e : G.edges())
			pq.insert(e);
		
		UF uf = new UF(G.V());
		while(!pq.isEmpty() && mst.size()<G.V()-1) {
			
			// greedily add edges to MST
			Edge e = pq.delMin();
			int v = e.either(), w = e.other(v);
			// edge v-w does not create cycle
			if (!uf.connected(v, w)) {
				uf.union(v, w);  // merge sets
				mst.enqueue(e);  // add edge to MST
			}
		}
	}
	
	public Iterable<Edge> edges() {
		return mst;
	}
}

1.3 Prim's algorithm

Prim算法與Kruskal算法類似,都是貪心的思想選取一條最小權重邊,不過Kruskal是在整個帶權有向圖中選取這樣的邊,並維護當前的樹;而Prim算法則是在與當前樹相連接的各個節點的邊中選取權重最小的,一旦選取了某個節點距當前樹的最小邊,就標記該節點並把邊加入隊列。此處的優先隊列維護的是當前邊的權重,根節點是與當前樹相連的邊的最小權重。

課程中給出了兩種實現的Prim算法,抱歉我只看懂了第一種,另一種和單源點最短路徑相關。

public class LazyPrimMST {
	
	private boolean[] marked;  // MST vertices
	private Queue<Edge> mst;   // MST edges
	private MinPQ<Edge> pq;    // PQ of edges
	
	public LazyPrimMST(WeightedGraph G) {
		
		pq = new MinPQ<Edge>();
		mst = new Queue<Edge>();
		marked = new boolean[G.V()];
		visit(G, 0);           // assume G is connected
		
		while (!pq.isEmpty() && mst.size()<G.V()-1) {
			// repeatedly delete the min weight edge e = v-w from PQ
			Edge e = pq.delMin();
			int v = e.either(), w = e.other(v);
			// ignore if both endpoints in T
			if (marked[v] && marked[w]) continue;
			mst.enqueue(e);
			if (!marked[v]) visit(G, v);  // add v or w to tree
			if (!marked[w]) visit(G, w);
		}
	}
	
	private void visit(WeightedGraph G, int v) {
		
		marked[v] = true;  // add v to tree
		// for each edge e = v-w, add to PQ if w not already in T
		for (Edge e : G.adj(v))
			if (!marked[e.other(v)])
				pq.insert(e);
	}
	
	public Iterable<Edge> mst() {
		return mst;
	}
}


2.1 Dijkstra's algorithm

與Prim算法類似的維護優先隊列找到到單源點的最短路徑,對於每個新加入的節點,relax由該節點發出的每一條邊,實現每個節點的到單源點的最小值。

public class DijkstraSP {
	
	private DirectedEdge[] edgeTo;
	private double[] distTo;
	private IndexMinPQ<Double> pq;
	
	public DijkstraSP(EdgeWeightedDigraph G, int s) {
		
		edgeTo = new DirectedEdge[G.V()];
		distTo = new double[G.V()];
		pq = new IndexMinPQ<Double>(G.V());
		
		for (int v = 0; v < G.V(); v++)
			distTo[v] = Double.POSITIVE_INFINITY;
		distTo[s] = 0.0;
		
		pq.insert(s, 0.0);
		// relax vertices in order of distance from s
		while (!pq.isEmpty()) {
			int v = pq.delMin();
			for (DirectedEdge e : G.adj(v))
				relax(e);
		}
	}
	
	private void relax(DirectedEdge e) {
		
		int v = e.from(), w = e.to();
		if (distTo[w] > distTo[v]+e.weight()) {
			distTo[w] = distTo[v]+e.weight();
			edge[w] = e;
			// update PQ
			if (pq.contains(w)) pq.decreaseKey(w, distTo[w]);
			else pq.insert(w, distTo[w]);
		}
	}
}


2.2 DAG的SP

與Dijkstra算法不同的是,DAG求解SP不需要維護優先隊列,節點訪問順序是DAG拓撲排序的結果,以此relax每條邊得到結果。

public class AcyclicSP {
	
	private DirectedEdge[] edgeTo;
	private double[] distTo;
	
	public AcyclicSP(EdgeWeightedDigraph G, int s) {
		
		edgeTo = new DirectedEdge[G.V()];
		distTo = new double[G.V()];
		
		for (int v = 0; v < G.V(); v++)
			distTo[v] = Double.POSITIVE_INFINITY;
		distTo[s] = 0.0;
		
		// topological order
		Topological topological = new Topological(G);
		for (int v : topological.order()) 
			for (DirectedEdge e : G.adj(v)) 
				relax(e);
	}
}


最後談談作業吧,自己雖然獨立完成了,但是花了好長的時間,而且作業的結果也是慘不忍睹,等自己修改好了再上傳吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章