210.课程表Ⅱ
BFS:
- 「拓扑排序」是专门应用于有向图的算法;
- 这道题用 BFS 和 DFS 都可以完成,只需要掌握 BFS 的写法就可以了,BFS 的写法很经典;
- BFS 的写法就叫「拓扑排序」,这里还用到了贪心算法的思想,贪的点是:当前让入度为 0 的那些结点入队;
- 「拓扑排序」的结果不唯一;
- 删除结点的操作,通过「入度数组」体现,这个技巧要掌握;
- 「拓扑排序」的一个附加效果是:能够顺带检测有向图中是否存在环,这个知识点非常重要,如果在面试的过程中遇到这个问题,要把这一点说出来。
算法流程
1、在开始排序前,扫描对应的存储空间(使用邻接表),将入度为 0 的结点放入队列。
2、只要队列非空,就从队首取出入度为 0 的结点,将这个结点输出到结果集中,并且将这个结点的所有邻接结点(它指向的结点)的入度减 1,在减 1 以后,如果这个被减 1的结点的入度为 0,就继续入队。
3、当队列为空的时候,检查结果集中的顶点个数是否和课程数相等即可。
(思考这里为什么要使用队列?如果不用队列,还可以怎么做,会比用队列的效果差还是更好?)
在代码具体实现的时候,除了保存入度为 0 的队列,我们还需要两个辅助的数据结构:
1、邻接表:通过结点的索引,我们能够得到这个结点的后继结点;
2、入度数组:通过结点的索引,我们能够得到指向这个结点的结点个数。
class solution{
public int[] findOrder(int numCourses, int[][] prerequisites) {
if (numCourses <= 0) {
return new int[0];
}
//通过邻接表法构建图
HashSet[] adj = new HashSet[numCourses];
for(int i=0;i<numCourses;i++) {
adj[i] = new HashSet<>();
}
//建立点的入度数表:
int[] inDegree = new int[numCourses];
for(int[] p:prerequisites) {
adj[p[1]].add(p[0]);
inDegree[p[0]]++;
}
Queue queue = new LinkedList();
//BFS框架
//将0度数点放入队列中
for(int i=0;i<numCourses;i++) {
if(inDegree[i]==0) {
queue.add(i);
}
}
//res数组保存结果
int[] res = new int[numCourses];
int count = 0;
while(!queue.isEmpty()) {
int head = (int) queue.poll();
res[count] = head;
count++;
//将后继结点度数减一
Set<Integer> successors = adj[head];
for(int nextCourse:successors) {
inDegree[nextCourse]--;
//检测度数是否为0,是则入栈
if(inDegree[nextCourse] == 0) {
queue.offer(nextCourse);
}
}
}
//如果集合中点的数量不等于,存在环
if(count == numCourses) {
return res;
}
return new int[0];
}
}
DFS版:
对于图中的任意一个节点,它在搜索的过程中有三种状态,即:
「未搜索」:我们还没有搜索到这个节点;
「搜索中」:我们搜索过这个节点,但还没有回溯到该节点,即该节点还没有入栈,还有相邻的节点没有搜索完成);
「已完成」:我们搜索过并且回溯过这个节点,即该节点已经入栈,并且所有该节点的相邻节点都出现在栈的更底部的位置,满足拓扑排序的要求。
通过上述的三种状态,我们就可以给出使用深度优先搜索得到拓扑排序的算法流程,在每一轮的搜索搜索开始时,我们任取一个「未搜索」的节点开始进行深度优先搜索。我们将当前搜索的节点 u标记为「搜索中」,遍历该节点的每一个相邻节点 v:
如果 v 为「未搜索」,那么我们开始搜索 v,待搜索完成回溯到 u;
如果 v 为「搜索中」,那么我们就找到了图中的一个环,因此是不存在拓扑排序的;
如果 v 为「已完成」,那么说明 v 已经在栈中了,而 u 还不在栈中,因此 u 无论何时入栈都不会影响到 (u, v)
之前的拓扑关系,以及不用进行任何操作。
//DFS版
class solution{
//邻接表
List<List> edges;
//记录每个点的状态
int[] visited;
//记录结果
int[] result;
//判断是否有环
boolean valid = true;
//下标
int index;
public int[] findOrder(int numCourses, int[][] prerequisites) {
//构建图
edges = new ArrayList<List<Integer>>();
for(int i=0;i<numCourses;i++) {
edges.add(new ArrayList());
}
for(int[] info:prerequisites) {
edges.get(info[1]).add(info[0]);
}
visited = new int[numCourses];
result = new int[numCourses];
index = numCourses - 1;
//每次挑选一个未检索的结点,开始深度优先遍历
for(int i=0;i<numCourses&&valid;i++) {
if(visited[i] == 0) {
dfs(i);
}
}
//如果有环
if(!valid) {
return new int[0];
}
return result;
}
private void dfs(int i) {
// TODO Auto-generated method stub
//将当前节点标记为检索中
visited[i] = 1;
//检索相邻结点
for(int v:edges.get(i)) {
//如果未检索,检索相邻结点
if(visited[v] == 0) {
dfs(v);
//检测点的过程中发现了环
if(!valid)return;
}
//如果发现了正在检测的点。说明找到了环
else if(visited[v] == 1) {
valid = false;
return;
}
}
//标记为已完成
visited[i] = 2;
result[index--] = i;
}
}
蓝桥杯_发现环
拓扑排序思想,无向图度数
package lanqiao;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;
public class PREV_49 {
//结点度数表
static int[] degree;
//邻接表
static List<List<Integer>> gra;
//结果
static ArrayList result;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
//初始化
gra = new ArrayList<List<Integer>>();
for(int i=0;i<=m;i++) {
gra.add(new ArrayList<Integer>());
}
degree = new int[m+1];
Queue queue = new LinkedList();
result = new ArrayList();
//构建无向图
for(int i=0;i<m;i++) {
int a = sc.nextInt();
int b = sc.nextInt();
gra.get(a).add(b);
gra.get(b).add(a);
degree[b]++;
degree[a]++;
}
for(int i=1;i<=m;i++) {
if(degree[i]==1) {
queue.add(i);
result.add(i);
}
}
//BFS框架
while(!queue.isEmpty()) {
int head = (int) queue.poll();
for(int item:gra.get(head)) {
//将后继点度数减一
--degree[item];
if(degree[item]==1) {
queue.add(item);
result.add(item);
}
}
}
for(int i=1;i<=m;i++) {
if(!result.contains(i)) {
System.out.print(i+" ");
}
}
}
}