图算法
图的存储:
邻接矩阵和邻接表
1 2 3 4
| List<Integer> graph[]=new List[numCourses]; for(int i=0;i<numCourses;i++){ graph[i]=new LinkedList<>(); }
|
首先,图的遍历和树的遍历的区别
图存在环,从一个节点遍历可能会走到自身;而树从一个节点出发,一定会走到叶子节点,所以遍历图需要辅助数组visited来记录是否被访问过。【所以,如果图一定不存在环,就可以不使用visited数组,】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| boolean[] visited;
boolean[] onPath;
void traverse(Graph graph, int s,boolean[] visited,boolean[] onPath) { if (visited[s]) return; visited[s] = true; onPath[s] = true; for (int neighbor : graph.neighbors(s)) { traverse(graph, neighbor); } onPath[s] = false; }
|
图遍历与回溯算法的比较 【这个还挺难的】
回溯算法做选择是在for循环里面,而图遍历是在for循环外面,两者有何区别


环检测
课程表
首先,需要将题目的输入转化成图。
方法1:使用dfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class Solution { boolean isCircle; public boolean canFinish(int numCourses, int[][] prerequisites) { int n=prerequisites.length; List<Integer> graph[]=new LinkedList[numCourses]; for(int i=0;i<numCourses;i++){ graph[i]=new LinkedList<Integer>(); } for(int i=0;i<n;i++){ graph[prerequisites[i][1]].add(prerequisites[i][0]); } boolean visited[]=new boolean[numCourses]; for(int i=0;i<numCourses;i++){ if(isCircle){ break; } if(!visited[i]){ boolean path[]=new boolean[numCourses]; traverse(graph,visited,path,i); } } return !isCircle; } void traverse(List<Integer> graph[],boolean visited[],boolean[] path,int i){ if(isCircle){ return; } if(path[i]){ isCircle=true; return; } if(visited[i]){ return; } visited[i]=true; path[i]=true; for(int j=0;j<graph[i].size();j++){ traverse(graph,visited,path,graph[i].get(j)); } path[i]=false; } }
|
方法2:使用bfs
bfs记录每个结点的入度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public boolean canFinish(int numCourses, int[][] prerequisites) { List<Integer>[] graph = buildGraph(numCourses, prerequisites); int[] indegree = new int[numCourses]; for (int[] edge : prerequisites) { int from = edge[1], to = edge[0]; indegree[to]++; }
Queue<Integer> q = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { if (indegree[i] == 0) { q.offer(i); } }
int count = 0; while (!q.isEmpty()) { int cur = q.poll(); count++; for (int next : graph[cur]) { indegree[next]--; if (indegree[next] == 0) { q.offer(next); } } }
return count == numCourses; }
List<Integer>[] buildGraph(int n, int[][] edges) { }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { int n=prerequisites.length; List<Integer> graph[]=new LinkedList[numCourses]; int indegree[]=new int[numCourses]; for(int i=0;i<numCourses;i++){ graph[i]=new LinkedList<Integer>(); } for(int i=0;i<n;i++){ graph[prerequisites[i][1]].add(prerequisites[i][0]); indegree[prerequisites[i][0]]++; } Queue<Integer> q=new LinkedList<Integer>(); for(int i=0;i<numCourses;i++){ if(indegree[i]==0){ q.offer(i); } } int count=0; while(!q.isEmpty()){ int cur=q.poll(); count++; for(int i=0;i<graph[cur].size();i++){ int temp=graph[cur].get(i); indegree[temp]--; if(indegree[temp]==0){ q.offer(temp); } } } return count==numCourses; } }
|
拓扑排序
后序遍历的反转就是拓扑序列

