这周的课程学得真是纠结,因为有考试的原因,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);
}
}
最后谈谈作业吧,自己虽然独立完成了,但是花了好长的时间,而且作业的结果也是惨不忍睹,等自己修改好了再上传吧。