List<Integer>[] buildGraph(int numCourses, int[][] prerequisites) {
// 图中共有 numCourses 个节点
List<Integer>[] graph = new LinkedList[numCourses];
for (int i = 0; i < numCourses; i++) {
graph[i] = new LinkedList<>();
}
for (int[] edge : prerequisites) {
int from = edge[1], to = edge[0];
// 添加一条从 from 指向 to 的有向边
// 边的方向是「被依赖」关系,即修完课程 from 才能修课程 to graph[from].add(to);
}
return graph;
}
分析:
- 创建一个邻接表,使用的是list的数组的形式,每一个链表中存储着对应节点的信息
- 使用for循环,进行初始化数组(一个数组必须进行初始化)graph[i] = new LinkedList<>(); 对应的每一个点都是一个链表进行初始化
- 填充对应一个节点的链表信息,二维数组中一个元素描述的是两个节点的关系,将其对应的节点的链表进行填充
总结:
邻接表分为两部分:第一是数组,第二是链表
- 建一个链表数组,初始化大小为课程的数组,即节点数
- 给每一个节点创建链表,节点是课程的数目,采用for循环
visited 记录哪些节点被遍历过,而 onPath 记录当前递归堆栈中有哪些节点,它们的作用不同,所以并不重复。
类比贪吃蛇游戏,visited 记录蛇经过过的格子,而 onPath 仅仅记录蛇身。onPath 用于判断是否成环,类比当贪吃蛇自己咬到自己(成环)的场景。
boolean[] onPath;
boolean[] visited;
boolean hasCycle = false;
void traverse(List<Integer>[] graph, int s) {
if (onPath[s]) {
// 发现环!!!
hasCycle = true;
}
if (visited[s] || hasCycle) {
return;
}
// 将节点 s 标记为已遍历
visited[s] = true;
// 开始遍历节点 s
onPath[s] = true;
for (int t : graph[s]) {
traverse(graph, t);
}
// 节点 s 遍历完成
onPath[s] = false;
}
使用OnPath数组可减少计算量,只维护当前visited进入时的节点开始,结束时,将Onpath进行变为false。相当于哨兵,当再次出现Onpath时候,相当于贪吃蛇咬住尾巴的时候
进阶:
返回成环时候的节点 类似于判断链表成环,快慢指针,返回相遇的时候的点
拓扑排序
拓扑排序是指对一个有向无环图的节点进行排序之后得到的序列
如果存在一条从节点 A 指向节点 B 的边,那么在拓扑排序的序列中节点 A 出现在节点 B 的前面。
一个有向无环图可以有一个或多个拓扑排序序列,但无向图或有环的有向图都不存在拓扑排序。
一种常用的拓扑排序算法是每次从有向无环图中取出一个入度为 0 的节点添加到拓扑排序序列之中,然后删除该节点及所有以它为起点的边。重复这个步骤,直到图为空或图中不存在入度为 0 的节点
- 如果最终图为空,那么图是有向无环图,此时就找到了该图的一个拓扑排序序列
- 如果最终图不为空并且已经不存在入度为 0 的节点,那么图中一定有环(可以使用拓扑排序来判断一个有向图是否有环)。
主要应用的问题
- 大一时一定要修完这门课,大二才学第二门课,这种排课问题就是拓扑排序问题。
总结:
- 采用DFS,不会涉及到出度和入度的问题(深度优先遍历,采用递归的方式)
- 采用BFS,主要思路如下 (采用队列的方式)拓扑排序,判断是否有环
1、构建邻接表,边的方向为被依赖关系
2、构建入度数组,用来记录每个节点的入度,这个入度数组是根据关系的二维数组进行创建的 indegree[to]++指向to的元素入度++
3、根据入度初始化队列中的节点,遍历所有的节点,将入度为0的节点加入到队列中
4、如果队列不为空,开始弹出队列,使用一个全局变量记录弹出队列的个数
5、开始遍历与入度为0相邻的节点,使用for(int next:graph[cur]),遍历的next入度--,当它为0则加入队列 6、判断count的数量是否等于节点的数量,等于则无环,不等则有环 7、入度为0进队列。开始遍历减入度,若再为0进队列,最后count比较节点数
使用map集合来创建表
Map<Integer,List<Integer>> buildGraph(int numCourses,int [][]pre){
//初始化数组
//List<Integer>[] graph= new LinkedList<>();
Map<Integer,List<Integer>> graph = new HashMap<>();
//初始化链表
//graph[i]=new LinkedList<>();
for(int i = 0;i < numCourses;i++){
garph.put(i,new LinkedList<>());
}
//将关系初始化
for(int[] edge:pre){
int from=edge[1];
int to =edge[0];
graph.get(form).add(to);
}
}