Coursera Algorithm, Part2 Week1: Undirected Graph & Directed Graph

Algorithm Part1 没学多久就结课了,真是忧桑,当时开始学这门课的时候离结课也就还剩下一个星期,所以学到的东西也不是很多。


于是,就直接跳到 Algorithm Part2 了,不知道没有Part1 的基础直接进入Part2 压力会不会很大。


中间大概等了一两个星期吧,开始Part2 的课程。一上来就是图论,自己心里当时还真有点打鼓,毕竟图论是一个看上去很抽象、很深奥的东西。而且当时学习算法的时候对图论也是抱着一知半解的理解,知道的图论的算法也就最最基础的深搜、广搜、最小生成树、单源点最短路径,可即使是这最最基础的算法玩得也不利索。所以这次真的是抱着认认真真、不打一点马虎眼、不骗自己的态度来学习的,认真学习理解每一个算法、认真写每一行靠谱的代码(每星期的课后作业还是很有意思的,有难度但有心就能做),还有及时作总结,就像现在做的事情一样。


我希望能够在这门课结束的时候能够坚实自己的基础,也能够保持对代码的感觉,代码量的不同真的会让程序员有不同的感觉。


好了,进入正题吧、、

1.1 数据结构

构建图的数据结构不多,典型的就几种吧:链表或者是数组,存放的方式可以是二维矩阵,0/1表示两个节点是否相连,空间开销是V*V,V表示图的节点个数;再有就是直接存放每条边,空间开销是E,E表示边的条数。

显然,有更好的数据结构方式,那就是邻接表。V大小的数组存放每个节点,数组的每个元素又由链表链出,链表中的每个元素表示与该数组元素相连的节点。邻接表的数据结构节省了空间的开销,也降低了查找的时间代价,是一种常用的图论的数据结构。

坑爹的csdn,好不容易写一回blog,竟然不能上传图片,只得一行一行敲代码!!!注:所有的示例代码以及所需包含的库都可以在这里下载。

public class Graph {
	
	private final int V;
	private Bag<Integer>[] adj;  // adjacency lists (using Bag data type)
	
	public Graph(int V) {
		
		this.V = V;
		adj = (Bag<Integer>[]) new Bag[V];  // create empty graph with V vertices
		for (int v = 0; v < V; v++)
			adj[v] = new Bag<Integer>();
	}
	
	public void addEdge(int v, int w) {
		
		// add edge v-w (parallel edges and self-loops allowed)
		adj[v].add(w);
		adj[w].add(v);
	}
	
	// iterator for vertices adjacent to v
	public Iterable<Integer> adj(int v) {
		return adj[v]
	}
}

1.2 深搜(带路径记录)

深搜是图论中很常见的算法,可能是图论中很多算法的基础,也许比广搜用得还多,话不多说,直接上代码。

public class DepthFirstPaths {
	
	private boolean[] marked[];  // marked[v]=true if v connected to s
	private int[] edgeTo;        // edge[v] = previous vertex on path from s to v
	private int s;
	
	public DepthFirstPahts(Graph G, int s) {
		
		// ... initialize data structire
		dfs(G, s);               // find vertices connected to s
	}
	
	// recursive DFS does the work
	private void dfs(Graph G, int v) {
		
		marked[v] = true;
		for (int w : G.adj(v)) {
			if (!marked[w]) {
				dfs(G, w);
				edge[w] = v;
 			}
		}
	}
	
	public boolean hasPathTo(int v) {
		return marked[v];
	}
	
	public Iterable<Integer> pathTo(int v) {
		
		if (!hasPathTo(v)) return null;
		Stack<Integer> path = new Stack<Integer>();
		for (int x = v; x != s; x = edgeTo[x])
			path.push(x);
		path.push(s);
		return path;
	}
}


1.3 广搜

与深搜不同,同样是图搜索,但是更广泛地用于最短路径的算法中。

public class BreadthFirstPaths {
	
	private boolean[] marked;
	private int[] edgeTo;
	
