這周的課程學得真是糾結,因爲有考試的原因,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);
}
}
最後談談作業吧,自己雖然獨立完成了,但是花了好長的時間,而且作業的結果也是慘不忍睹,等自己修改好了再上傳吧。