存储方法
假设m为边的数量,n为图的点数
- 直接存边- 查询某个边:O(m)
- 遍历一个点的所有边: O(m)
- 遍历整个图: O(nm)
- 空间复杂度:O(m)
 
- 领接矩阵- 查询某个边:O(1)
- 遍历一个点的所有边: O(n)
- 遍历整个图: O(n^2)
- 空间复杂度:O(n^2)
- 缺点是空间复杂度过大,不适用于稀疏图
 
- 邻接表 (动态数组或者链表)- 查询某个边(u->v):O(d(u)), 这里d(u)是u的出度- 如果u的出度边已经排序好,可以用二分做到O(log(d(u)))
 
- 遍历一个点的所有边: O(d(u))
- 遍历整个图: O(n+m), (BFS, DFS)
- 空间复杂度:O(m)
 
- 查询某个边(u->v):O(d(u)), 这里d(u)是u的出度
DFS
- 使用Depth First Search的遍历本质上是根树前序遍历的推广。
- 初始点的选择决定了遍历的结果,如果初始点可以通向所有点,随后的遍历结果将是DFS spinning tree;如果初始点有无法到达的点,那么结果将是DFS forest。
- 在使用邻接表的情况下,算法的复杂度是O(n+m),n是点数,m是边数,空间复杂度是O(n).
有无回路
BFS
- Breadth First Search is a graph traversal algorithm which explores the neighbor nodes first, before moving to the next level neighbors
Their time complexity are both O(|V| + |E|), where V is the set of the nodes and E is the set of edges.
Code example, traverse the tree using DFS and BFS (both iterative and recursive).
- DFS (recursive, java)1 
 2
拓扑排序 Topological sort
A topological sort or topological ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering.
Precisely, a topological sort is a graph traversal in which each node v is visited only after all its dependencies are visited. A topological ordering is possible if and only if the graph has no directed cycles, that is, if it is a directed acyclic graph (DAG).
- Implementations: - Kahn: 
- DFS TODO 
 
- Example problem: Leetcode 2101 
 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// Kahn implementation 
 class Solution {
 public int[] findOrder(int numCourses, int[][] prerequisites) {
 Map<Integer, List<Integer>> edgeMap = new HashMap<>();
 Map<Integer, Integer> inEdgeMap = new HashMap<>();
 for(int i=0; i<prerequisites.length; i++) {
 int from = prerequisites[i][1], to = prerequisites[i][0];
 edgeMap.computeIfAbsent(from, l -> new ArrayList<>()).add(to);
 inEdgeMap.put(to, inEdgeMap.getOrDefault(to, 0) + 1);
 }
 List<Integer> res = new ArrayList<>();
 Queue<Integer> q = new LinkedList<>();
 for(int i=0; i<numCourses; i++) {
 if(inEdgeMap.get(i)==null) {
 q.add(i);
 }
 }
 while(!q.isEmpty()) {
 int n = q.poll();
 res.add(n);
 for(int toNode: edgeMap.getOrDefault(n, new ArrayList<>())) {
 inEdgeMap.put(toNode, inEdgeMap.get(toNode) - 1);
 if(inEdgeMap.get(toNode) == 0) {
 q.add(toNode);
 }
 }
 }
 return res.size() == numCourses ?
 res.stream().mapToInt(i->i).toArray(): new int[]{};
 }
 }
| 1 | // DFS solution | 
Minimum Spanning Tree
Prim
- Prim algorithm is based on the greedy idea. Every iteration it chooses an un-visited node with the lowest cost from the storage, and then it adds all of its adjacent un-visited nodes into the storage with their costs. After $n$ iterations, the MST can be achieved.
- Time complexity: - Note $E$ is the number of edges, and $V$ is the number of nodes in the graph.
- Adjacency matrix implementation: $O(V^2)$.
- Binary heap + adjacency list implementation: $O(E*logV)$. - Why it’s $logV$?: see this post.
 
- Fibonacci heap: …
 
