求解思路
1.统计所有节点的出度及其前序节点,初始化一个空的访问集合;
2.将出度为零的节点放入访问集合,并将其前序节点的出度数减1;
3.重复第2步骤,直到所有节点从头到尾完整遍历一遍;
4.判断已访问节点个数是否等于节点个数,是则有向图无环,否则有向图有环。
实现代码
方法一:
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class AlgorithmUtil {
/**
* 查找有向图的有序集合,如果有环返回空数组
* 根据每个节点的入度和出度计算,把出度为零的依次去掉并更改其前序节点的出度个数,统计最后访问过的节点
*/
public static int[] findSequence(int[][] graph) {
int nodeNum = graph.length;
// 记录每个有入度的节点,及其所有的前序节点
Map<Integer, List<Integer>> inEdge = new HashMap<>(nodeNum);
// 记录每个节点的出度个数
int[] outEdgeNum = new int[nodeNum];
// 初始化数据
for (int i = 0; i < nodeNum; i++) {
for (int j = 0; j < nodeNum; j++) {
if (graph[i][j] != Integer.MAX_VALUE) {
outEdgeNum[i]++;
if (inEdge.get(j) == null) {
List<Integer> list = new ArrayList<>();
list.add(i);
inEdge.put(j, list);
} else {
inEdge.get(j).add(i);
}
}
}
}
// 已访问的节点个数
List<Integer> visitedList = new ArrayList<>(nodeNum);
// 循环遍历所有节点的出度
while (visitedList.size() < nodeNum) {
for (int i = 0; i < nodeNum; i++) {
if (outEdgeNum[i] == 0 && !visitedList.contains(i)) {
visitedList.add(i);
for (int temp = 0; inEdge.get(i) != null && temp < inEdge.get(i).size(); temp++) {
outEdgeNum[inEdge.get(i).get(temp)]--;
}
break;
}
if ((i == nodeNum - 1) && visitedList.size() != nodeNum) {
return new int[] {};
}
}
}
// 反转集合元素
Collections.reverse(visitedList);
int[] result = visitedList.stream().mapToInt(Integer::valueOf).toArray();
return result;
}
}
性能优化
方法一虽好但是使用集合太多,性能有瓶颈。优化方法是减少集合使用。
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
public class AlgorithmUtil {
/**
* 查找有向图的有序集合,如果有环返回空数组
* 根据每个节点的入度和出度计算,把出度为零的依次去掉并更改其前序节点的出度个数,统计最后访问过的节点
*/
public static int[] findSequence(int[][] graph) {
int nodeNum = graph.length;
// 记录每个有入度的节点,及其所有的前序节点
Map<Integer, List<Integer>> inEdge = new HashMap<>(nodeNum);
// 记录每个节点的出度个数
int[] outEdgeNum = new int[nodeNum];
// 初始化数据
for (int i = 0; i < nodeNum; i++) {
for (int j = 0; j < nodeNum; j++) {
if (graph[i][j] != Integer.MAX_VALUE) {
outEdgeNum[i]++;
if (inEdge.get(j) == null) {
List<Integer> list = new ArrayList<>();
list.add(i);
inEdge.put(j, list);
} else {
inEdge.get(j).add(i);
}
}
}
}
int[] visited = new int[nodeNum];
int index = 0;
Set<Integer> visitedSet = new HashSet<>(nodeNum);
while (index < nodeNum) {
for (int i = 0; i < nodeNum; i++) {
if (outEdgeNum[i] == 0 && !visitedSet.contains(i)) {
visited[index++] = i;
visitedSet.add(i);
for (int temp = 0; inEdge.get(i) != null && temp < inEdge.get(i).size(); temp++) {
outEdgeNum[inEdge.get(i).get(temp)]--;
}
break;
}
if ((i == nodeNum - 1) && visitedSet.size() != nodeNum) {
return new int[] {};
}
}
}
reverseArray(visited);
return visited;
}
/**
* 数组反转
* @param array 数组
*/
private static void reverseArray(int[] array) {
for (int i = 0; i < array.length / 2; i++) {
int temp = array[i];
array[i] = array[array.length - 1 - i];
array[array.length - 1 - i] = temp;
}
}
}
测试用例
import org.junit.Test;
public class AlgorithmUtilTest {
@Test
public void findSequence() {
int[][] graph = new int[][] {{NAN, 1, 1, NAN}, {NAN, NAN, NAN, 1}, {NAN, NAN, NAN, 1}, {NAN, NAN, NAN, NAN}};
int[] resp = AlgorithmUtil.findSequence(graph);
int[] goal = new int[] {0, 1, 2, 3};
int[] goal2 = new int[] {0, 2, 1, 3};
assert isEqual(resp, goal) || isEqual(resp, goal2);
}
@Test
public void findSequence2() {
int[][] graph = new int[][] {{NAN, 1, 1, NAN}, {1, NAN, NAN, 1}, {NAN, NAN, NAN, 1}, {NAN, NAN, NAN, NAN}};
int[] resp = AlgorithmUtil.findSequence(graph);
int[] goal = new int[] {};
assert isEqual(resp, goal);
}
@Test
public void findSequence3() {
int[][] graph = new int[][] {{NAN, 1, 1, NAN}, {NAN, NAN, NAN, 1}, {NAN, NAN, NAN, 1}, {1, NAN, NAN, NAN}};
int[] resp = AlgorithmUtil.findSequence(graph);
int[] goal = new int[] {};
assert isEqual(resp, goal);
}
@Test
public void findSequence4() {
int[][] graph = new int[][] {{NAN, 1, NAN, NAN, NAN, NAN, NAN}, {NAN, NAN, NAN, NAN, NAN, NAN, NAN},
{1, NAN, NAN, 1, NAN, NAN, NAN}, {1, NAN, NAN, NAN, NAN, NAN, NAN}, {NAN, NAN, 1, NAN, NAN, NAN, NAN},
{NAN, NAN, 1, NAN, 1, NAN, NAN}, {NAN, NAN, NAN, NAN, NAN, 1, NAN}};
int[] resp = AlgorithmUtil.findSequence(graph);
int[] goal = new int[] {6, 5, 4, 2, 3, 0, 1};
assert isEqual(resp, goal);
}
private boolean isEqual(int[] numA, int[] numB) {
if (numA.length == numB.length) {
for (int i = 0; i < numA.length; i++) {
if (numA[i] != numB[i]) {
return false;
}
}
return true;
}
return false;
}
}
知识背景
leetcode 210. 课程表 II
简单方法判断邻接矩阵的有向图是否有环
如有不同见解,欢迎留言讨论