方法1:dfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| class Solution { boolean isCircle; List<Integer> res; int index=0; public int[] findOrder(int numCourses, int[][] prerequisites) { List<Integer> graph[]=new LinkedList[numCourses]; for(int i=0;i<numCourses;i++){ graph[i]=new LinkedList<Integer>(); } int n=prerequisites.length; for(int i=0;i<n;i++){ graph[prerequisites[i][1]].add(prerequisites[i][0]); } res=new ArrayList<>(); boolean visited[]=new boolean[numCourses]; for(int i=0;i<numCourses;i++){ if(isCircle){ break; } if(!visited[i]){ boolean[] path=new boolean[numCourses]; traverse(graph,visited,path,i); } } if(isCircle){ return new int[0]; }else{ Collections.reverse(res); int ans[]=new int[numCourses]; for(int i=0;i<numCourses;i++){ ans[i]=res.get(i); } return ans; } }
void traverse(List<Integer> graph[],boolean visited[],boolean path[],int i){ if(isCircle){ return; } if(path[i]){ isCircle=true; return; } if(visited[i]){ return; } path[i]=true; visited[i]=true; for(int j=0;j<graph[i].size();j++){ traverse(graph,visited,path,graph[i].get(j)); } res.add(i); path[i]=false; } }
|
方法2:bfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Solution { public int[] findOrder(int numCourses, int[][] prerequisites) { int indegree[]=new int[numCourses]; List<Integer> graph[]=new LinkedList[numCourses]; for(int i=0;i<numCourses;i++){ graph[i]=new LinkedList<Integer>(); } int n=prerequisites.length; for(int i=0;i<n;i++){ graph[prerequisites[i][1]].add(prerequisites[i][0]); indegree[prerequisites[i][0]]++; } int res[]=new int[numCourses]; int index=0; Queue<Integer> q=new LinkedList<Integer>(); for(int i=0;i<numCourses;i++){ if(indegree[i]==0){ q.offer(i); } } while(!q.isEmpty()){ int cur=q.poll(); res[index++]=cur; for(int i=0;i<graph[cur].size();i++){ int temp=graph[cur].get(i); indegree[temp]--; if(indegree[temp]==0){ q.offer(temp); } } } if(index==numCourses){ return res; }else{ return new int[0]; } } }
|
二分图判定
二分图作用:
二分图可以高效地存储数据
比如存储演员和电影之间的关系,一个演员可以参演多部电影,一个电影由多个演员参演
通过HashMap存储电影和演员之间的映射,可以快速查询到一部电影由哪些演员参演
拿如果需要快速查询一个演员参演哪些电影,我们需要通过存储另外一个HashMap,这样,需要两个哈希表分别存储「每个演员到电影列表」的映射和「每部电影到演员列表」的映射。但如果用「图」结构存储,将电影和参演的演员连接,很自然地就成为了一幅二分图

思路:遍历一遍图,一边遍历,一边染色,看看能不能用两种颜色给所有结点染色,且相邻结点颜色不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| boolean ok=true;
void traverse(Graph graph, boolean[] visited, boolean color[],int v) { if(!ok){ return; } visited[v] = true; for (int neighbor : graph.neighbors(v)) { if (!visited[neighbor]) { color[neighbor]=!color[v]; traverse(graph, visited, color,neighbor); } else { if(color[neighbor]==color[v]){ ok=false; return; } } } }
void traverse(Graph graph, boolean[] visited, boolean color[],int start) { Queue q; q.offer(start); visited[start]=true; while(!q.isEmpty()){ int v=q.poll(); for (int w : graph[v]) { if (!visited[w]) { color[w] = !color[v]; visited[w] = true; q.offer(w); } else { if (color[w] == color[v]) { ok = false; return; } } } } }
|
判断二分图
可能的二分法
并查集Union-Find算法
使用森林(若干棵树)来表示图的连通性。 树的每个结点有一个指针都指向其父结点,若为根结点则指向自己。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class UF { private int count; private int[] parent;
public UF(int n) { this.count = n; parent = new int[n]; for (int i = 0; i < n; i++) parent[i] = i; }
public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; parent[rootP] = rootQ; count--; } public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; }
private int find(int x) { while (parent[x] != x) x = parent[x]; return x; }
public int count() { return count; } }
|
时间复杂度取决于find()方法,也就是树的高度,最坏情况是一个不平衡树,时间复杂度为O(n)
优化:通过路径压缩,在O(1)时间找到某个结点的根节点
1 2 3 4 5 6 7
| public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; }
|
被围绕的区域
方法1:dfs

方法2:Union-Find算法

与dummy相连的’O’不需要被替换成’X’
等式方程的可满足性
让元素分门别类,建立连通关系