二分图
如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。
1. 判断是否为二分图
Leetcode (opens new window)/ 力扣
(1)深度优先遍历
class Solution {
// 未着色
private static final int UNCOLORED = 0;
// 红色
private static final int RED = 1;
// 绿色
private static final int GREEN = 2;
private int[] color;
private boolean flag;
// 转换成图的染色问题
public boolean isBipartite(int[][] graph) {
int n = graph.length;
this.flag = true;
this.color = new int[n];
Arrays.fill(this.color, UNCOLORED);
for (int i = 0; i < n && flag; i++) {
if (color[i] == UNCOLORED) {
dfs(i, RED, graph);
}
}
return flag;
}
// 深度优先遍历
private void dfs(int node, int c, int[][] graph) {
color[node] = c;
int cNext = c == RED ? GREEN : RED;
for (int temp : graph[node]) {
if (color[temp] == UNCOLORED) {
dfs(temp, cNext, graph);
if (!flag) {
return ;
}
} else if (color[temp] != cNext) {
this.flag = false;
return ;
}
}
}
}
(2)广度优先遍历
class Solution {
private static final int UNCOLORED = 0;
private static final int RED = 1;
private static final int GREEN = 2;
private int[] color;
public boolean isBipartite(int[][] graph) {
int n = graph.length;
this.color = new int[n];
Arrays.fill(this.color, UNCOLORED);
for (int i = 0; i < n; i++) {
if (color[i] == UNCOLORED) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(i);
color[i] = RED;
while (!queue.isEmpty()) {
int node = queue.poll();
int cNext = color[node] == RED ? GREEN : RED;
for (int temp : graph[node]) {
if (color[temp] == UNCOLORED) {
queue.offer(temp);
color[temp] = cNext;
} else if (color[temp] != cNext) {
return false;
}
}
}
}
}
return true;
}
}
拓扑排序
常用于在具有先序关系的任务规划中。
1. 课程安排的合法性
Leetcode (opens new window)/ 力扣
(1)BFS
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 1 生成入度表,并将对应的关系用集合表示
int[] counts = new int[numCourses];
List<List<Integer>> edges = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
edges.add(new ArrayList<>());
}
for (int[] value : prerequisites) {
edges.get(value[1]).add(value[0]);
counts[value[0]]++;
}
// 2 初始化队列,将入度为 0 的队列入队
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (counts[i] == 0) {
queue.add(i);
}
}
// 3 BFS,拓扑排序
int visited = 0;
while (!queue.isEmpty()) {
visited++;
int i = queue.poll();
for (int value : edges.get(i)) {
// 入度减 1
counts[value]--;
// 当入度为 0 时,加入队列
if (counts[value] == 0) {
queue.add(value);
}
}
}
// 如果不相等,说明有环
return visited == numCourses;
}
}
(2)DFS
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> list = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
list.add(new ArrayList<>());
}
int[] flags = new int[numCourses];
for (int[] cp : prerequisites) {
list.get(cp[1]).add(cp[0]);
}
for (int i = 0; i < numCourses; i++) {
if (!dfs(list, flags, i)) {
return false;
}
}
return true;
}
private boolean dfs(List<List<Integer>> list, int[] flags, int i) {
if (flags[i] == 1) {
return false;
}
if (flags[i] == -1) {
return true;
}
flags[i] = 1;
for(Integer j : list.get(i)) {
if(!dfs(list, flags, j)) {
return false;
}
}
flags[i] = -1;
return true;
}
}
2. 课程安排的顺序
Leetcode (opens new window)/ 力扣
(1)BFS
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
// 建立入度表,用集合表示图的关系
int[] counts = new int[numCourses];
// 图的关系集合
List<List<Integer>> edges = new ArrayList<>();
// 初始化集合
for (int i = 0; i < numCourses; i++) {
edges.add(new ArrayList<>());
}
// 将图之间对应的关系添加到集合中
for (int[] values : prerequisites) {
// [1, 0] 对应的关系为 0 -> 1
// 因为是先修了 0 才能修 1
edges.get(values[1]).add(values[0]);
// 对应的入度加 1
counts[values[0]]++;
}
// 将入度为 0 的课程入队列
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (counts[i] == 0) {
queue.add(i);
}
}
// BFS
int[] result = new int[numCourses];
int index = 0;
while (!queue.isEmpty()) {
int i = queue.poll();
// 加入到结果数组中
result[index++] = i;
for (int value : edges.get(i)) {
// 入度减 1
counts[value]--;
// 如果入度为 0 就加入到队列中去
if (counts[value] == 0) {
queue.add(value);
}
}
}
// 如果不等于课程数
if (index != numCourses) {
// 返回空数组
return new int[]{};
}
return result;
}
}
(2)DFS
class Solution {
// 存储有向图
private List<List<Integer>> edges;
// 标记每个节点的状态,0 未搜索 1 搜索中 2 已搜索
private int[] visited;
// 用数组来模拟栈,n - 1 为栈顶,0 为栈顶
private int[] result;
// 判断有向图中是否有环
private boolean valid = true;
// 栈下标
private int index;
public int[] findOrder(int numCourses, int[][] prerequisites) {
edges = new ArrayList<>();
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[] value : prerequisites) {
edges.get(value[1]).add(value[0]);
}
for (int i = 0; i < numCourses && valid; i++) {
if (visited[i] == 0) {
dfs(i);
}
}
if (valid == false) {
return new int[]{};
}
return result;
}
// 深度优先遍历
private void dfs(int i) {
// 将节点标记为搜索中
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;
}
}
并查集
并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。
1. 冗余连接
Leetcode (opens new window)/ 力扣
class Solution {
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
int[] parent = new int[n + 1];
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
for (int i = 0; i < n; i++) {
int[] edge = edges[i];
int node1 = edge[0];
int node2 = edge[1];
if (find(parent, node1) != find(parent, node2)) {
union(parent, node1, node2);
} else {
return edge;
}
}
return new int[0];
}
public void union(int[] parent, int index1, int index2) {
parent[find(parent, index1)] = find(parent, index2);
}
public int find(int[] parent, int index) {
if (parent[index] != index) {
parent[index] = find(parent, parent[index]);
}
return parent[index];
}
}