当前位置:   article > 正文

Java学习第二十三天 图论(广度优先,深度优先,最小生成树,最短路径)一_java深度优先搜索求多叉树最短路径

java深度优先搜索求多叉树最短路径

图的表示

图的遍历

深度优先:连通性路径,二分图的检测,环的检测,fllodfill

广度优先:无权图的最短路径

使用图论对问题建模

欧拉路径

哈密尔顿路径 状态压缩

割点

有向图算法: DAG 环检测 拓扑排序 强连通分量

最小生成树: Kruskal Prim

最短路径: Dijkstra Floyed Bellman-Ford

网络流: 最大流-最小割  Ford-Fulkerson

图的表示

图的分类

无向图 Undirected Graph

有向图 Directed Graph

无权图

有权图

总的来说分四类

图的基本概念

无向无权图

邻接矩阵

01表示在0-1之间存在一条边,以此类推

  1. package com.graph.adjacencyMatrix;
  2. import java.io.*;//为了使用File类和异常抛出
  3. import java.util.ArrayList;
  4. import java.util.Scanner;//为了读取File里面的内容
  5. public class AdjMatrix {
  6. private int V;//顶点
  7. private int E;//边
  8. private int[][] adj;//邻接矩阵
  9. public AdjMatrix(String filename){
  10. //用绝对路径可以避免一些相对路径时的bug,这里他说的把“。。。”这里替换成filename就是相对路径但是会报错
  11. File file = new File("C:/Users/81909/Desktop/JavaSE/基础语法/g.txt");
  12. //捕获一个异常
  13. try(Scanner scanner = new Scanner(file)){//scanner存了file里面的内容
  14. V = scanner.nextInt();//根据Scanner声明时括号里的内容来决定从哪里读取一个整型数字,先读V
  15. if(V<0) throw new IllegalArgumentException("V需要是正数");
  16. adj = new int[V][V];//创建一个V*V的二维数组
  17. E = scanner.nextInt();//第二个读的是E
  18. if(E<0) throw new IllegalArgumentException("E需要是正数");
  19. for(int i = 0; i < E; i ++){//往矩阵里填1
  20. int a = scanner.nextInt();
  21. validateVertex(a);//判断顶点合法性
  22. int b = scanner.nextInt();
  23. validateVertex(b);
  24. if(a==b) throw new IllegalArgumentException("self loop is detected");//判断是否自环边
  25. if(adj[a][b]==1) throw new IllegalArgumentException("paralle edges are detected");//判断是否平行边
  26. adj[a][b] = 1;
  27. adj[b][a] = 1;
  28. }
  29. }
  30. catch(IOException e){
  31. e.printStackTrace();//栈中信息打印一下
  32. }
  33. }
  34. private void validateVertex(int v){
  35. if(v<0||v>=V) throw new IllegalArgumentException("vertex"+v+"is invalid");
  36. }
  37. public int V(){return V;};//之所以不直接把它俩定义为public,是为了不让用户修改变量
  38. public int E(){return E;};
  39. //判断两点直接是否有边
  40. public boolean hasEdge(int v,int w){
  41. validateVertex(v);
  42. validateVertex(w);
  43. return adj[v][w]==1;
  44. }
  45. //返回和顶点v相邻的边
  46. public ArrayList<Integer> adj(int v){
  47. validateVertex(v);
  48. ArrayList<Integer>res=new ArrayList<>();//将和v相邻的所有顶点存到res里然后反悔哦
  49. for (int i = 0; i < V; i++) {
  50. if(adj[v][i]==1)
  51. res.add(i);
  52. }
  53. return res;
  54. }
  55. //返回顶点相对的度
  56. public int degree(int v){
  57. return adj(v).size();
  58. }
  59. @Override
  60. public String toString(){
  61. StringBuilder sb = new StringBuilder();
  62. sb.append(String.format("V = %d, E = %d\n", V, E));//先说有多少顶点,有多少边
  63. for(int i = 0; i < V; i ++){
  64. for(int j = 0; j < V; j ++)
  65. sb.append(String.format("%d ", adj[i][j]));//把行列式给填充进去
  66. sb.append('\n');
  67. }
  68. return sb.toString();
  69. }
  70. public static void main(String[] args){
  71. AdjMatrix adjMatrix = new AdjMatrix("g.txt");
  72. System.out.print(adjMatrix);
  73. }
  74. }

结果

  1. V = 7, E = 9
  2. 0 1 0 1 0 0 0
  3. 1 0 1 0 0 0 1
  4. 0 1 0 1 0 1 0
  5. 1 0 1 0 1 0 0
  6. 0 0 0 1 0 1 0
  7. 0 0 1 0 1 0 1
  8. 0 1 0 0 0 1 0
  9. Process finished with exit code 0

图的基本表示:邻接矩阵

这里树形状的空间为点的个数加边的个数就足够存下所有信息了,degree(v)也可以小于O(v)

稀疏图与稠密图

