二分图

如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。

1. 判断是否为二分图

  1. Is Graph Bipartite? (Medium)

Leetcode / 力扣

  1. Input: [[1,3], [0,2], [1,3], [0,2]]
  2. Output: true
  3. Explanation:
  4. The graph looks like this:
  5. 0----1
  6. | |
  7. | |
  8. 3----2
  9. We can divide the vertices into two groups: {0, 2} and {1, 3}.
  1. Example 2:
  2. Input: [[1,2,3], [0,2], [0,1,3], [0,2]]
  3. Output: false
  4. Explanation:
  5. The graph looks like this:
  6. 0----1
  7. | \ |
  8. | \ |
  9. 3----2
  10. We cannot find a way to divide the set of nodes into two independent subsets.
  1. public boolean isBipartite(int[][] graph) {
  2. int[] colors = new int[graph.length];
  3. Arrays.fill(colors, -1);
  4. for (int i = 0; i < graph.length; i++) { // 处理图不是连通的情况
  5. if (colors[i] == -1 && !isBipartite(i, 0, colors, graph)) {
  6. return false;
  7. }
  8. }
  9. return true;
  10. }
  11. private boolean isBipartite(int curNode, int curColor, int[] colors, int[][] graph) {
  12. if (colors[curNode] != -1) {
  13. return colors[curNode] == curColor;
  14. }
  15. colors[curNode] = curColor;
  16. for (int nextNode : graph[curNode]) {
  17. if (!isBipartite(nextNode, 1 - curColor, colors, graph)) {
  18. return false;
  19. }
  20. }
  21. return true;
  22. }

拓扑排序

常用于在具有先序关系的任务规划中。

1. 课程安排的合法性

  1. Course Schedule (Medium)

Leetcode / 力扣

  1. 2, [[1,0]]
  2. return true
  1. 2, [[1,0],[0,1]]
  2. return false

题目描述:一个课程可能会先修课程,判断给定的先修课程规定是否合法。

本题不需要使用拓扑排序,只需要检测有向图是否存在环即可。

  1. public boolean canFinish(int numCourses, int[][] prerequisites) {
  2. List<Integer>[] graphic = new List[numCourses];
  3. for (int i = 0; i < numCourses; i++) {
  4. graphic[i] = new ArrayList<>();
  5. }
  6. for (int[] pre : prerequisites) {
  7. graphic[pre[0]].add(pre[1]);
  8. }
  9. boolean[] globalMarked = new boolean[numCourses];
  10. boolean[] localMarked = new boolean[numCourses];
  11. for (int i = 0; i < numCourses; i++) {
  12. if (hasCycle(globalMarked, localMarked, graphic, i)) {
  13. return false;
  14. }
  15. }
  16. return true;
  17. }
  18. private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked,
  19. List<Integer>[] graphic, int curNode) {
  20. if (localMarked[curNode]) {
  21. return true;
  22. }
  23. if (globalMarked[curNode]) {
  24. return false;
  25. }
  26. globalMarked[curNode] = true;
  27. localMarked[curNode] = true;
  28. for (int nextNode : graphic[curNode]) {
  29. if (hasCycle(globalMarked, localMarked, graphic, nextNode)) {
  30. return true;
  31. }
  32. }
  33. localMarked[curNode] = false;
  34. return false;
  35. }

2. 课程安排的顺序

  1. Course Schedule II (Medium)

Leetcode / 力扣

  1. 4, [[1,0],[2,0],[3,1],[3,2]]
  2. There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3].

使用 DFS 来实现拓扑排序,使用一个栈存储后序遍历结果,这个栈的逆序结果就是拓扑排序结果。

证明:对于任何先序关系:v->w,后序遍历结果可以保证 w 先进入栈中,因此栈的逆序结果中 v 会在 w 之前。

  1. public int[] findOrder(int numCourses, int[][] prerequisites) {
  2. List<Integer>[] graphic = new List[numCourses];
  3. for (int i = 0; i < numCourses; i++) {
  4. graphic[i] = new ArrayList<>();
  5. }
  6. for (int[] pre : prerequisites) {
  7. graphic[pre[0]].add(pre[1]);
  8. }
  9. Stack<Integer> postOrder = new Stack<>();
  10. boolean[] globalMarked = new boolean[numCourses];
  11. boolean[] localMarked = new boolean[numCourses];
  12. for (int i = 0; i < numCourses; i++) {
  13. if (hasCycle(globalMarked, localMarked, graphic, i, postOrder)) {
  14. return new int[0];
  15. }
  16. }
  17. int[] orders = new int[numCourses];
  18. for (int i = numCourses - 1; i >= 0; i--) {
  19. orders[i] = postOrder.pop();
  20. }
  21. return orders;
  22. }
  23. private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Integer>[] graphic,
  24. int curNode, Stack<Integer> postOrder) {
  25. if (localMarked[curNode]) {
  26. return true;
  27. }
  28. if (globalMarked[curNode]) {
  29. return false;
  30. }
  31. globalMarked[curNode] = true;
  32. localMarked[curNode] = true;
  33. for (int nextNode : graphic[curNode]) {
  34. if (hasCycle(globalMarked, localMarked, graphic, nextNode, postOrder)) {
  35. return true;
  36. }
  37. }
  38. localMarked[curNode] = false;
  39. postOrder.push(curNode);
  40. return false;
  41. }

并查集

并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。

1. 冗余连接

  1. Redundant Connection (Medium)

Leetcode / 力扣

  1. Input: [[1,2], [1,3], [2,3]]
  2. Output: [2,3]
  3. Explanation: The given undirected graph will be like this:
  4. 1
  5. / \
  6. 2 - 3

题目描述:有一系列的边连成的图,找出一条边,移除它之后该图能够成为一棵树。

  1. public int[] findRedundantConnection(int[][] edges) {
  2. int N = edges.length;
  3. UF uf = new UF(N);
  4. for (int[] e : edges) {
  5. int u = e[0], v = e[1];
  6. if (uf.connect(u, v)) {
  7. return e;
  8. }
  9. uf.union(u, v);
  10. }
  11. return new int[]{-1, -1};
  12. }
  13. private class UF {
  14. private int[] id;
  15. UF(int N) {
  16. id = new int[N + 1];
  17. for (int i = 0; i < id.length; i++) {
  18. id[i] = i;
  19. }
  20. }
  21. void union(int u, int v) {
  22. int uID = find(u);
  23. int vID = find(v);
  24. if (uID == vID) {
  25. return;
  26. }
  27. for (int i = 0; i < id.length; i++) {
  28. if (id[i] == uID) {
  29. id[i] = vID;
  30. }
  31. }
  32. }
  33. int find(int p) {
  34. return id[p];
  35. }
  36. boolean connect(int u, int v) {
  37. return find(u) == find(v);
  38. }
  39. }