回顾力扣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();
}
@Override
public 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顶点进行dfs
for(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顶点进行dfs
for(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顶点进行dfs
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 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();
}
@Override
public 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();
}
@Override
public 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;
}
@Override
public 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();
}
@Override
public 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 设置为 int
private 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; // 把未遍历的设置为-1
for(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();
}
@Override
public 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(){ // 每个联通分量设置一个ArrayList
ArrayList<Integer>[] res = new ArrayList[cccount];
for(int i = 0; i < cccount; i ++) // 设置联通分量的ArrayList
res[i] = new ArrayList<Integer>();
for(int v = 0; v < G.V(); v ++) // 填充每个连通分量的ArrayList
res[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、1
System.out.print(ccid + " : ");
for(int w: comp[ccid]) //把res[1]/res[2]里的值取出来
System.out.print(w + " ");
System.out.println();
}
}
}