图的基本表示:邻接表

  1. package com.graph.adjacencyMatrix;
  2. import java.util.LinkedList;//邻接表是链表
  3. import java.io.*;
  4. import java.util.Scanner;
  5. public class AdjList {
  6. private int V;
  7. private int E;
  8. private LinkedList<Integer>[] adj;
  9. public AdjList(String filename){
  10. File file = new File("C:/Users/81909/Desktop/JavaSE/基础语法/g.txt");
  11. try(Scanner scanner = new Scanner(file)){
  12. V = scanner.nextInt();
  13. if(V<0) throw new IllegalArgumentException("V需要是正数");
  14. adj = new LinkedList[V];//创建一个空间为V的链表
  15. for (int i = 0; i < V; i++) {
  16. adj[i]=new LinkedList<Integer>();//为每一个元素申请空间,不写Integer这个泛型也可以
  17. }
  18. E = scanner.nextInt();
  19. if(E<0) throw new IllegalArgumentException("E需要是正数");
  20. for(int i = 0; i < E; i ++){
  21. int a = scanner.nextInt();
  22. validateVertex(a);
  23. int b = scanner.nextInt();
  24. validateVertex(b);
  25. if(a==b) throw new IllegalArgumentException("self loop is detected");
  26. if(adj[a].contains(b)) throw new IllegalArgumentException("paralle edges are detected");
  27. adj[a].add(b);
  28. adj[b].add(a);
  29. }
  30. }
  31. catch(IOException e){
  32. e.printStackTrace();
  33. }
  34. }
  35. private void validateVertex(int v){
  36. if(v<0||v>=V) throw new IllegalArgumentException("vertex"+v+"is invalid");
  37. }
  38. public int V(){return V;};
  39. public int E(){return E;};
  40. //判断两点直接是否有边
  41. public boolean hasEdge(int v,int w){
  42. validateVertex(v);
  43. validateVertex(w);
  44. return adj[v].contains(w);
  45. }
  46. //返回和顶点v相邻的边
  47. public LinkedList<Integer> adj(int v){
  48. validateVertex(v);
  49. return adj[v];
  50. }
  51. //返回顶点相对的度
  52. public int degree(int v){
  53. return adj(v).size();
  54. }
  55. @Override
  56. public String toString(){
  57. StringBuilder sb = new StringBuilder();
  58. sb.append(String.format("V = %d, E = %d\n", V, E));//先说有多少顶点,有多少边
  59. for(int v = 0; v < V; v ++){
  60. sb.append(String.format("%d:",v));//每一轮v相邻的顶点都有谁
  61. for(int w:adj[v])
  62. sb.append(String.format("%d ", w));//把行列式给填充进去
  63. sb.append('\n');
  64. }
  65. return sb.toString();
  66. }
  67. public static void main(String[] args){
  68. AdjList adjList = new AdjList("g.txt");
  69. System.out.print(adjList);
  70. }
  71. }

结果

  1. V = 7, E = 9
  2. 0:1 3
  3. 1:0 2 6
  4. 2:1 3 5
  5. 3:0 2 4
  6. 4:3 5
  7. 5:2 4 6
  8. 6:1 5
  9. Process finished with exit code 0

邻接表的问题和改进

有的只有顶点没有边,用O(E)代替不可取

  1. package com.graph.adjacencyMatrix;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.util.TreeSet;
  5. import java.util.Scanner;
  6. public class AdjSet {
  7. private int V;
  8. private int E;
  9. private TreeSet<Integer>[] adj;
  10. public AdjSet(String pathStr){
  11. File file = new File(pathStr);
  12. try(Scanner scanner = new Scanner(file)){
  13. V = scanner.nextInt();
  14. if(V < 0) throw new IllegalArgumentException("V must be non-negative");
  15. adj = new TreeSet[V];
  16. for(int i = 0; i < V; i ++)
  17. adj[i] = new TreeSet<Integer>();
  18. E = scanner.nextInt();
  19. if(E < 0) throw new IllegalArgumentException("E must be non-negative");
  20. for(int i = 0; i < E; i ++){
  21. int a = scanner.nextInt();
  22. validateVertex(a);
  23. int b = scanner.nextInt();
  24. validateVertex(b);
  25. if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");
  26. if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");
  27. adj[a].add(b);
  28. adj[b].add(a);
  29. }
  30. }
  31. catch(IOException e){
  32. e.printStackTrace();
  33. }
  34. }
  35. private void validateVertex(int v){
  36. if(v < 0 || v >= V)
  37. throw new IllegalArgumentException("vertex " + v + "is invalid");
  38. }
  39. public int V(){
  40. return V;
  41. }
  42. public int E(){
  43. return E;
  44. }
  45. public boolean hasEdge(int v, int w){
  46. validateVertex(v);
  47. validateVertex(w);
  48. return adj[v].contains(w);
  49. }
  50. public Iterable<Integer> adj(int v){
  51. // public TreeSet<Integer> adj(int v){
  52. validateVertex(v);
  53. return adj[v];
  54. }
  55. public int degree(int v){
  56. validateVertex(v);
  57. return adj[v].size();
  58. }
  59. @Override
  60. public String toString(){
  61. StringBuilder sb = new StringBuilder();
  62. sb.append(String.format("V = %d, E = %d\n", V, E));
  63. for(int v = 0; v < V; v ++){
  64. sb.append(String.format("%d : ", v));
  65. for(int w : adj[v])
  66. sb.append(String.format("%d ", w));
  67. sb.append('\n');
  68. }
  69. return sb.toString();
  70. }
  71. public static void main(String[] args){
  72. AdjSet adjSet = new AdjSet("g.txt");
  73. System.out.print(adjSet);
  74. }
  75. }

图的基本表示比较

遍历的意义

很多算法的本质都是遍历,对图论问题来说,绝大部分问题都依托于遍历

图的深度优先遍历

树与图的优先遍历比较

