基于《算法4》的描述,在之前有向图数据结构基础上,实现有向无环图(DAG)、拓扑排序、强连通分量(Kosaraju)算法;
有向无环图(DAG):不含有环的有向图;
拓扑排序:
给定一幅有向图,使得有向边均从排在前面的元素指向排在后面的元素;
当且仅当,有向无环图才可以做拓扑排序;
常用于任务调度、课程安排...等解决方案;
强连通分量(Kosaraju):
顶点v和顶点w互相可达为连通性;
顶点之间可达的最大子集,就是强连通分量;
有向无环图检测算法类 DirectedCycle.java
/**
* 有向无环图(DAG)
*
* @Author: ltx
* @Description: DAG有向环检测
*
*/
public class DirectedCycle {
private DirectedGraph graph;//图
public boolean marked[];//标记已经遍历过的顶点
public Integer edgeTo[];//路径
private Stack cycle;//形成环的节点
private boolean[] onStack;//标记是否形成环
/**
* 初始化
*
* @param graph
*/
public DirectedCycle(DirectedGraph graph) {
this.graph = graph;
marked = new boolean[graph.v];
edgeTo = new Integer[graph.v];
onStack = new boolean[graph.v];
for (int i = 0; i < graph.v; i++) {
if (!marked[i]) {
// System.out.printf("起点 %d, 开始遍历...\n", i);
//深度度优先遍历
dfs(i);
// System.out.println();
}
}
}
/**
* 深度优先遍历
*
* @param v 起点
*/
public void dfs(int v) {
// System.out.printf("%d, ", v);
onStack[v] = true;
//标记已走
marked[v] = true;
for (int w : graph.adj[v]) {
if (!marked[w]) {
//路径
edgeTo[w] = v;
dfs(w);
} else if (onStack[w]) {
cycle = new Stack();
for (int x = v; x != v; x = edgeTo[x]) {
cycle.push(x);
}
cycle.push(w);
cycle.push(v);
}
}
onStack[v] = false;
}
/**
* 有环
*
* @return
*/
public boolean hasCycle() {
return cycle != null;
}
/**
* 初始化有向图
*
* @return
*/
public static DirectedGraph init() {
DirectedGraph graph = new DirectedGraph(13);
graph.addEdge(0, 5);
graph.addEdge(0, 1);
graph.addEdge(0, 6);
graph.addEdge(2, 0);
graph.addEdge(2, 3);
graph.addEdge(3, 5);
graph.addEdge(5, 4);
graph.addEdge(6, 4);
graph.addEdge(6, 9);
graph.addEdge(7, 6);
graph.addEdge(8, 7);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(9, 10);
graph.addEdge(11, 12);
return graph;
}
public static void main(String[] args) {
System.out.println("################有向图################");
//初始化一个图(来自《算法4》的有向图demo)
DirectedGraph graph = DirectedCycle.init();
System.out.println("-------------------邻接表结构-------------------");
graph.show();
DirectedCycle directedCycle = new DirectedCycle(graph);
System.out.printf("是否有环: %s\n", directedCycle.hasCycle());
}
}
拓扑排序算法实现的测试用例图:
有向无环图拓扑排序算法类 DepthFirstOrder.java
/**
* 拓扑排序
* @Author: ltx
* @Description: 有向无环图拓扑排序
*/
public class DepthFirstOrder {
private DirectedGraph graph;//图
public boolean marked[];//标记已经遍历过的顶点
Queue pre; //所有顶点前序排列(队列)
Queue post; //所有顶点后序排列(队列)
Stack reversePost; //所有顶点逆后序排列(栈)--拓扑排序
/**
* 初始化
*
* @param graph
*/
public DepthFirstOrder(DirectedGraph graph) {
this.graph = graph;
pre = new LinkedList<>();
post = new LinkedList<>();
reversePost = new Stack<>();
marked = new boolean[graph.v];
for (int i = 0; i < graph.v; i++) {
if (!marked[i]) {
//深度度优先遍历
dfs(i);
}
}
}
/**
* 深度优先遍历
*
* @param v 起点
*/
public void dfs(int v) {
//前序
pre.add(v);
//标记已走
marked[v] = true;
for (int w : graph.adj[v]) {
if (!marked[w]) {
dfs(w);
}
}
//后续
post.add(v);
//逆后续
reversePost.push(v);
}
/**
* 初始化有向图
*
* @return
*/
public static DirectedGraph init() {
DirectedGraph graph = new DirectedGraph(13);
graph.addEdge(0, 5);
graph.addEdge(0, 1);
graph.addEdge(0, 6);
graph.addEdge(2, 0);
graph.addEdge(2, 3);
graph.addEdge(3, 5);
graph.addEdge(5, 4);
graph.addEdge(6, 4);
graph.addEdge(6, 9);
graph.addEdge(7, 6);
graph.addEdge(8, 7);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(9, 10);
graph.addEdge(11, 12);
return graph;
}
public static void main(String[] args) {
System.out.println("################有向图################");
//初始化一个图(来自《算法4》的有向图demo)
DirectedGraph graph = DepthFirstOrder.init();
System.out.println("-------------------邻接表结构-------------------");
graph.show();
System.out.println("-------------------是否有环-------------------");
DirectedCycle directedCycle = new DirectedCycle(graph);
System.out.printf("是否有环: %s\n", directedCycle.hasCycle());
System.out.println("-------------------拓扑排序-------------------");
if (!directedCycle.hasCycle()) {
DepthFirstOrder depthFirstOrder = new DepthFirstOrder(graph);
System.out.println("前序:");
System.out.println(depthFirstOrder.pre);
System.out.println("后序:");
System.out.println(depthFirstOrder.post);
System.out.println("逆后序(拓扑排序):");
List temp = new ArrayList<>();
while (!depthFirstOrder.reversePost.isEmpty()) {
temp.add(depthFirstOrder.reversePost.pop());
}
System.out.println(temp);
}
}
}
强连通分量(Kosaraju)算法实现的测试用例图:
算法图解:
有向图强连通分量算法类 Kosaraju.java
/**
* @Author: ltx
* @Description: 有向图强连通分量算法
*/
public class Kosaraju {
private DirectedGraph graph;//图
public boolean marked[];//标记已经遍历过的顶点
public int[] id;//连通分量顶点
public int count;//连通分量数量
/**
* 初始化
*
* @param graph
*/
public Kosaraju(DirectedGraph graph) {
this.graph = graph;
marked = new boolean[graph.v];
id = new int[graph.v];
// 反向图
System.out.println("-------------------邻接表结构(反向图)-------------------");
graph.reverse().show();
// 逆后序(拓扑排序)
DepthFirstOrder depthFirstOrder = new DepthFirstOrder(graph.reverse());
System.out.println("反向图的逆后序:");
List temp = new ArrayList<>();
while (!depthFirstOrder.reversePost.isEmpty()) {
temp.add(depthFirstOrder.reversePost.pop());
}
System.out.println(temp);
//按方向图的拓扑排序做深度优先遍历
for (int i : temp) {
if (!marked[i]) {
//深度度优先遍历
dfs(i);
count++;
}
}
}
/**
* 深度优先遍历
*
* @param v 起点
*/
public void dfs(int v) {
//标记已走
marked[v] = true;
//记录强连通分量数量
id[v] = count;
for (int w : graph.adj[v]) {
if (!marked[w]) {
dfs(w);
}
}
}
/**
* 初始化有向图
*
* @return
*/
public static DirectedGraph init() {
DirectedGraph graph = new DirectedGraph(13);
graph.addEdge(0, 1);
graph.addEdge(0, 5);
graph.addEdge(2, 0);
graph.addEdge(2, 3);
graph.addEdge(3, 2);
graph.addEdge(3, 5);
graph.addEdge(4, 2);
graph.addEdge(4, 3);
graph.addEdge(5, 4);
graph.addEdge(6, 0);
graph.addEdge(6, 4);
graph.addEdge(6, 9);
graph.addEdge(7, 6);
graph.addEdge(7, 8);
graph.addEdge(8, 7);
graph.addEdge(8, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(10, 12);
graph.addEdge(11, 4);
graph.addEdge(11, 12);
graph.addEdge(12, 9);
return graph;
}
/**
* 打印强连通分量
*/
public void printWeight() {
for (int temp = 0; temp < count; temp++) {
System.out.printf("第 %d 个: ", temp + 1);
for (int i = 0; i < id.length; i++) {
if (this.id[i] == temp) {
System.out.printf("%d, ", i);
}
}
System.out.println();
}
}
public static void main(String[] args) {
System.out.println("################有向图################");
//初始化一个图(来自《算法4》的有向图demo)
DirectedGraph graph = Kosaraju.init();
System.out.println("-------------------邻接表结构-------------------");
graph.show();
Kosaraju kosaraju = new Kosaraju(graph);
System.out.println("-------------------强连通分量-------------------");
System.out.printf("强连通分量 %d 个:\n", kosaraju.count);
kosaraju.printWeight();
}
}