拓扑排序
拓扑排序是,从题目中知道 事情是存在先后顺序被执行,则可以想到
- 构建一副图
- 对图中的顶点进行拓扑排序 ,同时需要判断图中是否存在环
拓扑排序存在两种实现方式
- 深度优先搜索
- 广度优先搜索
深度优先拓扑排序的实现
import java.util.List;
/**
* 拓扑排序 深度优先搜索模板
* 深度优先搜索是一种「逆向思维」,最先被收集节点是在拓扑排序中最后面的节点
*/
public class OrderBfs {
/**
* 构造有向图对象 优先级高的顶点指向 优先级低的
*/
List< List< Integer > > edgs;
/**
* 记录顶点的搜索情况 ,每个顶点在搜索的过程中 有三种状态
* 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
*/
int[] marked;
/**
* 标记图中是否存在环
*/
boolean hasCycle;
/**
* 收集拓扑排序的结果
*/
int[] result;
/**
* 每次都是遍历到最底层,然后对应收集顶点
*/
int index;
public int[] findOrder (int numCourses, int[][] prerequisites) {
//从题意知道是需要用拓扑排序,先构建一副图
//第一步 初始化存放顶点的图
edgs = new ArrayList<>();
//第二步 每个顶点的邻接表也初始化
for (int i = 0; i < numCourses; i++) {
edgs.add(new ArrayList<>());
}
//根据题意顶点的邻接表进行赋值
//这里注意,要让优先级高的顶点 指向优先级点低的
for (int[] arr : prerequisites) {
edgs.get(arr[1]).add(arr[0]);
}
//初始化顶点搜索情况标记数组
marked = new int[numCourses];
//初始化 标记是否存在环 最开始是没有
hasCycle = false;
//初始化 结果数组
result = new int[numCourses];
//因为每次都是从深度搜索到底层,顶点是优先加入的数组的话 则 index = 0
index = numCourses - 1;
//遍历顶点
for (int i = 0; i < numCourses; i++) {
//当已经出现环了,则不可能存在拓扑排序结果
if (hasCycle) {
return new int[0];
} else {
//从未搜索过的顶点 开始深度搜索
if (marked[i] == 0) {
dfs(i);
}
}
}
//如果没有环,那么就有拓扑排序
return result;
}
/**
* 从顶点i 开始进行深度搜索
*
* @param i 顶点i
*/
private void dfs (int i) {
//将顶点标记为「搜索中」
marked[i] = 1;
//遍历顶点i 的邻接表
for (int v : edgs.get(i)) {
/**
* 对于当前搜索的顶点的 ,存在三种情况
* 第一种 情况,当前的顶点未搜索过
*/
if (marked[v] == 0) {
//对这个顶点进行深度搜索
dfs(v);
//经过一轮搜索,需要判断是否存在环
if (hasCycle) {
//存在环 直接中止
return;
}
/**
* 如果当前要处理顶点 已经处于正在搜索的过程
* 说明这个顶点在这次深度搜索中 反复被搜索,形成了环
*/
} else if (marked[v] == 1) {
//标记出现了环
hasCycle = true;
//搜索中止
return;
/**
* 第三种情况,这个顶点已经是搜索完了,因此不用处理
*/
} else {
//不做处理
}
}
//当对顶点的邻接表进行深度搜索完后 ,标记该顶点已经被搜索过了
marked[i] = 2;
//拓扑排序数组中收集该顶点
result[index--] = i;
}
}
广度优先搜索实现拓扑排序
使用一个队列来进行广度优先搜索,开始时,所有入度为 0 的节点都被放入队列中,它们就是可以作为拓扑排序最前面的节点,并且它们之间的相对顺序是无关紧要的
1、在广度优先搜索的每一步中,我们取出队首的节点 u:
2、我们将 u 放入答案中;
3、我们移除 u 的所有出边,也就是将 u 的所有相邻节点的入度减少 11。如果某个相邻节点 vv 的入度变为 00,那么我们就将 v 放入队列中
广度搜索 每次都是加入到入度为0 的顶点,在搜索完 判断我们收集到的顶点是否 和 图中顶点相等,相等说明存在环
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* 通过广度优先搜索实现拓扑排序
* 这是一种正向思维,顺序地生成拓扑排序
*/
public class OrderBFS {
/**
* 构造有向图对象 优先级高的顶点指向 优先级低的
*/
List< List< Integer > > edgs;
/**
* 记录下每个顶点的入度
*/
int[] inDegree;
/**
* 收集拓扑排序的结果
*/
int[] result;
/**
* result 收集的下标
*/
int index;
public int[] findOrder (int numCourses, int[][] prerequisites) {
//从题意知道是需要用拓扑排序,先构建一副图
//初始化记录顶点入度的数组
inDegree = new int[numCourses];
// 初始化存放顶点的图
edgs = new ArrayList<>();
// 每个顶点的邻接表也初始化
for (int i = 0; i < numCourses; i++) {
edgs.add(new ArrayList<>());
}
//根据题意顶点的邻接表进行赋值
//这里注意,要让优先级高的顶点 指向优先级点低的
for (int[] arr : prerequisites) {
edgs.get(arr[1]).add(arr[0]);
//每次被指向的顶点 对应的入度加1
++inDegree[arr[0]];
}
//初始化收集拓扑排序结果的数组
result = new int[numCourses];
//初始化收集数组开始收集 的索引
index = 0;
//创建队列,队列中存放的是 入度为0 的顶点
Queue< Integer > queue = new LinkedList<>();
//先将入度为0的顶点入队
for (int v = 0; v < inDegree.length; v++) {
if (inDegree[v] == 0) {
queue.add(v);
}
}
// 下面这种写法是错误 ,因为加入队列 是数组顶点为0索引 而不是 对应的值
// for (int v : inDegree) {
// if (v == 0) {
// queue.add(v); 相当于 queue.add(0);
// }
// }
//开始弹出顶点,进行操作
while (!queue.isEmpty()) {
//从对首弹出一个顶点
int u = queue.poll();
//收集顶点
result[index++] = u;
//遍历u的邻接表,将u顶点指向的边消除,也就是u指向的顶点,入度全部减一
for (int v : edgs.get(u)) {
//v顶点的入度减1
--inDegree[v];
//判断消除u -> v边后,v顶点的入度是否为0 ,为0则入队
if (inDegree[v] == 0) {
queue.add(v);
}
}
}
//最后通过判断我们收集到入度为0 的顶点数量 是否等于图的顶点数量
//如果不相等说明 存在环,无法获得拓扑排序结果
if (index != numCourses) {
return new int[0];
}
return result;
}
}