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);
	}
}


最后谈谈作业吧,自己虽然独立完成了,但是花了好长的时间,而且作业的结果也是惨不忍睹,等自己修改好了再上传吧。

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