	private void bfs(Graph G, int s) {
		
		Queue<Integer> q = new Queue<Integer>();
		q.enqueue(s);
		marked[s] = true;
		while(!q.isEmpty()) {
			int v = q.dequeue();
			for (int w : G.adj(v)) {
				if (!marked[w]) {
					q.enqueue(w);
					marked[w] = true;
					edgeTo[w] = v;
				}
			}
		}
	}
}

1.4 连通分量(深搜解决)

public class CC {
	
	private boolean[] marked;
	private int[] id;  // id[v] = id of component containing v
	private int count; // number of components
	
	public CC(Graph G) {
		
		marked = new boolean[G.V()];
		id = new int[G.V()];
		for (int v = 0; v < G.V(); v++) {
			if (!marked[v]) {
				dfs(G, v);  // run DFS from one vertex in each component
				count++;
			}
		}
	}
	
	// number of components
	public int count() {
		return count;
	}
	
	// id of component containing v
	public int id(int v) {
		return id[v];
	}
	
	// all vertices discovered in same call of dfs have same id
	private void dfs(Graph G, int v) {
		
		marked[v] = true;
		id[v] = count;
		for (int w : G.adj(v))
			if (!marked[w])
				dfs(G, w);
	}
}



下面是有向图的基本算法,深搜和广搜就不再赘述了,和无向图基本上一致,没什么好说的。

2.1 拓扑排序(DAG:Directed acyclic graph)

首先,什么是拓补排序呢。我自己也说不清楚,只能感觉出来,就比如一批工作,每两个工作有先后次序,什么先做、什么后做,需要给出所有这些工作它们之间清楚的序列关系,大概就是拓扑排序了吧。这里拓扑排序算法是借助深搜,终于认识到深搜的强大了吧。

public class DepthFirstOrder {
	
	private boolean[] marked;
	private Stack<Integer> reversePost;
	
	public DepthFirstOrder(Digraph G) {
		
		reversePost = new Stack<Integer>();
		marked = new boolean[G.V()];
		for (int v = 0; v < G.V(); v++) 
			if (!marked[v]) dfs(G, v);
	}
	
	private void dfs(Digraph G, int v) {
		
		marked[v] = true;
		for (int w : G.adj(v))
			if (!marked[w]) dfs(G, w);
		reversePost.push(v);
	}
	
	// returns all vertices in "reverse DFS postorder"
	public Iterable<Integer> reversePost() {
		return reversePost;
	}
}

2.2 强连通分量

首先,强连通的定义:节点v和节点w强连通充分必要条件是在有向图中有v到w的路径,也有w到v的路径。

那么强连通分量的定义:最大化强连通集合的节点个数。

这里用的是Kosaraju-Sharir算法计算强连通分量,Kosaraju-Sharir算法主要由两部分组成:

(1)计算图G'的拓扑排序,其中图G'是图G中所有的有向边调转指向,即原来由v->w的边变成w->v

(2)根据图G'的拓扑排序的顺序在图G中进行深搜,所能访问到的节点属于同一个强连通分量

其实代码和连通分量非常像,就是加了个拓扑排序,深搜的节点顺序是拓扑排序的结果。话不多说,上代码:

public class KosarajuSharirSCC {
	
	private boolean[] marked;
	private int[] id;
	private int count;
	
	public KosarajuSharirSCC(Digraph G) {
		
		marked = new boolean[G.V()];
		id = new int[G.V()];
		DepthFirstOrder dfs = new DepthFirstOrder(G.reverse());
		for (int v : dfs.reversePost()) {
			if (!marked[v]) {
				dfs(G, v);
				count++;
			}
		}
	}
	
	private void dfs(Digraph G, int v) {
		
		marked[v] = true;
		id[v] = count;
		for (int w : G.adj(v))
			if (!marked[w]) dfs(G, w);
	}
	
	public boolean stronglyConnected(int v, int w) {
		return id[v] == id[w];
	}
}


Assigment

最后附上Algorithm Part2 Week1 的作业和测试用例。看这里



冬冬加油!再苦再累路也得走完,想明白自己想要的是什么,放弃的是什么。每天进步一点点,我知道过程会很艰苦,会没有人相信你,但是真的要自己相信自己。一定行行行!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值