作业要求: 对tinyDG.txt(http://pan.baidu.com/s/1o6jWtcA)文件所表示的图,输出其反向图中顶点post的逆序表示,并输出每一个强连通分量,输出图的超图(思考,or实现),类名:GraphSCC。
找出一个有向图的所有强连通分量(SCC-Strong Connectivity Component),关键是:
1. 生成该图的反向图(请看:第五周作业——有向图邻接表表示及反向图构造);
2. 对反向图进行DFS(请看:第四周作业——无向图的DFS算法);
3. 对反向图进行post值降序(请看:第三周作业——冒泡排序和归并排序),再到原图中DFS.
将以上三步用到的知识点综合,用Java实现有向图强连通分量的表示,代码如下:
/** * 有向图的强连通分量 * SCC-Strong Connectivity Component * * 有向图G(V,E)的强连通分量发现算法: * (a)生成G的反向图Gr; * (b)对反向图Gr进行DFS搜索,得到顶点post的逆序数组; * (c)按照顶点post的逆序,在原图中进行深度优先遍历。 */ public class GraphSCC { //所有顶点的集合 private GraphNode[] nodes ; //强连通分量集合 private List<GraphNode[]> scc = new ArrayList<GraphNode[]>(); private boolean[] visited; //pre和post指针 private int _pre_post = 1; //连通分量指针,指向每个连通分量在顶点集合nodes里的开始位置 private int _cc = 0; //邻接表Map集合 private Map<Integer,List<Integer>> adjacencyList; //初始化 public GraphSCC(Map<Integer,List<Integer>> adjacencyList){ this.adjacencyList = adjacencyList; //顶点数目 int vCount = adjacencyList.size(); List<GraphNode> list = new ArrayList<GraphNode>(); for(int i=0;i<vCount;i++){ list.add(new GraphNode()); } nodes = list.toArray(new GraphNode[0]); visited = new boolean[vCount]; } /** * 对单个强连通分量进行深度优先搜索 * @param v 第几个顶点 */ public void DFSTraverse(int v){ visited[v] = true; nodes[_cc].setName(v); int tmp = _cc; //记录当前遍历到的顶点的pre nodes[tmp].setPre(_pre_post++); _cc++; //在该顶点的邻接顶点集合里对邻接顶点继续DFS List<Integer> vList = adjacencyList.get(v); if(vList.size()>0){ for(int j=0; j<vList.size(); j++){ int u = vList.get(j); if(!visited[u]){ DFSTraverse(u); } } } //记录当前遍历到的顶点的post nodes[tmp].setPost(_pre_post++); } /** * 对所有强连通分量进行深度优先搜索 */ public void dfs(){ for(int i=0; i<nodes.length; i++){ if(!visited[i]){ //该连通分量在nodes集合的开始位置 int start = _cc; //对该连通分量进行深度遍历 DFSTraverse(i); //该连通分量的大小 int count = _cc - start; //该连通分量包含的所有顶点 GraphNode[] newNodes = new GraphNode[count]; for(int j=0; j<count;j++){ int v = start+j; newNodes[j] = nodes[v]; } scc.add(newNodes); } } } //scc: Strong Connected Component,强连通分量 public List<GraphNode[]> getSCC(){ return scc; } }
public static void main(String[] args) { try(Scanner scanner = new Scanner (GraphDAG.class.getClassLoader().getResourceAsStream("tinyDG.txt"));){ //第一行的数字是顶点的数目 int v = scanner.nextInt(); //第二行的数字是边的数目 int e = scanner.nextInt(); //构造该图的反向图 GraphReverse graphReverse = new GraphReverse(v, e); //读取每条边对应的两个顶点,将边添加到反向图的邻接表里 for (int i = 0; i < e; i++) { int v1 = scanner.nextInt(); int v2 = scanner.nextInt(); graphReverse.addEdgeReverse(v2, v1); } //对反向图进行DFS GraphSCC graphSCC = new GraphSCC(graphReverse.getAdjacencyListReverse()); graphSCC.dfs(); //反向图的强连通分量集合 List<GraphNode[]> cc = graphSCC.getSCC(); int count = cc.size(); System.err.println("********** 共有"+count+"个连通分量,分别为: ********** "); //遍历强连通分量,输出SCC的post值逆序和每个顶点信息 for(int i=count-1; i>=0; i--){ GraphNode[] c = cc.get(i); System.err.println("第"+(count-i)+"个连通分量:"); //存储该SCC的post值 int [] postDESC = new int[c.length]; int index = 0; for(GraphNode node : c){ System.err.println(node.getInfo()); postDESC[index++] = node.getPost(); } //对post值冒泡排序 postDESC = BubbleSort.sort(postDESC); System.err.print("反向图中顶点post的逆序表示: "); for(int j=postDESC.length-1; j>=0; j--){ for(GraphNode node : c){ if(node.getPost() == postDESC[j]){ int name = node.getName(); System.err.print(name+" "); } continue; } } System.err.println("\n"); } } }