- Therefore, in a dense graph, Adjacency matrix implementation is better. Otherwise, heap implementations are better.
- Example problem. LC 11351 
 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
 43class Solution { 
 public int minimumCost(int n, int[][] connections) {
 // init
 int visited[] = new int[n+1];
 Map<Integer, List<int[]>> map = new HashMap<>();
 Queue<int[]> q = new PriorityQueue<int[]>((a,b) -> a[1] - b[1]);
 int cost = 0;
 int numOfVisited = n;
 for(int i=0; i<connections.length; i++) {
 int p1 = connections[i][0], p2 = connections[i][1], v = connections[i][2];
 if(!map.containsKey(p1)) { map.put(p1, new ArrayList<>()); }
 if(!map.containsKey(p2)) { map.put(p2, new ArrayList<>()); }
 List<int[]> l1 = map.get(p1); l1.add(new int[]{p2, v});
 List<int[]> l2 = map.get(p2); l2.add(new int[]{p1, v});
 }
 
 // add initial points
 int initP = connections[0][0];
 visited[initP] = 1;
 numOfVisited--;
 for(int[] e: map.get(initP)) {
 q.add(e);
 }
 
 // Prim
 while(!q.isEmpty()) {
 int[] e = q.poll();
 int toNode = e[0], v = e[1];
 if(visited[toNode]==1) {
 continue;
 }
 visited[toNode]=1;
 numOfVisited--;
 cost+=v;
 for(int[] toNodeE: map.get(toNode)) {
 if(visited[toNodeE[0]]!=1) {
 q.add(toNodeE);
 }
 }
 }
 return numOfVisited==0?cost:-1;
 }
 }
Kruskal
- Kruskal algorithm pick ups the edge with lowest cost that doesn’t include a cycle every iteration, after $n-1$ iterations, the MST can be achieved.
- Time complexity: $O(ElogE) = O(ElogV)$, where $E$ is the number of edges, and $V$ is the number of nodes in the graph.
- Example problem. LC 11351 
 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
 39class Solution { 
 public int minimumCost(int n, int[][] connections) {
 Arrays.sort(connections, (a, b) -> a[2] - b[2]);
 UF uf = new UF(n+1);
 int cost = 0;
 int count = n-1;
 for(int i=0; i<connections.length; i++) {
 int n1 = connections[i][0], n2 = connections[i][1], v = connections[i][2];
 if(uf.union(n1, n2)) {
 cost+=v;
 count--;
 }
 }
 return count==0?cost:-1;
 }
 
 class UF {
 int[] parent;
 UF(int n) {
 parent = new int[n];
 for(int i=0; i<n; i++) {
 parent[i] = i;
 }
 }
 int find(int i) {
 if(parent[i]!=i) {
 parent[i] = find(parent[i]);
 }
 return parent[i];
 }
 boolean union(int i, int j) {
 int pi = find(i);
 int pj = find(j);
 if(pi==pj) return false;
 parent[pi] = pj;
 return true;
 }
 }
 }
Single Source Shortest Path
Dijkstra
- Dijkstra algorithm is also based on the greedy idea. It is used to compute the shortest path from a given start node to all rest reachable nodes in the graph.
- Just like prim algo., every iteration, it picks up an un-visited node with the shortest path, then it evaluates all of the nodes’ unvisited neighbors, update the distance if needed and insert the neighbor nodes into the storage.
- However, Dijkstra algo. can only be used in graph with non-negative edges.
- Time Complexity: just like the analysis of Prim:- Adjacency matrix implementation: $O(V^2)$.
- Binary heap + adjacency list implementation: $O(E*logV)$.
 
