回顾力扣144/94//145/102/589/590/429,熟练掌握递归和非递归写法。
图论不强调非递归。
使用邻接表
1个连通分量
Graph.java
package Chapt02_DFS;
import java.io.File;
import java.io.IOException;
import java.util.TreeSet;
import java.util.Scanner;/// 暂时只支持无向无权图
public class Graph {private int V;private int E;private TreeSet<Integer>[] adj;public Graph(String pathStr){File file = new File(pathStr);try(Scanner scanner = new Scanner(file)){V = scanner.nextInt();if(V < 0) throw new IllegalArgumentException("V must be non-negative");adj = new TreeSet[V];for(int i = 0; i < V; i ++)adj[i] = new TreeSet<Integer>();E = scanner.nextInt();if(E < 0) throw new IllegalArgumentException("E must be non-negative");for(int i = 0; i < E; i ++){int a = scanner.nextInt();validateVertex(a);int b = scanner.nextInt();validateVertex(b);if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");adj[a].add(b);adj[b].add(a);}}catch(IOException e){e.printStackTrace();}}private void validateVertex(int v){if(v < 0 || v >= V)throw new IllegalArgumentException("vertex " + v + "is invalid");}public int V(){return V;}public int E(){return E;}public boolean hasEdge(int v, int w){validateVertex(v);validateVertex(w);return adj[v].contains(w);}public Iterable<Integer> adj(int v){validateVertex(v);return adj[v];}public int degree(int v){validateVertex(v);return adj[v].size();}@Overridepublic String toString(){StringBuilder sb = new StringBuilder();sb.append(String.format("V = %d, E = %d\n", V, E));for(int v = 0; v < V; v ++){sb.append(String.format("%d : ", v));for(int w : adj[v])sb.append(String.format("%d ", w));sb.append('\n');}return sb.toString();}public static void main(String[] args){Graph g = new Graph("g2.txt");System.out.print(g);}
}
GraphDFS.java
package Chapt02_DFS;import java.util.ArrayList;public class GraphDFS {private Graph G;private boolean[] visited;//order存放遍历结果private ArrayList<Integer> order = new ArrayList<>();//在构造函数中对图遍历public GraphDFS(Graph G){this.G = G;/* public int V(){ return V;} 返回顶点的数量*/visited = new boolean[G.V()];dfs(0); //从第0个节点开始遍历}private void dfs(int v){visited[v] = true; //v这个顶点遍历过了order.add(v); //把v放到order数组中//把v的相邻顶点w进行遍历,如果相邻顶点没有被访问过则递归地对w顶点进行dfsfor(int w: G.adj(v))if(!visited[w])dfs(w);}// 返回一个可遍历的对象,具体是数组、链表、哈希表或者红黑树对用户屏蔽// 使用本方法访问遍历的元素public Iterable<Integer> order(){return order;}public static void main(String[] args){Graph g = new Graph("g2.txt");GraphDFS graphDFS = new GraphDFS(g);System.out.println(graphDFS.order());}
}
多个连通分量
只需要对GraphDFS中的构造函数进行改进
GraphDFS.java
package Chapt02_DFS;import java.util.ArrayList;public class GraphDFS {private Graph G;private boolean[] visited;//order存放遍历结果private ArrayList<Integer> order = new ArrayList<>();//在构造函数中对图遍历public GraphDFS(Graph G){this.G = G;/* public int V(){ return V;} 返回顶点的数量*/visited = new boolean[G.V()];for (int v = 0; v < G.V(); v++) {if(!visited[v]) dfs(v);}// dfs(0); //从第0个节点开始遍历}private void dfs(int v){visited[v] = true; //v这个顶点遍历过了order.add(v); //把v放到order数组中//把v的相邻顶点w进行遍历,如果相邻顶点没有被访问过则递归地对w顶点进行dfsfor(int w: G.adj(v))if(!visited[w])dfs(w);}// 返回一个可遍历的对象,具体是数组、链表、哈希表或者红黑树对用户屏蔽// 使用本方法访问遍历的元素public Iterable<Integer> order(){return order;}public static void main(String[] args){Graph g = new Graph("g3.txt");GraphDFS graphDFS = new GraphDFS(g);System.out.println(graphDFS.order());}
}
图的先序遍历和后续遍历
package Chapt02_DFS;import java.util.ArrayList;public class GraphDFS {private Graph G;private boolean[] visited;//order存放遍历结果
// private ArrayList<Integer> order = new ArrayList<>();private ArrayList<Integer> pre = new ArrayList<>(); // 存放先序遍历的结果private ArrayList<Integer> post = new ArrayList<>(); // 存放后续遍历的结果//在构造函数中对图遍历public GraphDFS(Graph G){this.G = G;/* public int V(){ return V;} 返回顶点的数量*/visited = new boolean[G.V()];for (int v = 0; v < G.V(); v++) {if(!visited[v]) dfs(v);}// dfs(0); //从第0个节点开始遍历}private void dfs(int v){visited[v] = true; //v这个顶点遍历过了
// order.add(v); //把v放到order数组中pre.add(v); /*****************///把v的相邻顶点w进行遍历,如果相邻顶点没有被访问过则递归地对w顶点进行dfsfor(int w: G.adj(v))if(!visited[w])dfs(w);post.add(v); /*****************/}// 返回一个可遍历的对象,具体是数组、链表、哈希表或者红黑树对用户屏蔽// 使用本方法访问遍历的元素public Iterable<Integer> pre(){return pre;}public Iterable<Integer> post(){return post;}public static void main(String[] args){Graph g = new Graph("g3.txt");GraphDFS graphDFS = new GraphDFS(g);System.out.println(graphDFS.post());System.out.println(graphDFS.pre());}
}
使用邻接矩阵
逻辑一模一样
AdjMatrix.java
package Chapt02_DFS;
import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;public class AdjMatrix {private int V;private int E;private int[][] adj;public AdjMatrix(String filename){File file = new File(filename);try(Scanner scanner = new Scanner(file)){V = scanner.nextInt();if(V < 0) throw new IllegalArgumentException("V must be non-negative");adj = new int[V][V];E = scanner.nextInt();if(E < 0) throw new IllegalArgumentException("E must be non-negative");for(int i = 0; i < E; i ++){int a = scanner.nextInt();validateVertex(a);int b = scanner.nextInt();validateVertex(b);if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");if(adj[a][b] == 1) throw new IllegalArgumentException("Parallel Edges are Detected!");adj[a][b] = 1;adj[b][a] = 1;}}catch(IOException e){e.printStackTrace();}}private void validateVertex(int v){if(v < 0 || v >= V)throw new IllegalArgumentException("vertex " + v + "is invalid");}public int V(){return V;}public int E(){return E;}public boolean hasEdge(int v, int w){validateVertex(v);validateVertex(w);return adj[v][w] == 1;}public ArrayList<Integer> adj(int v){validateVertex(v);ArrayList<Integer> res = new ArrayList<>();for(int i = 0; i < V; i ++)if(adj[v][i] == 1)res.add(i);return res;}public int degree(int v){return adj(v).size();}@Overridepublic String toString(){StringBuilder sb = new StringBuilder();sb.append(String.format("V = %d, E = %d\n", V, E));for(int i = 0; i < V; i ++){for(int j = 0; j < V; j ++)sb.append(String.format("%d ", adj[i][j]));sb.append('\n');}return sb.toString();}public static void main(String[] args){AdjMatrix adjMatrix = new AdjMatrix("g3.txt");System.out.print(adjMatrix);}
}
AdjMatrixDFS.java
package Chapt02_DFS;
import java.util.ArrayList;public class AdjMatrixDFS {private AdjMatrix G;private boolean[] visited;private ArrayList<Integer> pre = new ArrayList<>();private ArrayList<Integer> post = new ArrayList<>();public AdjMatrixDFS(AdjMatrix G){this.G = G;visited = new boolean[G.V()];for(int v = 0; v < G.V(); v ++)if(!visited[v])dfs(v);}private void dfs(int v){visited[v] = true;pre.add(v);for(int w: G.adj(v))if(!visited[w])dfs(w);post.add(v);}public Iterable<Integer> pre(){return pre;}public Iterable<Integer> post(){return post;}public static void main(String[] args){AdjMatrix g = new AdjMatrix("g3.txt");AdjMatrixDFS graphDFS = new AdjMatrixDFS(g);System.out.println("DFS preOrder : " + graphDFS.pre());System.out.println("DFS postOrder : " + graphDFS.post());}
}
使用接口
Graph.java
public interface Graph {int V();int E();boolean hasEdge(int v, int w);Iterable<Integer> adj(int v);int degree(int v);
}
在不同的图表中重写接口里的方法,其实是把不同图的方法都抽象了出来
邻接表
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Scanner;public class AdjList implements Graph{private int V;private int E;private LinkedList<Integer>[] adj;public AdjList(String pathStr){File file = new File(pathStr);try(Scanner scanner = new Scanner(file)){V = scanner.nextInt();if(V < 0) throw new IllegalArgumentException("V must be non-negative");adj = new LinkedList[V];for(int i = 0; i < V; i ++)adj[i] = new LinkedList<Integer>();E = scanner.nextInt();if(E < 0) throw new IllegalArgumentException("E must be non-negative");for(int i = 0; i < E; i ++){int a = scanner.nextInt();validateVertex(a);int b = scanner.nextInt();validateVertex(b);if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");adj[a].add(b);adj[b].add(a);}}catch(IOException e){e.printStackTrace();}}private void validateVertex(int v){if(v < 0 || v >= V)throw new IllegalArgumentException("vertex " + v + "is invalid");}public int V(){return V;}public int E(){return E;}public boolean hasEdge(int v, int w){validateVertex(v);validateVertex(w);return adj[v].contains(w);}public LinkedList<Integer> adj(int v){validateVertex(v);return adj[v];}public int degree(int v){return adj[v].size();}@Overridepublic String toString(){StringBuilder sb = new StringBuilder();sb.append(String.format("V = %d, E = %d\n", V, E));for(int v = 0; v < V; v ++){sb.append(String.format("%d : ", v));for(int w : adj[v])sb.append(String.format("%d ", w));sb.append('\n');}return sb.toString();}public static void main(String[] args){Graph adjList = new AdjList("g.txt");System.out.print(adjList);}
}
邻接矩阵
import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;public class AdjMatrix implements Graph {private int V;private int E;private int[][] adj;public AdjMatrix(String filename){File file = new File(filename);try(Scanner scanner = new Scanner(file)){V = scanner.nextInt();if(V < 0) throw new IllegalArgumentException("V must be non-negative");adj = new int[V][V];E = scanner.nextInt();if(E < 0) throw new IllegalArgumentException("E must be non-negative");for(int i = 0; i < E; i ++){int a = scanner.nextInt();validateVertex(a);int b = scanner.nextInt();validateVertex(b);if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");if(adj[a][b] == 1) throw new IllegalArgumentException("Parallel Edges are Detected!");adj[a][b] = 1;adj[b][a] = 1;}}catch(IOException e){e.printStackTrace();}}private void validateVertex(int v){if(v < 0 || v >= V)throw new IllegalArgumentException("vertex " + v + "is invalid");}public int V(){return V;}public int E(){return E;}public boolean hasEdge(int v, int w){validateVertex(v);validateVertex(w);return adj[v][w] == 1;}public Iterable<Integer> adj(int v){validateVertex(v);ArrayList<Integer> res = new ArrayList<>();for(int i = 0; i < V; i ++)if(adj[v][i] == 1)res.add(i);return res;}public int degree(int v){validateVertex(v);int res = 0;for(int i = 0; i < V; i ++)res += adj[v][i];return res;}@Overridepublic String toString(){StringBuilder sb = new StringBuilder();sb.append(String.format("V = %d, E = %d\n", V, E));for(int i = 0; i < V; i ++){for(int j = 0; j < V; j ++)sb.append(String.format("%d ", adj[i][j]));sb.append('\n');}return sb.toString();}public static void main(String[] args){Graph adjMatrix = new AdjMatrix("g.txt");System.out.print(adjMatrix);}
}
使用红黑树构造邻接表
import java.io.File;
import java.io.IOException;
import java.util.TreeSet;
import java.util.Scanner;public class AdjSet implements Graph{private int V;private int E;private TreeSet<Integer>[] adj;public AdjSet(String pathStr){File file = new File(pathStr);try(Scanner scanner = new Scanner(file)){V = scanner.nextInt();if(V < 0) throw new IllegalArgumentException("V must be non-negative");adj = new TreeSet[V];for(int i = 0; i < V; i ++)adj[i] = new TreeSet<Integer>();E = scanner.nextInt();if(E < 0) throw new IllegalArgumentException("E must be non-negative");for(int i = 0; i < E; i ++){int a = scanner.nextInt();validateVertex(a);int b = scanner.nextInt();validateVertex(b);if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");adj[a].add(b);adj[b].add(a);}}catch(IOException e){e.printStackTrace();}}private void validateVertex(int v){if(v < 0 || v >= V)throw new IllegalArgumentException("vertex " + v + "is invalid");}public int V(){return V;}public int E(){return E;}public boolean hasEdge(int v, int w){validateVertex(v);validateVertex(w);return adj[v].contains(w);}public Iterable<Integer> adj(int v){// public TreeSet<Integer> adj(int v){validateVertex(v);return adj[v];}public int degree(int v){validateVertex(v);return adj[v].size();}@Overridepublic String toString(){StringBuilder sb = new StringBuilder();sb.append(String.format("V = %d, E = %d\n", V, E));for(int v = 0; v < V; v ++){sb.append(String.format("%d : ", v));for(int w : adj[v])sb.append(String.format("%d ", w));sb.append('\n');}return sb.toString();}public static void main(String[] args){Graph adjSet = new AdjSet("g.txt");System.out.print(adjSet);}
}
GraphDFS.java
package Chapt02_DFS;import java.util.ArrayList;public class GraphDFS {private Graph G;private boolean[] visited;private ArrayList<Integer> pre = new ArrayList<>();private ArrayList<Integer> post = new ArrayList<>();public GraphDFS(Graph G){this.G = G;visited = new boolean[G.V()];for(int v = 0; v < G.V(); v ++)if(!visited[v])dfs(v);}private void dfs(int v){visited[v] = true;pre.add(v);for(int w: G.adj(v))if(!visited[w])dfs(w);post.add(v);}public Iterable<Integer> pre(){return pre;}public Iterable<Integer> post(){return post;}public static void main(String[] args){Graph g1 = new AdjSet("g3.txt");GraphDFS graphDFS1 = new GraphDFS(g1);System.out.println("DFS preOrder : " + graphDFS1.pre());System.out.println("DFS postOrder : " + graphDFS1.post());System.out.println();Graph g2 = new AdjList("g3.txt");GraphDFS graphDFS2 = new GraphDFS(g2);System.out.println("DFS preOrder : " + graphDFS2.pre());System.out.println("DFS postOrder : " + graphDFS2.post());System.out.println();Graph g3 = new AdjMatrix("g3.txt");GraphDFS graphDFS3 = new GraphDFS(g3);System.out.println("DFS preOrder : " + graphDFS3.pre());System.out.println("DFS postOrder : " + graphDFS3.post());System.out.println();}
}
使用非递归的方法遍历图——使用栈
package Chapt02_DFS;import java.util.ArrayList;
import java.util.Stack;public class GraphDFSnr {private Graph G;private boolean[] visited;private ArrayList<Integer> pre = new ArrayList<>();public GraphDFSnr(Graph G){this.G = G;visited = new boolean[G.V()];for(int v = 0; v < G.V(); v ++)if(!visited[v])dfs(v);}private void dfs(int v){Stack<Integer> stack = new Stack<>();stack.push(v);visited[v] = true;while(!stack.empty()){int cur = stack.pop();pre.add(cur);for(int w: G.adj(v))if(!visited[w]){stack.push(w);visited[w] = true;}}}public Iterable<Integer> pre(){return pre;}public static void main(String[] args){Graph g1 = new AdjSet("g3.txt");GraphDFSnr graphDFS1 = new GraphDFSnr(g1);System.out.println("DFS preOrder : " + graphDFS1.pre());System.out.println();Graph g2 = new AdjList("g3.txt");GraphDFSnr graphDFS2 = new GraphDFSnr(g2);System.out.println("DFS preOrder : " + graphDFS2.pre());System.out.println();Graph g3 = new AdjMatrix("g3.txt");GraphDFSnr graphDFS3 = new GraphDFSnr(g3);System.out.println("DFS preOrder : " + graphDFS3.pre());System.out.println();}
}
联通分量的数量
public class CC {private Graph G;private boolean[] visited;private int cccount = 0;public CC(Graph G){this.G = G;visited = new boolean[G.V()];for(int v = 0; v < G.V(); v ++)if(!visited[v]){dfs(v);cccount ++; // 在if循环内}}private void dfs(int v){visited[v] = true;for(int w: G.adj(v))if(!visited[w])dfs(w);}public int count(){return cccount;}public static void main(String[] args){Graph g = new AdjList("g3.txt");CC cc = new CC(g);System.out.println(cc.count());}
}
具体求解无向图的联通分量
list并不是必要的,可以通过设置visited的值来区别不同的联通分量。
package Chapt02_DFS;
import java.util.ArrayList;public class CC {private Graph G;private int[] visited; // boolean 设置为 intprivate int cccount = 0;public CC(Graph G){this.G = G;visited = new int[G.V()];for(int i = 0; i < visited.length; i ++)visited[i] = -1; // 把未遍历的设置为-1for(int v = 0; v < G.V(); v ++)if(visited[v] == -1){dfs(v, cccount); //dfs(int v, int ccid),ccid初始值为ccount==0 cccount ++; //遍历完一组以后,ccount的值+1}}private void dfs(int v, int ccid){visited[v] = ccid;for(int w: G.adj(v))if(visited[w] == -1)dfs(w, ccid);}public int count(){for(int e: visited)System.out.print(e + " ");System.out.println();return cccount;}public static void main(String[] args){Graph g = new AdjList("g3.txt");CC cc = new CC(g);System.out.println(cc.count());}
}
判断两个顶点是否属于同一连通分量 / 这个图有多少联通分量,每个联通分量有多少顶点
把验证代码调整为public
Graph.java
import java.io.File;
import java.io.IOException;
import java.util.TreeSet;
import java.util.Scanner;/// 暂时只支持无向无权图
public class Graph {private int V;private int E;private TreeSet<Integer>[] adj;public Graph(String filename){File file = new File(filename);try(Scanner scanner = new Scanner(file)){V = scanner.nextInt();if(V < 0) throw new IllegalArgumentException("V must be non-negative");adj = new TreeSet[V];for(int i = 0; i < V; i ++)adj[i] = new TreeSet<Integer>();E = scanner.nextInt();if(E < 0) throw new IllegalArgumentException("E must be non-negative");for(int i = 0; i < E; i ++){int a = scanner.nextInt();validateVertex(a);int b = scanner.nextInt();validateVertex(b);if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");adj[a].add(b);adj[b].add(a);}}catch(IOException e){e.printStackTrace();}}public void validateVertex(int v){if(v < 0 || v >= V)throw new IllegalArgumentException("vertex " + v + "is invalid");}public int V(){return V;}public int E(){return E;}public boolean hasEdge(int v, int w){validateVertex(v);validateVertex(w);return adj[v].contains(w);}public Iterable<Integer> adj(int v){validateVertex(v);return adj[v];}public int degree(int v){validateVertex(v);return adj[v].size();}@Overridepublic String toString(){StringBuilder sb = new StringBuilder();sb.append(String.format("V = %d, E = %d\n", V, E));for(int v = 0; v < V; v ++){sb.append(String.format("%d : ", v));for(int w : adj[v])sb.append(String.format("%d ", w));sb.append('\n');}return sb.toString();}public static void main(String[] args){Graph g = new Graph("g.txt");System.out.print(g);}
}
CC.java
package Chapt02_DFS.connected;
import Chapt02_DFS.AdjList;
import Chapt02_DFS.Graph;import java.util.ArrayList;public class CC {private Graph_1 G;private int[] visited;private int cccount = 0;public CC(Graph_1 G){this.G = G;visited = new int[G.V()];for(int i = 0; i < visited.length; i ++)visited[i] = -1;for(int v = 0; v < G.V(); v ++)if(visited[v] == -1){dfs(v, cccount);cccount ++;}}private void dfs(int v, int ccid){visited[v] = ccid;for(int w: G.adj(v))if(visited[w] == -1)dfs(w, ccid);}public int count(){return cccount;}// 判断两个顶点是否属于同一连通分量public boolean isConnected(int v, int w){G.validateVertex(v);G.validateVertex(w);return visited[v] == visited[w];}public ArrayList<Integer>[] components(){ // 每个联通分量设置一个ArrayListArrayList<Integer>[] res = new ArrayList[cccount];for(int i = 0; i < cccount; i ++) // 设置联通分量的ArrayListres[i] = new ArrayList<Integer>();for(int v = 0; v < G.V(); v ++) // 填充每个连通分量的ArrayListres[visited[v]].add(v); // visited[v]是组名,表示是哪个连通分量return res;}public static void main(String[] args){Graph_1 g = new Graph_1("g3.txt");CC cc = new CC(g);System.out.println(cc.count());System.out.println(cc.isConnected(0, 6));System.out.println(cc.isConnected(5, 6));ArrayList<Integer>[] comp = cc.components(); // 把res数组给了comp数组for(int ccid = 0; ccid < comp.length; ccid ++){ // ccid = 0、1System.out.print(ccid + " : ");for(int w: comp[ccid]) //把res[1]/res[2]里的值取出来System.out.print(w + " ");System.out.println();}}
}