实现图的深度优先遍历

  1. package com.graph.GraphDFS;
  2. import java.util.ArrayList;
  3. public class GraphDFS {
  4. private Graph G;
  5. private boolean[] visited;
  6. private ArrayList<Integer> order = new ArrayList<>();
  7. public GraphDFS(Graph G){//构造函数
  8. this.G = G;
  9. visited = new boolean[G.V()];//图中每一个顶点对应一个visited记录值
  10. dfs(0);
  11. }
  12. //递归遍历
  13. private void dfs(int v){
  14. visited[v] = true;
  15. order.add(v);
  16. for(int w: G.adj(v))
  17. if(!visited[w])
  18. dfs(w);
  19. }
  20. public Iterable<Integer> order(){
  21. return order;
  22. }
  23. public static void main(String[] args){
  24. Graph g = new Graph("C:/Users/81909/Desktop/JavaSE/基础语法/g.txt");
  25. GraphDFS graphDFS = new GraphDFS(g);
  26. System.out.println(graphDFS.order());
  27. }
  28. }

结果

[0, 1, 3, 2, 6, 5, 4]

  1. package com.graph.GraphDFS;
  2. import java.util.ArrayList;
  3. public class GraphDFS {
  4. private Graph G;
  5. private boolean[] visited;
  6. private ArrayList<Integer> order = new ArrayList<>();
  7. public GraphDFS(Graph G){//构造函数
  8. this.G = G;
  9. visited = new boolean[G.V()];//图中每一个顶点对应一个visited记录值
  10. for (int v = 0; v < G.V(); v++) {
  11. if(!visited[v])
  12. dfs(v);
  13. }
  14. }
  15. //递归遍历
  16. private void dfs(int v){
  17. visited[v] = true;
  18. order.add(v);
  19. for(int w: G.adj(v))
  20. if(!visited[w])
  21. dfs(w);
  22. }
  23. public Iterable<Integer> order(){
  24. return order;
  25. }
  26. public static void main(String[] args){
  27. Graph g = new Graph("C:/Users/81909/Desktop/JavaSE/基础语法/g.txt");
  28. GraphDFS graphDFS = new GraphDFS(g);
  29. System.out.println(graphDFS.order());
  30. }
  31. }

对象与结果

  1. 7 6
  2. 0 1
  3. 0 2
  4. 1 3
  5. 1 4
  6. 2 3
  7. 2 6
  8. [0, 1, 3, 2, 6, 4, 5]

二叉树的遍历

图的遍历

  1. package com.graph.GraphDFS;
  2. import java.util.ArrayList;
  3. public class GraphDFS {
  4. private Graph G;
  5. private boolean[] visited;
  6. private ArrayList<Integer> pre = new ArrayList<>();
  7. private ArrayList<Integer> post = new ArrayList<>();
  8. public GraphDFS(Graph G){//构造函数
  9. this.G = G;
  10. visited = new boolean[G.V()];//图中每一个顶点对应一个visited记录值
  11. for (int v = 0; v < G.V(); v++) {
  12. if(!visited[v])
  13. dfs(v);
  14. }
  15. }
  16. //递归遍历
  17. private void dfs(int v){
  18. visited[v] = true;
  19. pre.add(v);
  20. for(int w: G.adj(v))
  21. if(!visited[w])
  22. dfs(w);
  23. post.add(v);
  24. }
  25. public Iterable<Integer> pre(){
  26. return pre;
  27. }
  28. public Iterable<Integer> post(){
  29. return post;
  30. }
  31. public static void main(String[] args){
  32. Graph g = new Graph("C:/Users/81909/Desktop/JavaSE/基础语法/g.txt");
  33. GraphDFS graphDFS = new GraphDFS(g);
  34. System.out.println(graphDFS.pre());
  35. System.out.println(graphDFS.post());
  36. }
  37. }

结果

  1. [0, 1, 3, 2, 6, 4, 5]
  2. [6, 2, 3, 4, 1, 0, 5]

深度优先遍历复杂度:O(V+E)

深度优先遍历的应用

无向图的联通分量Connected Component

无向图的联通分量个数

具体求解无向图的联通分量

更进一步

  1. import java.util.ArrayList;
  2. public class CC {
  3. private Graph G;
  4. private int[] visited;
  5. private int cccount = 0;
  6. public CC(Graph G){
  7. this.G = G;
  8. visited = new int[G.V()];
  9. for(int i = 0; i < visited.length; i ++)
  10. visited[i] = -1;
  11. for(int v = 0; v < G.V(); v ++)
  12. if(visited[v] == -1){
  13. dfs(v, cccount);
  14. cccount ++;
  15. }
  16. }
  17. private void dfs(int v, int ccid){
  18. visited[v] = ccid;
  19. for(int w: G.adj(v))
  20. if(visited[w] == -1)
  21. dfs(w, ccid);
  22. }
  23. public int count(){
  24. // for(int e: visited)
  25. // System.out.print(e + " ");
  26. // System.out.println();
  27. return cccount;
  28. }
  29. //判断俩顶点是否在一个联通分量里面
  30. public boolean isConnected(int v, int w){
  31. G.validateVertex(v);
  32. G.validateVertex(w);
  33. return visited[v] == visited[w];
  34. }
  35. //返回每张图有多少联通分量,每个联通分量对应的顶点分别是谁
  36. public ArrayList<Integer>[] components(){
  37. ArrayList<Integer>[] res = new ArrayList[cccount];
  38. for(int i = 0; i < cccount; i ++)
  39. res[i] = new ArrayList<Integer>();
  40. for(int v = 0; v < G.V(); v ++)
  41. res[visited[v]].add(v);
  42. return res;
  43. }
  44. public static void main(String[] args){
  45. Graph g = new Graph("g.txt");
  46. CC cc = new CC(g);
  47. System.out.println(cc.count());
  48. System.out.println(cc.isConnected(0, 6));
  49. System.out.println(cc.isConnected(5, 6));
  50. ArrayList<Integer>[] comp = cc.components();
  51. for(int ccid = 0; ccid < comp.length; ccid ++){
  52. System.out.print(ccid + " : ");
  53. for(int w: comp[ccid])
  54. System.out.print(w + " ");
  55. System.out.println();
  56. }
  57. }
  58. }

路径问题

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. public class SingleSourcePath {
  4. private Graph G;
  5. private int s;
  6. private boolean[] visited;
  7. private int[] pre;
  8. public SingleSourcePath(Graph G, int s){
  9. G.validateVertex(s);
  10. this.G = G;
  11. this.s = s;
  12. visited = new boolean[G.V()];
  13. pre = new int[G.V()];
  14. dfs(s, s);
  15. }
  16. private void dfs(int v, int parent){
  17. visited[v] = true;
  18. pre[v] = parent;
  19. for(int w: G.adj(v))
  20. if(!visited[w])
  21. dfs(w, v);
  22. }
  23. //判断是不是和源在一起
  24. public boolean isConnectedTo(int t){
  25. G.validateVertex(t);
  26. return visited[t];
  27. }
  28. //输出路径
  29. public Iterable<Integer> path(int t){
  30. ArrayList<Integer> res = new ArrayList<Integer>();
  31. if(!isConnectedTo(t)) return res;//不能到达路径就返回空路径
  32. int cur = t;
  33. while(cur != s){
  34. res.add(cur);
  35. cur = pre[cur];
  36. }
  37. res.add(s);
  38. Collections.reverse(res);//颠倒过来,返回正序
  39. return res;
  40. }
  41. public static void main(String[] args){
  42. Graph g = new Graph("g.txt");
  43. SingleSourcePath sspath = new SingleSourcePath(g, 0);
  44. System.out.println("0 -> 6 : " + sspath.path(6));
  45. System.out.println("0 -> 5 : " + sspath.path(5));
  46. }
  47. }

点对点路径问题

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. public class Path {
  4. private Graph G;
  5. private int s, t;
  6. private int[] pre;
  7. private boolean[] visited;
  8. public Path(Graph G, int s, int t){//源点与终止点
  9. G.validateVertex(s);
  10. G.validateVertex(t);
  11. this.G = G;
  12. this.s = s;
  13. this.t = t;
  14. visited = new boolean[G.V()];
  15. pre = new int[G.V()];
  16. for(int i = 0; i < pre.length; i ++)
  17. pre[i] = -1;
  18. dfs(s, s);
  19. for(boolean e: visited)
  20. System.out.print(e + " ");
  21. System.out.println();
  22. }
  23. private boolean dfs(int v, int parent){
  24. visited[v] = true;
  25. pre[v] = parent;
  26. if(v == t) return true;
  27. for(int w: G.adj(v))
  28. if(!visited[w])
  29. if(dfs(w, v))
  30. return true;
  31. return false;
  32. }
  33. public boolean isConnected(){
  34. return visited[t];
  35. }
  36. public Iterable<Integer> path(){
  37. ArrayList<Integer> res = new ArrayList<Integer>();
  38. if(!isConnected()) return res;
  39. int cur = t;
  40. while(cur != s){
  41. res.add(cur);
  42. cur = pre[cur];
  43. }
  44. res.add(s);
  45. Collections.reverse(res);
  46. return res;
  47. }
  48. public static void main(String[] args){
  49. Graph g = new Graph("g.txt");
  50. Path path = new Path(g, 0, 6);
  51. System.out.println("0 -> 6 : " + path.path());
  52. Path path2 = new Path(g, 0, 5);
  53. System.out.println("0 -> 5 : " + path2.path());
  54. Path path3 = new Path(g, 0, 1);
  55. System.out.println("0 -> 1 : " + path3.path());
  56. }
  57. }

无向图中的环检测问题

  1. public class CycleDetection {
  2. private Graph G;
  3. private boolean[] visited;
  4. private boolean hasCycle = false;
  5. public CycleDetection(Graph G){
  6. this.G = G;
  7. visited = new boolean[G.V()];
  8. for(int v = 0; v < G.V(); v ++)
  9. if(!visited[v])
  10. if(dfs(v, v)){
  11. hasCycle = true;
  12. break;
  13. }
  14. }
  15. // 从顶点 v 开始,判断图中是否有环
  16. private boolean dfs(int v, int parent){
  17. visited[v] = true;
  18. for(int w: G.adj(v))
  19. if(!visited[w]){
  20. if(dfs(w, v)) return true;
  21. }
  22. else if(w != parent)
  23. return true;
  24. return false;
  25. }
  26. public boolean hasCycle(){
  27. return hasCycle;
  28. }
  29. public static void main(String[] args){
  30. Graph g = new Graph("g.txt");
  31. CycleDetection cycleDetection = new CycleDetection(g);
  32. System.out.println(cycleDetection.hasCycle());
  33. Graph g2 = new Graph("g2.txt");
  34. CycleDetection cycleDetection2 = new CycleDetection(g2);
  35. System.out.println(cycleDetection2.hasCycle());
  36. }
  37. }

二分图检测

染色处理

  1. import java.util.ArrayList;
  2. public class BipartitionDetection {
  3. private Graph G;
  4. private boolean[] visited;
  5. private int[] colors;
  6. private boolean isBipartite = true;
  7. public BipartitionDetection(Graph G){
  8. this.G = G;
  9. visited = new boolean[G.V()];
  10. colors = new int[G.V()];
  11. for(int i = 0; i < G.V(); i ++)
  12. colors[i] = -1;
  13. for(int v = 0; v < G.V(); v ++)
  14. if(!visited[v])
  15. if(!dfs(v, 0)){
  16. isBipartite = false;
  17. break;
  18. }
  19. }
  20. private boolean dfs(int v, int color){
  21. visited[v] = true;
  22. colors[v] = color;
  23. for(int w: G.adj(v))
  24. if(!visited[w]){
  25. if(!dfs(w, 1 - color)) return false;
  26. }
  27. else if(colors[w] == colors[v])
  28. return false;
  29. return true;
  30. }
  31. public boolean isBipartite(){
  32. return isBipartite;
  33. }
  34. public static void main(String[] args){
  35. Graph g = new Graph("g.txt");
  36. BipartitionDetection bipartitionDetection = new BipartitionDetection(g);
  37. System.out.println(bipartitionDetection.isBipartite);
  38. // true
  39. Graph g2 = new Graph("g2.txt");
  40. BipartitionDetection bipartitionDetection2 = new BipartitionDetection(g2);
  41. System.out.println(bipartitionDetection2.isBipartite);
  42. // false
  43. Graph g3 = new Graph("g3.txt");
  44. BipartitionDetection bipartitionDetection3 = new BipartitionDetection(g3);
  45. System.out.println(bipartitionDetection3.isBipartite);
  46. // true
  47. }
  48. }

小节

图的广度优先遍历

从树的广度优先遍历到图的广度优先遍历

广度优先:对一个节点,先把所有子节点都遍历了再操作

从边遍历,只能遍历同一联通分量

需要一层外循环调用各个联通分量

  1. import java.util.ArrayList;
  2. import java.util.LinkedList;
  3. import java.util.Queue;
  4. public class GraphBFS {
  5. private Graph G;
  6. private boolean[] visited;
  7. private ArrayList<Integer> order = new ArrayList<>();//广度优先没有前序后序,这里order代表遍历顺序
  8. public GraphBFS(Graph G){
  9. this.G = G;
  10. visited = new boolean[G.V()];//为visited开空间,有多少顶点就开多少个
  11. for(int v = 0; v < G.V(); v ++)//如果从0遍历就只能遍历0所在联通分量,所以需要从所有节点开始
  12. if(!visited[v])
  13. bfs(v);
  14. }
  15. private void bfs(int s){
  16. Queue<Integer> queue = new LinkedList<>();//Queue是一个接口,所以要具体一个实践的类,这里用链表类
  17. queue.add(s);
  18. visited[s] = true;//入队了就visited=true
  19. while(!queue.isEmpty()){//链表队列不为空时
  20. int v = queue.remove();//队首取出元素v
  21. order.add(v);//添加到order这个队列中
  22. for(int w: G.adj(v))//查看v顶点所有相邻顶点w
  23. if(!visited[w]){
  24. queue.add(w);//如果w没被遍历过,入队
  25. visited[w] = true;//设为已访问
  26. }
  27. }
  28. }
  29. public Iterable<Integer> order(){
  30. return order;
  31. }
  32. public static void main(String[] args){
  33. Graph g = new Graph("g.txt");
  34. GraphBFS graphBFS = new GraphBFS(g);
  35. System.out.println("BFS Order : " + graphBFS.order());
  36. }
  37. }

复杂度:O(V+E)

使用BFS求解单源路径问题

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.LinkedList;
  4. import java.util.Queue;
  5. public class SingleSourcePath {
  6. private Graph G;
  7. private int s;
  8. private boolean[] visited;
  9. private int[] pre;
  10. //没有顺序问题,所以去除了BFS中的ArrayList<>order
  11. public SingleSourcePath(Graph G, int s){//创建对象时考虑源是谁
  12. this.G = G;
  13. this.s = s;
  14. visited = new boolean[G.V()];
  15. pre = new int[G.V()];//路径问题,记录pre这个信息,给pre开空间
  16. for(int i = 0; i < pre.length; i ++)//再赋初值
  17. pre[i] = -1;
  18. bfs(s);//单源问题,只需要从s出发就行
  19. }
  20. private void bfs(int s){
  21. Queue<Integer> queue = new LinkedList<>();
  22. queue.add(s);
  23. visited[s] = true;
  24. pre[s] = s;
  25. while(!queue.isEmpty()){
  26. int v = queue.remove();
  27. for(int w: G.adj(v))//求v的相邻节点w
  28. if(!visited[w]){
  29. queue.add(w);
  30. visited[w] = true;
  31. pre[w] = v;//w上一个节点是v
  32. }
  33. }
  34. }
  35. public boolean isConnectedTo(int t){//判断是不是连接到了t节点
  36. G.validateVertex(t);
  37. return visited[t];
  38. }
  39. public Iterable<Integer> path(int t){//求解路径
  40. ArrayList<Integer> res = new ArrayList<Integer>();//定义res用来之后存路径
  41. if(!isConnectedTo(t)) return res;//到达不了t就返回空
  42. int cur = t;//从终值t出发,记为cur
  43. while(cur != s){//只要还没回到起点s
  44. res.add(cur);//往存路径的res里add一个cur
  45. cur = pre[cur];//往前面一个点跑
  46. }
  47. res.add(s);//最后添加源点s
  48. Collections.reverse(res);//反转一下
  49. return res;
  50. }
  51. public static void main(String[] args){
  52. Graph g = new Graph("g.txt");
  53. SingleSourcePath sspath = new SingleSourcePath(g, 0);
  54. System.out.println("0 -> 6 : " + sspath.path(6));
  55. }
  56. }

其他BFS应用

BFS的重要性