- Example: Leetcode 7431 
 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
 43class Solution { 
 public int networkDelayTime(int[][] times, int n, int k) {
 int[] dis = new int[n+1];
 for(int i=1; i<=n; i++) {
 dis[i] = -1;
 }
 dis[k] = 0;
 Set<Integer> set = new HashSet<>();
 Map<Integer, List<int[]>> map = new HashMap<>();
 for(int i=0; i<times.length; i++) {
 int from = times[i][0], to = times[i][1], cost = times[i][2];
 map.computeIfAbsent(from, list -> new ArrayList<>()).add(new int[]{to, cost});
 }
 PriorityQueue<int[]> q = new PriorityQueue<>((a,b) -> a[1]-b[1]);
 for(int[] e: map.getOrDefault(k, new ArrayList<>())) {
 dis[e[0]] = e[1]; // Note here
 q.add(new int[]{e[0], e[1]});
 }
 set.add(k);
 
 while(!q.isEmpty()) {
 int[] e = q.poll();
 int from = e[0];
 if(set.contains(from)) continue; // Note here
 set.add(from);
 for(int[] fromE: map.getOrDefault(from, new ArrayList<>())) {
 if(set.contains(fromE[0])) continue; // Note here
 int cost = dis[from] + fromE[1];
 if(dis[fromE[0]]==-1 || cost<dis[fromE[0]]) {
 dis[fromE[0]] = cost;
 q.add(new int[]{fromE[0], dis[fromE[0]]}); // Note here
 }
 }
 }
 
 if(set.size() < n) return -1;
 int max = -1;
 for(int i=1; i<=n; i++) {
 max = Math.max(max, dis[i]);
 }
 return max;
 }
 }
Bellman-ford
- Bellman-ford algorithm is a SSSP algorithm that can be applied in any graphs. However, if the graph contains a negative loop, the algorithm will fail.
- This algorithm will try $n-1$ iterations, in each iteration, it iterates all the edges and tries to update the distance.
- Time complexity: $O(VE)$
- Example problem: Leetcode 743 - 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- class Solution { 
 public int networkDelayTime(int[][] times, int n, int k) {
 int[] dis = new int[n+1];
 for(int i=1; i<=n; i++) {
 dis[i] = -1;
 }
 dis[k] = 0;
 for(int i=0; i<n; i++) {
 boolean flag = false;
 for(int j=0; j<times.length; j++) {
 int from = times[j][0], to = times[j][1], cost = times[j][2];
 if(dis[from] != -1) {
 if(dis[to]==-1 || cost + dis[from] < dis[to]) {
 dis[to] = cost + dis[from];
 flag = true;
 }
 }
 }
 if(!flag) break;
 }
 int max = -1;
 for(int i=1; i<=n; i++) {
 if(dis[i]==-1) return -1;
 max = Math.max(max, dis[i]);
 }
 return max;
 }
 }
- How to detect a loop with negative value? after the $n-1$ rounds, try to update the distance again, if there is a chance, then the negative loop exists. 
- Shortest Path Faster Algorithm: TODO
Multiple Sources Shortest Path
Floyd
- Floyd algorithm is used to find shortest path from multiple source nodes. It can be used in graph with negative edges.
- Time complexity: $O(V^3)$
- Example code: Leetcode 7431 
 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
 30class Solution { 
 public int networkDelayTime(int[][] times, int n, int k) {
 long[][] dis = new long[n+1][n+1];
 for(int i=1; i<=n; i++) {
 for(int j=1; j<=n; j++)
 dis[i][j] = Integer.MAX_VALUE;
 dis[i][i] = 0;
 }
 
 for(int i=0; i<times.length; i++) {
 int from = times[i][0], to = times[i][1], cost = times[i][2];
 dis[from][to] = cost;
 }
 for(int t=1; t<=n; t++) {
 for(int i=1; i<=n; i++) {
 for(int j=1; j<=n; j++) {
 dis[i][j] = Math.min(dis[i][t] + dis[t][j], dis[i][j]);
 }
 }
 }
 long max = -1;
 for(int i=1; i<=n; i++) {
 if(dis[k][i]==Integer.MAX_VALUE) return -1;
 max = Math.max(max, dis[k][i]);
 }
 return (int)max;
 }
 }