理论知识
有向图/无向图
在图中,若节点的边用箭头标明了边是有方向性的,则称这样的图为有向图,否则称为无向图。
出入度
在有向图中边的箭头指入的节点数量则为该节点的入度,从该节点指出的边则称为出度。
权重
图中边所标记的数
图的遍历
和多叉树的遍历很相似
bfs/dfs1 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 61 class Graph{ int v; private List< Integer > adj[]; public Graph(int num){ v = num; adj = new LinkedList[v]; for (int i = 0 ; i < num; i+ + ) adj[i] = new LinkedList<> (); } public void addEdge(int v,int w){ adj[v].add (w); } / / 借助队列:每轮将当前轮遍历,将自节点入队列 void bfs(int s){ boolean [] visited = new boolean [v]; LinkedList< Integer > queue = new LinkedList<> (); visited[s]= true ; queue.add(s); while (! queue.isEmpty()){ Integer poll = queue.poll(); System.out.println(poll);/ / 输出遍历的节点 List< Integer > edges = adj[poll]; for (Integer edge : edges) { if (! visited[edge]){ visited[edge]= true ; queue.offer(edge); } } } } / / 借助栈:一直压栈直到,弹栈后遇到没遍历过的再次压栈,如此反复 void dfs(int s){ boolean [] visited = new boolean [v]; Stack< Integer > stack = new Stack<> (); visited[s]= true ; stack.push(s); while (! stack.isEmpty()){ Integer poll = stack.pop(); System.out.println(poll); List< Integer > edges = adj[poll]; for (Integer edge : edges) { if (! visited[edge]){ visited[edge]= true ; stack.push(edge); } } } } } public static void main(String[] args) { Graph g = new Graph(4 ); g.addEdge(0 , 1 ); g.addEdge(0 , 2 ); g.addEdge(1 , 2 ); g.addEdge(2 , 0 ); g.addEdge(2 , 3 ); g.addEdge(3 , 3 ); System.out.println("Following is Breadth First Traversal (starting from vertex 2)"); g.bfs(2 ); g.dfs(2 ); }
拓扑排序 对一个有向无环图 (Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前 。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
实现拓扑的步骤
选择一个无前驱的节点开始(入度为0的节点)
从图中删除此顶点及所有出边。
重复上述两个步骤
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 public class Graph { private int V; private ArrayList<ArrayList<Integer>> adj; Graph(int v) { V = v; adj = new ArrayList <>(v); for (int i = 0 ; i < v; ++i) adj.add(new ArrayList <>()); } void addEdge (int v, int w) { adj.get(v).add(w); } void topologicalSort (int v, boolean visited[], Stack<Integer> stack) { visited[v] = true ; Integer i; Iterator<Integer> it = adj.get(v).iterator(); while (it.hasNext()) { i = it.next(); if (!visited[i]) topologicalSort(i, visited, stack); } stack.push(new Integer (v)); } void topologicalSort () { Stack<Integer> stack = new Stack <>(); boolean visited[] = new boolean [V]; for (int i = 0 ; i < V; i++) { if (visited[i] == false ) { topologicalSort(i, visited, stack); } } while (stack.empty() == false ) System.out.print(stack.pop() + " " ); } public static void main (String args[]) { Graph g = new Graph (6 ); g.addEdge(5 , 2 ); g.addEdge(5 , 0 ); g.addEdge(4 , 0 ); g.addEdge(4 , 1 ); g.addEdge(2 , 3 ); g.addEdge(3 , 1 ); g.topologicalSort(); } }
如何维护字典序最小的拓扑序?什么是最小子典序?
简单的理解方法是,把两种解看作两个数字1243 和 2143,小的那个数对应的排列就是字典序偏小的。
相关算法 Dijkstra 算法
最短路径算法
Prim 算法 Kruskal 算法 Bellman-Ford 算法 基于「队列」优化的 Bellman-Ford 算法 — SPFA 算法
拓扑排序之 Kahn 算法 相关题目 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 class Solution { public int secondMinimum(int n, int [][] edges, int time , int change) { List< Integer > [] graph = new List[n + 1 ]; for (int i = 0 ; i <= n; i+ + ) { graph[i] = new ArrayList< Integer > (); } for (int [] edge : edges) { graph[edge[0 ]].add (edge[1 ]); graph[edge[1 ]].add (edge[0 ]); } / / path[i][0 ] 表示从 1 到 i 的最短路长度,path[i][1 ] 表示从 1 到 i 的严格次短路长度 int [][] path = new int [n + 1 ][2 ]; for (int i = 0 ; i <= n; i+ + ) { Arrays.fill(path[i], Integer.MAX_VALUE); } path[1 ][0 ] = 0 ; Queue< int []> queue = new ArrayDeque< int []> (); queue.offer(new int []{1 , 0 }); while (path[n][1 ] = = Integer.MAX_VALUE) { int [] arr = queue.poll(); int cur = arr[0 ], len = arr[1 ]; for (int next : graph[cur]) { if (len + 1 < path[next][0 ]) { path[next][0 ] = len + 1 ; queue.offer(new int []{next, len + 1 }); } else if (len + 1 > path[next][0 ] && len + 1 < path[next][1 ]) { path[next][1 ] = len + 1 ; queue.offer(new int []{next, len + 1 }); } } } int ret = 0 ; for (int i = 0 ; i < path[n][1 ]; i+ + ) { if (ret % (2 * change) >= change) { ret = ret + (2 * change - ret % (2 * change)); } ret = ret + time ; } return ret; } }
克隆图 (medium)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 Solution { / / bfs / / 记录节点是否已经克隆:key= 旧节点,value = 新clone的节点 private HashMap < Node, Node> visited = new HashMap <> (); public Node cloneGraphDFS(Node node) { if (node = = null ) return node; / / 如果该节点已经被访问过了,则直接从哈希表中取出对应的克隆节点返回 if (visited.containsKey(node)) return visited.get(node); / / 克隆节点,注意到为了深拷贝我们不会克隆它的邻居的列表 Node cloneNode = new Node(node.val, new ArrayList()); / / 哈希表存储 visited.put(node, cloneNode); / / 遍历该节点的邻居并更新克隆节点的邻居列表 for (Node neighbor: node.neighbors) { cloneNode.neighbors.add(cloneGraph(neighbor)); } return cloneNode; } / / public Node cloneGraphBFS(Node node) { if (node = = null ) return node; HashMap< Node, Node> visited = new HashMap(); / / 将题目给定的节点添加到队列 LinkedList< Node> queue = new LinkedList< Node> (); queue.add(node); / / 克隆第一个节点并存储到哈希表中 visited.put(node, new Node(node.val, new ArrayList())); / / 广度优先搜索 while (! queue.isEmpty()) { / / 取出队列的头节点 Node n = queue.remove(); / / 遍历该节点的邻居 for (Node neighbor: n.neighbors) { if (! visited.containsKey(neighbor)) { / / 如果没有被访问过,就克隆并存储在哈希表中 visited.put(neighbor, new Node(neighbor.val, new ArrayList())); / / 将邻居节点加入队列中 queue.add(neighbor); } / / 更新当前节点的邻居列表 visited.get(n).neighbors.add(visited.get(neighbor)); } } return visited.get(node); } }
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 class Solution { List<List<Integer>> edges; int [] visited; boolean valid = true ; public boolean canFinish (int numCourses, int [][] prerequisites) { edges = new ArrayList <>(); visited = new int [numCourses]; for (int i=0 ;i<numCourses;i++)edges.add(new ArrayList <>()); for (int [] pre:prerequisites)edges.get(pre[1 ]).add(pre[0 ]); for (int i=0 ;i<numCourses&&valid;i++)if (visited[i]==0 )dfs(i); return valid; } public void dfs (int index) { visited[index] = 1 ; for (int i:edges.get(index)){ if (visited[i]==0 ){ dfs(i); if (!valid)return ; }else if (visited[i]==1 ){ valid = false ; return ; } } visited[index] = 2 ; } }
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 61 class Solution { List<List<Integer>> edges; int [] visited; int [] result; boolean valid = true ; 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; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int scheduleCourse (int [][] courses) { Arrays.sort(courses, (a, b) -> a[1 ] - b[1 ]); PriorityQueue<Integer> q = new PriorityQueue <Integer>((a, b) -> b - a); int total = 0 ; for (int [] course : courses) { int ti = course[0 ], di = course[1 ]; if (total + ti <= di) { total += ti; q.offer(ti); } else if (!q.isEmpty() && q.peek() > ti) { total -= q.poll() - ti; q.offer(ti); } } return q.size(); } }
相关资料 https://leetcode-cn.com/leetbook/detail/graph/ https://www.geeksforgeeks.org/breadth-first-search-or-bfs-for-a-graph/