前言
若用DAG图表示一个工程,其顶点表示活动,用有向边<Vi,Vj>表示活动Vi必须先于活动Vj进行的这样一种关系,则将这种有向图称为顶点表示活动的网络,即AOV网。在AOV网中,可以寻找是否有一条路径,让每个活动有序进行,即是否有拓扑排序。
一、案例
二、题解
package com.xhu.offer.offerII;
import java.util.*;
//课程顺序
public class FindOrder {
//DS-Set + 理清思路
//bug1:第35行,找到了符合的节点之后,没有加break,一直寻找。
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] order = new int[numCourses];
int[] visited = new int[numCourses];
int count = 0;
List<Set<Integer>> edges = new ArrayList<>();
for (int i = 0; i < numCourses; i++) edges.add(new HashSet<>());
for (int[] prerequisite : prerequisites) {
Set<Integer> edge = edges.get(prerequisite[0]);
edge.add(prerequisite[1]);
}
for (int i = 0; i < numCourses; i++) {
boolean flag = false;
for (int j = 0; j < edges.size(); j++) {
if (visited[j] == 0 && edges.get(j).size() == 0) {
order[count++] = j;
visited[j] = 1;
for (Set<Integer> edge : edges) {
edge.remove(j);
}
flag = true;
break;
}
}
if (!flag) return new int[]{};
}
return order;
}
//DS-Stack
public int[] findOrder2(int numCourses, int[][] prerequisites) {
int[] order = new int[numCourses];
int[] visited = new int[numCourses];
int count = 0;
Stack<Integer> stack = new Stack<>();
List<Set<Integer>> edges = new ArrayList<>();
for (int i = 0; i < numCourses; i++) edges.add(new HashSet<>());
for (int[] prerequisite : prerequisites) {
Set<Integer> edge = edges.get(prerequisite[0]);
edge.add(prerequisite[1]);
}
for (int i = 0; i < edges.size(); i++) {
if (visited[i] == 0 && edges.get(i).size() == 0) {
visited[i] = 1;
stack.push(i);
}
}
while (!stack.isEmpty()) {
int i = stack.pop();
order[count++] = i;
for (int j = 0; j < edges.size(); j++) {
if (visited[j] == 1) continue;
edges.get(j).remove(i);
if (0 == edges.get(j).size()) {
stack.push(j);
visited[j] = 1;
}
}
}
return count == numCourses ? order : new int[]{};
}
public static void main(String[] args) {
int n = 2;
int[][] data = new int[][]{{1, 0}};
new FindOrder().findOrder(n, data);
}
}
总结
1)拓扑排序
2)没有小错误就Debug,因为思想固化之后,逻辑会看不清楚,全部变为我以为的,调试可以看到真实的逻辑所展示的执行过程。
参考文献
附录
1、外星文字典
package com.xhu.offer.offerII;
import java.util.*;
//外星人字典
public class AlienOrder {
//core:26个小写字母,用单词顺序构建字典树,树的孩子节点添加按位置递增来加入,即表示它们之间的大小。
//再加入的过程中看当前字符是否已经在之间加入过,这个可以用HashMap来确定以前加入的位置,如果为length,则不管,否则返回空,表示不合法。
//在合法的构建字典树之后,每一个节点的孩子节点都可构成一个有向图,然后DFS来寻找是否有一条可达路径包含了所有字符
//总结:DS-plugin-Trie + DS-plugin-HashMap + DS-plugin-List + component-树遍历 + Pre-Graph + component-DFS
public String alienOrder(String[] words) {
final String none = "";
//构建字典树
boolean isTrie = trie(words);
if (!isTrie) return none;
//Pre-Graph
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
if (cur.isEnd) continue;
addEdge(cur, queue);
}
//拓扑排序-Stack
StringBuilder sb = new StringBuilder();
Stack<Integer> stack = new Stack<>();
int[] visited = new int[26];
for (int i = 0; i < edges.size(); i++) {
if (edges.get(i).size() == 0 && visited[i] == 0) {
stack.push(i);
visited[i] = 1;
}
}
if (stack.isEmpty()) return none;
while (!stack.isEmpty()) {
int idx = stack.pop();
sb.append(chs[idx]);
//remove
for (int i = 0; i < edges.size(); i++) {
edges.get(i).remove(idx);
if (visited[i] == 0 && edges.get(i).size() == 0) {
stack.push(i);
visited[i] = 1;
}
}
}
return sb.length() == nodeNum ? sb.toString() : none;
}
private void addEdge(TreeNode cur, Queue<TreeNode> queue) {
TreeNode[] next = cur.next;
queue.offer(next[0]);
for (int i = 0; i < next.length - 1; i++) {
if (next[i] != null) addNode(next[i].val);
if (next[i + 1] != null) {
addNode(next[i + 1].val);
queue.offer(next[i + 1]);
int idx = nodes.get(next[i + 1].val);
Set<Integer> edge = edges.get(idx);
edge.add(nodes.get(next[i].val));
}
}
}
private void addNode(char val) {
if (!nodes.containsKey(val)) {
nodes.put(val, nodeNum++);
chs[nodeNum - 1] = val;
edges.add(new HashSet<>());
}
}
Map<Character, Integer> nodes = new HashMap<>();//将节点映射成idx,配合edges。
char[] chs = new char[26];//通过id反向得到val
List<Set<Integer>> edges = new ArrayList<>();//用数组式的邻接表存储边。
int nodeNum = 0;//记录有多少节点,顺便作为每个节点的index。
private boolean trie(String[] words) {
for (String word : words) {
char[] chs = word.toCharArray();
TreeNode cur = root;
for (char ch : chs) {
int idx = cur.index.getOrDefault(ch, -1);
if (idx == -1) idx = cur.count++;
if (idx < cur.count - 1) return false;
if (cur.next[idx] == null) {
cur.next[idx] = new TreeNode();
cur.next[idx].val = ch;
}
if (!cur.index.containsKey(ch)) cur.index.put(ch, idx);
if (cur.isEnd) cur.isEnd = false;
cur = cur.next[idx];
}
if (cur.next[0] == null) cur.isEnd = true;//用例有问题,该处改为cur.isEnd = true; + 用set计有多少字符 + return nums.size() == sb.length()
}
return true;
}
TreeNode root = new TreeNode();
{
root.val = '#';
}
public class TreeNode {
char val;
int count;
boolean isEnd;
TreeNode[] next = new TreeNode[26];
Map<Character, Integer> index = new HashMap<>();
}
}
2、重建序列
package com.xhu.offer.offerII;
import java.util.*;
//重建序列(题目要求重建的序列必须是唯一序列,但是案例不是唯一的)
public class SequenceReconstruction {
//Pre-Graph-Map + DS-Queue + component-(逆)拓扑排序
public boolean sequenceReconstruction(int[] org, List<List<Integer>> seqs) {
//构建有向图,设置好每个节点的入度。
for (List<Integer> seq : seqs) {
//构建该节点值为id 的 所有边。
int size = seq.size();
addNode(seq.get(0));
for (int i = 0; i < size - 1; i++) {
addNode(seq.get(i));
addNode(seq.get(i + 1));
int id = seq.get(i), nextId = seq.get(i + 1);
Set<Integer> edgeForI = graph.get(id);
if (!edgeForI.contains(nextId)) {
//更新入度
in.put(nextId, in.get(nextId) + 1);
edgeForI.add(nextId);
}
}
}
int len = org.length;
if (len != graph.size()) return false;
//拓扑排序
//1-找到入度为0的节点,让它进入队列
Queue<Integer> sort = new LinkedList<>();
for (int id : in.keySet()) {
int inDegree = in.get(id);
if (inDegree == 0) sort.offer(id);
}
if (sort.size() != 1) return false;
//开始拓扑排序
int count = 0;
while (!sort.isEmpty()) {
if (sort.size() != 1) return false;
int id = sort.poll();
if (id != org[count++]) return false;
//更新入度
Set<Integer> edgeForId = graph.get(id);
for (Integer nextId : edgeForId) {
in.put(nextId, in.get(nextId) - 1);
if (in.get(nextId) == 0) sort.offer(nextId);
}
}
return count == len ? true : false;
}
private void addNode(Integer id) {
if (!graph.containsKey(id)) {
graph.put(id, new HashSet<>());
//设置入度
in.put(id, 0);
}
}
Map<Integer, Set<Integer>> graph = new HashMap<>();//有向图构造
Map<Integer, Integer> in = new HashMap<>();//每个节点的入读。
}