这篇文章代码是关于有向图的拓扑排序和强连通性算法;
测试结果和预期结果有差别,找了很久,新手的(┬_┬)坑;
Stack 这个数据结果在JAVA已经自动实现,但是对于foreach ,以及迭代器,数据的顺序是先进先出的,不会按照栈的后进先出顺序;这里修改了一下保证了迭代器中数据的顺序是后进先出的,测试结果正确;
接下来是实现代码(包括测试代码可以直接运行的);
主要内容:
- Digraph 有向图的数据结构
- DirectedCycle 查找有向图中是否有环(也可以用于无向图),深度优先搜索,
主要是找后向边 - DirectGraphDFS 有向图的深度优先搜索算法
- DepthFirstOrder 有向图中基于深度优先搜索的顶点排序,可以得到前序、后序、逆后序
- Topological 拓扑排序
命题F: 一副有向无环图的拓扑顺序即为所有顶点的逆后序排列
- Kosaraju 使用Kosaraju算法得到强连通分量
* 将一幅图G进行反向得到G2
* 将G2进行拓扑排序得到它的逆后序排序
* 然后按照步骤2得到的逆后序序列深度搜索G
* 在构造函数中,使用同一个dfs()函数调用被访问 的顶点在同一个强连通分量中
- Main 测试代码
- jobs.txt 测试数据
- tinyDG.txt
- 测试结果
文章目录
Digraph 有向图的数据结构
import java.util.ArrayList;
import java.util.List;
/**
* FileName: Digraph
* Author: Jerry
* Date: 2020/2/12 21:57
* Description: Digraph 数据类型
*/
public class Digraph {
private final int V;
private int E;
private List<Integer>[]adj;
public Digraph(int V){
adj = new ArrayList[V];
for(int i=0;i<V;i++){
adj[i]=new ArrayList<>();
}
this.V =V;
this.E =0;
}
public int V(){
return V;
}
public int E(){
return E;
}
public void addEdge(int v,int w){
adj[v].add(w);
E++;
}
public Iterable<Integer> adj(int v){
return adj[v];
}
public Digraph reverse(){
Digraph R = new Digraph(V);
for(int v =0;v<V;v++){
for(int w:adj(v))
R.addEdge(w,v);
}
return R;
}
@Override
public String toString(){
String s = V+"vertices, "+E+"edges\n";
for(int v=0;v<V;v++){
s+=v+": ";
for(int w:this.adj(v))
s+=w+" ";
s+='\n';
}
return s;
}
}
DirectedCycle 查找有向图中是否有环(也可以用于无向图),深度优先搜索,
import java.util.Stack;
/**
* FileName: DirectedCycle
* Author: Jerry
* Date: 2020/2/13 10:13
* Description: 查找有向图中是否有环(也可以用于无向图),深度优先搜索
* 主要是找后向边
*/
public class DirectedCycle {
private boolean[] marked;
//用来记录顶点顺序
private int[] edgeTo;
/**
* 判断顶点是否在递归调用栈中
*/
private Stack<Integer> cycle;
/**
* 顶点是否在递归调用栈上
*/
private boolean[] onStack;
public DirectedCycle(Digraph digraph) {
onStack = new boolean[digraph.V()];
edgeTo = new int[digraph.V()];
marked = new boolean[digraph.V()];
for (int v = 0; v < digraph.V(); v++) {
if (!marked[v]) {
dfs(digraph, v);
}
}
}
private void dfs(Digraph digraph, int v) {
onStack[v] = true;
marked[v] = true;
for (int w : digraph.adj(v)) {
if (this.hasCycle()) {
return;
} else if (!marked[w]) {
edgeTo[w] = v;
dfs(digraph, w);
} else if (onStack[w]) {
cycle = new Stack<Integer>();
for (int x = v; x != w; x = edgeTo[x]) {
cycle.push(x);
}
cycle.push(w);
cycle.push(v);
}
}
onStack[v] = false;
}
public boolean hasCycle() {
return cycle != null;
}
/**
* 获得有向环中的顶点
*/
public Iterable cycle() {
return this.cycle;//反序也是环,不用置反
}
}
DirectGraphDFS 有向图的深度优先搜索算法
/**
* FileName: DirectGraphDFS
* Author: Jerry
* Date: 2020/2/13 9:39
* Description: 有向图的深度优先搜索算法
*/
public class DirectGraphDFS {
private boolean marked[];
/**
* 从v点进行深度优先搜索
* @param digraph
* @param v
*/
public DirectGraphDFS(Digraph digraph,int v){
marked = new boolean[digraph.V()];
dfs(digraph,v);
}
public void dfs(Digraph digraph,int v){
marked[v]=true;
for(int w:digraph.adj(v)){
if(!marked[w]){
dfs(digraph,w);
}
}
}
/**
* 从v可以到达w吗?(单点可达性)
* @param w
* @return
*/
public boolean pathTo(int w){
return marked[w];
}
}
DepthFirstOrder 有向图中基于深度优先搜索的顶点排序,可以得到前序、后序、逆后序
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
/**
* FileName: DepthFirstOrder
* Author: Jerry
* Date: 2020/2/13 12:49
* Description: 有向图中基于深度优先搜索的顶点排序
* 可以得到前序、后序、逆后序
*/
public class DepthFirstOrder {
private boolean[] marked;
private Queue<Integer> pre;
private Queue<Integer> post;
private Stack<Integer> reversePost;
public DepthFirstOrder(Digraph G) {
pre = new LinkedList<>();
post = new LinkedList<>();
reversePost = new Stack<>();
marked = new boolean[G.V()];
for (int v = 0; v < G.V(); v++)
if (!marked[v]) dfs(G, v);
}
public void dfs(Digraph G, int v) {
pre.add(v);
marked[v] = true;
for (int w : G.adj(v))
if (!marked[w])
dfs(G, w);
post.add(v);
reversePost.push(v);
}
public Iterable<Integer> pre(){
return pre;
}
public Iterable<Integer> post(){
return post;
}
public Iterable<Integer> reversePost(){
LinkedList<Integer> list = new LinkedList<>();
while(!reversePost.isEmpty()){
list.add(reversePost.pop());
}
return list;
}
}
Topological 拓扑排序
/**
* FileName: Topological
* Author: Jerry
* Date: 2020/2/13 13:10
* Description: 拓扑排序
* 命题F:一副有向无环图的拓扑顺序即为所有顶点的逆后序排列
*/
public class Topological {
private Iterable<Integer> order; //顶点的拓扑顺序
/**
* 拓扑排序
*
* @param digraph
*/
public Topological(Digraph digraph) {
DirectedCycle cycle = new DirectedCycle(digraph);
if (!cycle.hasCycle()) {
DepthFirstOrder dfs = new DepthFirstOrder(digraph);
order = dfs.reversePost();
}
}
/**
* 返回拓扑排序
*
* @return
*/
public Iterable<Integer> Order() {
return order;
}
/**
* 判断是否有拓扑排序
*
* @return
*/
public boolean isDAG() {
return order != null;
}
}
Kosaraju 使用Kosaraju算法得到强连通分量
/**
* FileName: Kosaraju
* Author: Jerry
* Date: 2020/2/13 15:43
* Description: 使用Kosaraju算法得到强连通分量
*/
public class Kosaraju {
private boolean[]marked;
private int[]id;
private int count;
/**
* 将一幅图G进行反向得到G2
* 将G2进行拓扑排序得到它的逆后序排序
* 然后按照步骤2得到的逆后序序列深度搜索G
* 在构造函数中,使用同一个dfs()函数调用被访问 的顶点在同一个强连通分量中
* @param digraph
*/
public Kosaraju(Digraph digraph){
marked = new boolean[digraph.V()];
id = new int[digraph.V()];
DepthFirstOrder order = new DepthFirstOrder(digraph.reverse());
for(int s:order.reversePost()){
if(!marked[s]){
dfs(digraph,s);
count++;
}
}
}
private void dfs(Digraph digraph,int v){
marked[v]=true;
id[v]=count;
for(int w:digraph.adj(v)){
if(!marked[w]){
dfs(digraph,w);
}
}
}
public boolean stronglyConnected(int v,int w){
return id[v]==id[w];
}
public int id(int v){
return id[v];
}
public int count(){
return count;
}
}
Main 测试代码
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* FileName: Main
* Author: Jerry
* Date: 2020/2/13 11:46
* Description: 测试功能
*/
public class Main {
public static void main(String[] args) throws FileNotFoundException {
try {
String fileName = "src/tinyDG.txt";
FileReader fileReader = new FileReader(fileName);
Digraph graph = null;
graph = createDigraph(graph, fileReader);
System.out.println("有向图:");
System.out.println(graph.toString());
System.out.println("有向图逆置:");
System.out.println(graph.reverse().toString());
System.out.println("逆置后的拓扑排序:");
new DepthFirstOrder(graph.reverse()).reversePost().forEach(e->System.out.print(e+" "));
System.out.println();
System.out.println("判断拓扑排序(有则输出):");
Topological topo = new Topological(graph);
if (topo.isDAG()) {
Iterable<Integer> iter = topo.Order();
for (int i : iter) {
System.out.print(i + " ");
}
} else {
System.out.println("No Topologic");
}
System.out.println();
System.out.println("求强连通分量个数:");
System.out.println("Strong connected component: "+new Kosaraju(graph).count());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static Digraph createDigraph(Digraph digraph, FileReader file) {
BufferedReader bf = new BufferedReader(file);
try {
String v = bf.readLine();
int V = Integer.parseInt(v);
digraph = new Digraph(V);
String e = bf.readLine();
int E = Integer.parseInt(e);
String[] sp = null;
for (int i = 0; i < E; i++) {
String edge = bf.readLine();
sp = edge.split(" ");
int m = Integer.parseInt(sp[0]);
int n = Integer.parseInt(sp[1]);
digraph.addEdge(m, n);
}
} catch (IOException e) {
e.printStackTrace();
}
return digraph;
}
}
jobs.txt测试代码
13
15
0 5
0 1
0 6
2 0
2 3
3 5
5 4
6 4
8 7
7 6
6 9
9 10
9 12
9 11
11 12
tinyDG.txt
13
22
0 1
0 5
2 0
2 3
3 2
3 5
4 3
4 2
5 4
6 0
6 4
6 9
7 6
7 8
8 7
8 9
9 10
9 11
10 12
11 4
11 12
12 9