作业要求: 对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");
}
}
}
测试结果: