定义
给定一个包含n个节点的有向图,给出它的节点编号的一个排列,如果满足对于图中的任意一条有向边(u,v),u的排列都出现在v的前面,则称该排列是图的拓扑排序
- 当图中存在环,那么它不存在拓扑排序
- 当图是有向无环图,那么它的拓扑排序不止一种
对于拓扑排序有dfs和bfs两种方法可以求解
例题-课程顺序
dfs解法
class Solution {
// 存储有向图
List<List<Integer>> edges;
// 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
int[] visited;
// 结果数组
int[] result;
// 判断有向图中是否有环
boolean valid = true;
// 数组下标,由于dfs是倒着加入结果集的,所以需要从后往前添加
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<Integer>());
}
visited = new int[numCourses];
result = new int[numCourses];
index = numCourses - 1;
//构造图
for (int[] info : prerequisites) {
edges.get(info[1]).add(info[0]);
}
// 每次挑选一个「未搜索」的节点,开始进行深度优先搜索
for (int i = 0; i < numCourses && valid; ++i) {
if (visited[i] == 0) {
dfs(i);
}
}
if (!valid) {
return new int[0];
}
// 如果没有环,那么就有拓扑排序
return result;
}
public void dfs(int u) {
// 将节点标记为「搜索中」
visited[u] = 1;
// 搜索其相邻节点
// 只要发现有环,立刻停止搜索
for (int v: edges.get(u)) {
// 如果「未搜索」那么搜索相邻节点
if (visited[v] == 0) {
dfs(v);
if (!valid) {
return;
}
}
// 如果「搜索中」说明找到了环
else if (visited[v] == 1) {
valid = false;
return;
}
}
// 将节点标记为「已完成」
visited[u] = 2;
// 将节点加入结果集
result[index--] = u;
}
}
BFS解法
//记录下一个可以访问的数
List<List<Integer>> canGo;
//记录每个数的入度,当入度为0的时候可以直接访问
int[] inDegNum;
public int[] findOrder(int numCourses, int[][] prerequisites) {
canGo = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
canGo.add(new ArrayList<>());
}
inDegNum = new int[numCourses];
for (int[] prerequisite : prerequisites) {
int to = prerequisite[0];
int from = prerequisite[1];
//to的入度增加,from可以造访的点添加to
inDegNum[to]++;
canGo.get(from).add(to);
}
Queue<Integer> queue = new LinkedList<>();
//如果入度数为零则添加当前节点
for (int i = 0; i < inDegNum.length; i++) {
if (inDegNum[i] == 0) queue.add(i);
}
List<Integer> res = new ArrayList<>();
//拓扑排序过程
while (!queue.isEmpty()) {
Integer cur = queue.poll();
res.add(cur);
for (Integer next : canGo.get(cur)) {
inDegNum[next]--;
if (inDegNum[next] == 0) queue.offer(next);
}
}//拓扑排序失败
if (res.size() != numCourses) return new int[]{};
return res.stream().mapToInt(Integer::intValue).toArray();
}
bfs的算法步骤如下:
- 构建有向图
- 向队列中添加入度为0的节点(节点P),并且添加结果集
- 遍历所有入度有P的节点,入度数量-1,如果为0,加入队列
- 结果集数量不等于顶点数说明有环状结构导师结果集缺少,排除答案
- 结果集数量等于顶点数说明找到了一个正确结果
进阶例题-外星文字典
注意这里判断排序未完成的那段代码
//记录下一个可以访问的数
List<List<Integer>> canGo;
//记录每个数的入度,当入度为0的时候可以直接访问
int[] inDeg;
public String alienOrder(String[] words) {
//初始化入度数组
inDeg = new int[26];
Arrays.fill(inDeg, -1);
for (String word : words) {
for (int i = 0; i < word.length(); i++) {
inDeg[word.charAt(i) - 'a'] = 0;
}
}
//初始化canGo
canGo = new ArrayList<>(26);
for (int i = 0; i < 26; i++) {
canGo.add(new ArrayList<>());
}
//建图
for (int i = 0; i < words.length; i++) {
for (int j = i + 1; j < words.length; j++) {
String w1 = words[i], w2 = words[j];
int len = Math.min(w1.length(), w2.length());
for (int k = 0; k < len; k++) {
if (w1.charAt(k) == w2.charAt(k)) {
//排除abc ab这种情况
if (k == len - 1 && w1.length() < w2.length()) {
return "";
}
continue;
}
int from = w1.charAt(k) - 'a';
int to = w2.charAt(k) - 'a';
canGo.get(from).add(to);
inDeg[to]++;
//每两个单词只贡献一次字符集顺序
break;
}
}
}
//添加入度为0的节点
Queue<Character> queue = new LinkedList<>();
for (int i = 0; i < 26; i++) {
if (inDeg[i] == 0) queue.add((char) (i + 'a'));
}
//拓扑排序
StringBuilder builder = new StringBuilder();
while (!queue.isEmpty()) {
Character cur = queue.poll();
builder.append(cur);
for (Integer next : canGo.get(cur - 'a')) {
inDeg[next]--;
if(inDeg[next]==0)queue.offer((char)(next+'a'));
}
}
//检查所有入度节点是否都小于等于0,如果有一个还存在入度,说明排序未完成
for (int i = 0; i < 26; ++i) if (inDeg[i] > 0) return "";
return builder.toString();
}
进阶例题-重建序列
此题对于其他拓扑排序增加了一个唯一的条件
public boolean sequenceReconstruction(int[] org, List<List<Integer>> seqs) {
int n = org.length;
//用于判断特殊情况的set
HashSet<Integer> set = new HashSet<>();
for (List<Integer> seq : seqs) {
set.addAll(seq);
}
//结果集不完整
if (set.size() != n) return false;
//只有一个元素,且不在org数组中
if (n == 1 && !set.contains(org[0])) return false;
//初始化,为了一一对应,多添加一个位置
List<List<Integer>> canGo = new ArrayList<>(n + 1);
int[] inDegree = new int[n + 1];
for (int i = 0; i <= n; i++) {
canGo.add(new ArrayList<>());
}
//建图过程
for (List<Integer> seq : seqs) {
for (int i = 0; i < seq.size() - 1; i++) {
int from = seq.get(i), to = seq.get(i + 1);
if (!canGo.get(from).contains(to)) {
canGo.get(from).add(to);
inDegree[to]++;
}
}
}
//拓扑排序
Queue<Integer> queue = new LinkedList<>();
for (int i = 1; i <= n; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
int[] res = new int[n];
int index = 0;
while (!queue.isEmpty()) {
int size = queue.size();
//唯一重建的体现
if (size > 1) return false;
Integer poll = queue.poll();
res[index++] = poll;
for (Integer integer : canGo.get(poll)) {
inDegree[integer]--;
if (inDegree[integer] == 0) queue.offer(integer);
}
}
//判断是否为原数组
return Arrays.equals(res, org);
}