树的广度优先遍历(层序遍历

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.LinkedList;
  4. import java.util.Queue;
  5. // Unweighted Single Source Shortest Path
  6. public class USSSPath {
  7. private Graph G;
  8. private int s;
  9. private boolean[] visited;
  10. private int[] pre;
  11. private int[] dis;
  12. public USSSPath(Graph G, int s){
  13. this.G = G;
  14. this.s = s;
  15. visited = new boolean[G.V()];
  16. pre = new int[G.V()];
  17. dis = new int[G.V()];//给dis看空间
  18. for(int i = 0; i < G.V(); i ++) {//给他俩所有值初始化
  19. pre[i] = -1;
  20. dis[i] = -1;
  21. }
  22. bfs(s);
  23. for(int i = 0; i < G.V(); i ++)//遍历所有点,打印所有点的dis值,不联通时值为-1
  24. System.out.print(dis[i] + " ");
  25. System.out.println();
  26. }
  27. private void bfs(int s){
  28. Queue<Integer> queue = new LinkedList<>();
  29. queue.add(s);
  30. visited[s] = true;
  31. pre[s] = s;
  32. dis[s] = 0;//源点s到s的距离初始化为0
  33. while(!queue.isEmpty()){
  34. int v = queue.remove();
  35. for(int w: G.adj(v))
  36. if(!visited[w]){
  37. queue.add(w);
  38. visited[w] = true;
  39. pre[w] = v;
  40. dis[w] = dis[v] + 1;//距离为源点到v的距离+1
  41. }
  42. }
  43. }
  44. public boolean isConnectedTo(int t){
  45. G.validateVertex(t);
  46. return visited[t];
  47. }
  48. public int dis(int t){//源点到目标t的最短路径长度
  49. G.validateVertex(t);
  50. return dis[t];
  51. }
  52. public Iterable<Integer> path(int t){//源点到目标t的最短路径
  53. ArrayList<Integer> res = new ArrayList<Integer>();
  54. if(!isConnectedTo(t)) return res;
  55. int cur = t;
  56. while(cur != s){
  57. res.add(cur);
  58. cur = pre[cur];
  59. }
  60. res.add(s);
  61. Collections.reverse(res);
  62. return res;
  63. }
  64. public static void main(String[] args){
  65. Graph g = new Graph("g.txt");
  66. USSSPath ussspath = new USSSPath(g, 0);
  67. System.out.println("0 -> 6 : " + ussspath.path(6));
  68. System.out.println("0 -> 6 : " + ussspath.dis(6));
  69. }
  70. }

带权图和最小生成树

带权图

输入

  1. 7 12
  2. 0 1 2
  3. 0 3 7
  4. 0 5 2
  5. 1 2 1
  6. 1 3 4
  7. 1 4 3
  8. 1 5 5
  9. 2 4 4
  10. 2 5 4
  11. 3 4 1
  12. 3 6 5
  13. 4 6 7

代码

  1. import java.io.File;
  2. import java.io.IOException;
  3. import java.util.Map;
  4. import java.util.TreeMap;
  5. import java.util.Scanner;
  6. /// 暂时只支持无向带权图
  7. public class WeightedGraph implements Cloneable{
  8. private int V;
  9. private int E;
  10. private TreeMap<Integer, Integer>[] adj;//TreeMap比TreeSet多存一个值,可以当权,<顶点,权值类型>
  11. public WeightedGraph(String filename){
  12. File file = new File(filename);
  13. try(Scanner scanner = new Scanner(file)){
  14. V = scanner.nextInt();//读取顶点数
  15. if(V < 0) throw new IllegalArgumentException("V must be non-negative");
  16. adj = new TreeMap[V];//定义一个数组
  17. for(int i = 0; i < V; i ++)//为每个TreeMap开空间
  18. adj[i] = new TreeMap<Integer, Integer>();
  19. E = scanner.nextInt();
  20. if(E < 0) throw new IllegalArgumentException("E must be non-negative");
  21. for(int i = 0; i < E; i ++){
  22. int a = scanner.nextInt();
  23. validateVertex(a);
  24. int b = scanner.nextInt();
  25. validateVertex(b);
  26. int weight = scanner.nextInt();//读取权值
  27. if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");
  28. if(adj[a].containsKey(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");
  29. //adj的TreeMap里不包含以b元素为键的数据对,带权图处理平行边挺常见的,如果两个点直接有多条边,保留最短边
  30. adj[a].put(b, weight);//从a-b之间有一条边,权值为weight
  31. adj[b].put(a, weight);
  32. }
  33. }
  34. catch(IOException e){
  35. e.printStackTrace();
  36. }
  37. }
  38. public void validateVertex(int v){
  39. if(v < 0 || v >= V)
  40. throw new IllegalArgumentException("vertex " + v + "is invalid");
  41. }
  42. public int V(){
  43. return V;
  44. }
  45. public int E(){
  46. return E;
  47. }
  48. public boolean hasEdge(int v, int w){
  49. validateVertex(v);
  50. validateVertex(w);
  51. return adj[v].containsKey(w);
  52. }
  53. public Iterable<Integer> adj(int v){
  54. validateVertex(v);
  55. //不能直接返回TreeMap类的adj[v],TreeMap中封装了一个keySet方法,可以返回所有的键对应的集合
  56. return adj[v].keySet();
  57. }
  58. public int getWeight(int v, int w){//获得v-w边所对应的权值
  59. if(hasEdge(v, w)) return adj[v].get(w);//有边抛权,无边抛异常
  60. throw new IllegalArgumentException(String.format("No edge %d-%d", v, w));
  61. }
  62. public int degree(int v){
  63. validateVertex(v);
  64. return adj[v].size();//返回v所对顶点对应的度
  65. }
  66. //带权图基本不会有删边操作,把这个和clone移除基本不会影响
  67. public void removeEdge(int v, int w){
  68. validateVertex(v);
  69. validateVertex(w);
  70. if(adj[v].containsKey(w)) E --;
  71. adj[v].remove(w);
  72. adj[w].remove(v);
  73. }
  74. @Override
  75. public Object clone(){
  76. try{
  77. WeightedGraph cloned = (WeightedGraph) super.clone();
  78. cloned.adj = new TreeMap[V];
  79. for(int v = 0; v < V; v ++){
  80. cloned.adj[v] = new TreeMap<Integer, Integer>();//对每一个adj[v]开空间
  81. //遍历TreeMap,遍历它所有的键;Map接口下封装的Entry类,从adj[v]这个TreeMap中,
  82. // entrySet里面存的就是一个个键值数据对,把键和对应值放入entry中
  83. for(Map.Entry<Integer, Integer> entry: adj[v].entrySet())
  84. //adj[v]中put(键,值)
  85. cloned.adj[v].put(entry.getKey(), entry.getValue());
  86. }
  87. return cloned;
  88. }
  89. catch (CloneNotSupportedException e){
  90. e.printStackTrace();
  91. }
  92. return null;
  93. }
  94. @Override
  95. public String toString(){
  96. StringBuilder sb = new StringBuilder();
  97. sb.append(String.format("V = %d, E = %d\n", V, E));
  98. for(int v = 0; v < V; v ++){
  99. sb.append(String.format("%d : ", v));
  100. for(Map.Entry<Integer, Integer> entry: adj[v].entrySet())//从adj[v].entrySet()中取出键值数据对
  101. sb.append(String.format("(%d: %d) ", entry.getKey(), entry.getValue()));
  102. sb.append('\n');
  103. }
  104. return sb.toString();
  105. }
  106. public static void main(String[] args){
  107. WeightedGraph g = new WeightedGraph("g.txt");
  108. System.out.print(g);
  109. }
  110. }

运行结果

最小生成树

应用

从最小权值开始选边

用了4的这条边,就导致和其他小边构成了一个环

贪心算法难点在于,它为什么是对的

切分定理

横切边中的最短边,属于最小生成树

可以用用数学归纳法证明

Kruskal算法实现

  1. public class WeightedEdge implements Comparable<WeightedEdge>{
  2. private int v, w, weight;
  3. public WeightedEdge(int v, int w, int weight){
  4. this.v = v;
  5. this.w = w;
  6. this.weight = weight;
  7. }
  8. public int getV(){return v;}
  9. public int getW(){return w;}
  10. public int getWeight(){return weight;}
  11. @Override
  12. public int compareTo(WeightedEdge another){
  13. return weight - another.weight;
  14. }
  15. @Override
  16. public String toString(){
  17. return String.format("(%d-%d: %d)", v, w, weight);
  18. }
  19. }
  1. import java.util.ArrayList;
  2. public class CC {//求带权图的联通分量个数
  3. private WeightedGraph G;
  4. private int[] visited;
  5. private int cccount = 0;
  6. public CC(WeightedGraph G){
  7. this.G = G;
  8. visited = new int[G.V()];
  9. for(int i = 0; i < visited.length; i ++)
  10. visited[i] = -1;
  11. for(int v = 0; v < G.V(); v ++)
  12. if(visited[v] == -1){
  13. dfs(v, cccount);
  14. cccount ++;
  15. }
  16. }
  17. private void dfs(int v, int ccid){
  18. visited[v] = ccid;
  19. for(int w: G.adj(v))
  20. if(visited[w] == -1)
  21. dfs(w, ccid);
  22. }
  23. public int count(){
  24. return cccount;
  25. }
  26. public boolean isConnected(int v, int w){
  27. G.validateVertex(v);
  28. G.validateVertex(w);
  29. return visited[v] == visited[w];
  30. }
  31. public ArrayList<Integer>[] components(){
  32. ArrayList<Integer>[] res = new ArrayList[cccount];
  33. for(int i = 0; i < cccount; i ++)
  34. res[i] = new ArrayList<Integer>();
  35. for(int v = 0; v < G.V(); v ++)
  36. res[visited[v]].add(v);
  37. return res;
  38. }
  39. }
  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. public class Kruskal {
  4. private WeightedGraph G;
  5. private ArrayList<WeightedEdge> mst;//最小生成树返回的是用ArrayList存储的一个个边
  6. public Kruskal(WeightedGraph G){//用户传来一个带权图G
  7. this.G = G;
  8. mst = new ArrayList<>();//开空间
  9. CC cc = new CC(G);//带权图中联通分量个数
  10. if(cc.count() > 1) return;//有断开区域,最小生成树是null
  11. //Kruskal
  12. ArrayList<WeightedEdge> edges = new ArrayList<>();//存储所有的边
  13. for(int v = 0; v < G.V(); v ++)//把所有边放入edges中
  14. for(int w: G.adj(v))
  15. if(v < w)//防止类似0-2,2-0重复遍历的问题
  16. edges.add(new WeightedEdge(v, w, G.getWeight(v, w)));
  17. Collections.sort(edges);//把边传进去排序,记得设定WeightedEdge为可比较的类
  18. //判断是否行成环
  19. UF uf = new UF(G.V());//有几个顶点UF中就有多少元素
  20. for(WeightedEdge edge: edges){
  21. int v = edge.getV();
  22. int w = edge.getW();
  23. if(!uf.isConnected(v, w)){//v和w不在一个集合时才连成边
  24. mst.add(edge);
  25. uf.unionElements(v, w);
  26. }
  27. }
  28. }
  29. public ArrayList<WeightedEdge> result(){
  30. return mst;
  31. }//返回最小生成树
  32. public static void main(String[] args){
  33. WeightedGraph g = new WeightedGraph("g.txt");
  34. Kruskal kruskal = new Kruskal(g);
  35. System.out.println(kruskal.result());
  36. }
  37. }

其中,Kruskal算法中快速判断是否生成环

  1. public class UF{
  2. private int[] parent;
  3. public UF(int n){
  4. parent = new int[n];
  5. for(int i = 0 ; i < n ; i ++)
  6. parent[i] = i;
  7. }
  8. public int find(int p){
  9. if( p != parent[p] )
  10. parent[p] = find( parent[p] );
  11. return parent[p];
  12. }
  13. public boolean isConnected(int p , int q){
  14. return find(p) == find(q);
  15. }
  16. public void unionElements(int p, int q){
  17. int pRoot = find(p);
  18. int qRoot = find(q);
  19. if( pRoot == qRoot )
  20. return;
  21. parent[pRoot] = qRoot;
  22. }
  23. }

时间复杂度:O(ElogE)

Prim算法 

  1. import java.util.ArrayList;
  2. public class Prim {
  3. private WeightedGraph G;
  4. private ArrayList<WeightedEdge> mst;
  5. public Prim(WeightedGraph G){
  6. this.G = G;
  7. mst = new ArrayList<>();
  8. CC cc = new CC(G);
  9. if(cc.count() > 1) return;//判断联通图
  10. //Prim
  11. boolean[] visited = new boolean[G.V()];//切分在初始时,只有一个点切了
  12. visited[0] = true;
  13. for(int i = 1; i < G.V(); i ++){//共切V-1次
  14. WeightedEdge minEdge = new WeightedEdge(-1, -1, Integer.MAX_VALUE);//声明一个最大权值,用来用后面的小权值覆盖它
  15. for(int v = 0; v < G.V(); v ++)//每次切的时候扫描所有的边
  16. if(visited[v])//当一个边访问过了,再访问它所有的邻边
  17. for(int w: G.adj(v))
  18. if(!visited[w] && G.getWeight(v, w) < minEdge.getWeight())//如果没访问过,且权值小于minEdge
  19. minEdge = new WeightedEdge(v, w, G.getWeight(v, w));//更新一下minEdge这条边
  20. mst.add(minEdge);
  21. visited[minEdge.getV()] = true;
  22. visited[minEdge.getW()] = true;
  23. }
  24. }
  25. public ArrayList<WeightedEdge> result(){
  26. return mst;
  27. }
  28. public static void main(String[] args){
  29. WeightedGraph g = new WeightedGraph("g.txt");
  30. Prim prim = new Prim(g);
  31. System.out.println(prim.result());
  32. }
  33. }

输入

  1. 7 12
  2. 0 1 2
  3. 0 3 7
  4. 0 5 2
  5. 1 2 1
  6. 1 3 4
  7. 1 4 3
  8. 1 5 5
  9. 2 4 4
  10. 2 5 4
  11. 3 4 1
  12. 3 6 5
  13. 4 6 7

输出

时间复杂度:O((V-1)*(V+E))=O(VE)

优先队列中所有的横切边不一定合法

这里的1-5,2-5,使用时先判断,非法的话直接扔掉就行

  1. import java.util.ArrayList;
  2. import java.util.Queue;
  3. import java.util.PriorityQueue;
  4. public class Prim {
  5. private WeightedGraph G;
  6. private ArrayList<WeightedEdge> mst;
  7. public Prim(WeightedGraph G){
  8. this.G = G;
  9. mst = new ArrayList<>();
  10. CC cc = new CC(G);
  11. if(cc.count() > 1) return;//判断联通图
  12. //Prim
  13. boolean visited[] = new boolean[G.V()];//切分在初始时,只有一个点切了
  14. visited[0] = true;
  15. Queue pq = new PriorityQueue<WeightedEdge>();//声明优先队列,里面存储带权边
  16. for(int w: G.adj(0))//添加初始状态下的横切边,即所有和0相邻的边
  17. pq.add(new WeightedEdge(0, w, G.getWeight(0, w)));
  18. while(!pq.isEmpty()){//如果用V-1个循环来弄的话还要判断是否合法
  19. WeightedEdge minEdge = (WeightedEdge) pq.remove();//这个minEdge有可能是最短横切边
  20. if(visited[minEdge.getV()] && visited[minEdge.getW()])//如果minEdge两个端点都访问过,则非法,跳过它
  21. continue;
  22. mst.add(minEdge);//合法边加入mst最小生成树中
  23. //拓展切分,有可能产生新的最小横切边
  24. int newv = visited[minEdge.getV()] ? minEdge.getW() : minEdge.getV();//判断哪个点没有被访问
  25. visited[newv] = true;//没有访问的那个点设为已访问
  26. for(int w: G.adj(newv))//取出newv的所有邻边
  27. if(!visited[w])
  28. //把有可能的最小横切边加入优先队列,在下一次的循环中从优先队列中取出最小的那个边
  29. pq.add(new WeightedEdge(newv, w, G.getWeight(newv, w)));
  30. }
  31. }
  32. public ArrayList<WeightedEdge> result(){
  33. return mst;
  34. }
  35. public static void main(String[] args){
  36. WeightedGraph g = new WeightedGraph("g.txt");
  37. Prim prim = new Prim(g);
  38. System.out.println(prim.result());
  39. }
  40. }

时间复杂度:O(Elog E)首选(因为其他语言几乎没有并查集

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/152134
推荐阅读
相关标签
  

闽ICP备14008679号