数据结构与算法--图论

打印 上一主题 下一主题

主题 2037|帖子 2037|积分 6111

目录

一 熟悉
图的表示
图的应用
 二 DFS和BFS
三 拓扑排序
 四 Dijkstra算法
 五 BellmanFord算法
一、基本原理
二、算法步调
三、算法特点
 六 Floyd-Warshall算法
一、算法概述
二、算法步调
三、算法实现
 七 最小生成树算法
一、Prim算法
二、 kruskal算法
一、算法思想
二、具体步调
三、核心问题
四、算法特点
五、应用场景


一 熟悉

在Java中,"图"(Graph)是一种非常紧张的数据结构,用于表示一组对象(称为顶点或节点)之间的连接关系。图可以是无向的(边没有方向)或有向的(边有方向)。图在办理许多算法问题中非常有用,如路径查找、网络流、最短路径、图遍历(如深度优先搜索DFS和广度优先搜索BFS)等。
图的表示

在Java中,图可以通过多种方式表示,此中两种常见的方法是:

  • 邻接矩阵:利用二维数组来表示图。数组中的每个元素表示顶点对之间的连接状态(在有向图中,还大概表示边的权重或是否存在边)。如果顶点i和顶点j之间有一条边,则对应的数组元素非零(或者存储边的权重)。
  • 邻接表:对于图中的每个顶点,都有一个列表来存储与该顶点相邻的全部顶点。这种方法比邻接矩阵更节省空间,特殊是当图很稀疏(即边的数目远小于顶点对数目的图)时。
图的应用

图的应用非常广泛,包罗但不限于:


  • 社交网络分析:将用户作为顶点,用户之间的关系(如朋侪关系)作为边。
  • 最短路径问题:如Dijkstra算法或A*算法,用于找到从一个顶点到另一个顶点的最短路径。
  • 网络流问题:如最大流问题,用于确定通过图的流量网络的最大大概流量。
  • 图的遍历:如深度优先搜索(DFS)和广度优先搜索(BFS),用于访问或搜索图的顶点。
  • 舆图导航:将地点作为顶点,路径作为边,办理路径查找和规划问题。
通过学习和把握图的相干算法,你可以办理许多实际中遇到的复杂问题。











 二 DFS和BFS

  1. public static void dfs(Vertex v) {
  2.     dfs1(v);
  3. }
  4. public static void dfs1(Vertex v) {
  5.     v.visited=true;
  6.     System.out.println(v.name);
  7.     for (Edge edge:v.edges) {
  8.        Vertex v1=edge.linked;
  9.        if (!v1.visited) {
  10.           dfs1(v1);
  11.        }
  12.     }
  13. }
  14. public static void dfs2(Vertex v) {
  15.     LinkedList<Vertex> stack=new LinkedList<Vertex>();
  16.     stack.push(v);
  17.     while(!stack.isEmpty()) {
  18.        Vertex v1=stack.pop();
  19.        v1.visited=true;
  20.        System.out.println(v1.name);
  21.        for(Edge edge:v1.edges) {
  22.           Vertex vv=edge.linked;
  23.           if (!vv.visited) {
  24.              stack.push(vv);
  25.           }
  26.           }
  27.        }
  28.     }
  29. public static void bfs(Vertex v) {
  30.     LinkedList<Vertex> queue=new LinkedList<Vertex>();
  31.     queue.offer(v);
  32.     v.visited=true;
  33.     while (!queue.isEmpty()) {
  34.        Vertex vv=queue.poll();
  35.        System.out.println(vv.name);
  36.        for(Edge edge:vv.edges) {
  37.           if (!edge.linked.visited) {
  38.              edge.linked.visited=true;
  39.              queue.offer(edge.linked);
  40.           }
  41.        }
  42.     }
  43.    
  44.    
  45.     }
复制代码

  1. /**
  2. * 边
  3. */
  4. public class Edge {
  5.     Vertex linked;// 边指向的点
  6.     int weight;
  7.     public Edge(Vertex linked) {
  8.         this(linked, 1);
  9.     }
  10.     public Edge(Vertex linked, int weight) {
  11.         this.linked = linked;
  12.         this.weight = weight;
  13.     }
  14. }
复制代码
  1. /**
  2. * 顶点
  3. */
  4. public class Vertex {
  5.     String name;// 顶点的名称
  6.     List<Edge> edges;// 与其相连的边
  7.    
  8.     boolean visited;
  9.    
  10.     public Vertex(String name) {
  11.        this.name = name;
  12.     }
  13.    
  14.     public String getName() {
  15.        return name;
  16.     }
  17.    
  18. }
复制代码
三 拓扑排序



  • 首先求出图中全部节点的度(即与该节点相连的边的数目)。
  • 将全部度小于等于1的节点入队。
  • 当队列不空时,弹出队首元素,并将其相邻节点的度减1。如果相邻节点的度变为1,则将该相邻节点入队。
如果拓扑排序中出现环,则是无法将环中元素输出的
下图中,Spring输出后,微服务框架对应的入度从2减1 还有1 则不会加入队列中 循环结束了
判定是否有环的方法:
判定已经访问的节点数是否等于图中的总节点数。如果相等,阐明无环;否则,有环。



  1. public class TopologicalSort {
  2.     public static void main(String[] args) {
  3.        Vertex v1 = new Vertex("网页基础");
  4.        Vertex v2 = new Vertex("Java基础");
  5.        Vertex v3 = new Vertex("JavaWeb");
  6.        Vertex v4 = new Vertex("Spring框架");
  7.        Vertex v5 = new Vertex("微服务框架");
  8.        Vertex v6 = new Vertex("数据库");
  9.        Vertex v7 = new Vertex("实战项目");
  10.        v1.edges = List.of(new Edge(v3));
  11.        v2.edges = List.of(new Edge(v3));
  12.        v3.edges = List.of(new Edge(v4));
  13.        v4.edges = List.of(new Edge(v5));
  14.        v5.edges = List.of(new Edge(v7));
  15.        v6.edges = List.of(new Edge(v4));
  16.        v7.edges = List.of();
  17.        List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6, v7);
  18.        // 1.统计每个顶点的入度
  19.        for (Vertex vertex : graph) {
  20.           for (Edge edge : vertex.edges) {
  21.              edge.linked.inDegree++;
  22.           }
  23.        }
  24.        // 2. 找到入度为0的顶点,输出,并将其邻接点的入度减1
  25.        LinkedList<Vertex> queue = new LinkedList<>();
  26.        for (Vertex vertex : graph) {
  27.           if (vertex.inDegree == 0) {
  28.              queue.offer(vertex);
  29.           }
  30.        }
  31.        // 3.队列中不断移除顶点,移除顶点的邻接点的入度--,并将入度为0的顶点加入队列
  32.        while (!queue.isEmpty()) {
  33.           Vertex pop = queue.poll();
  34.           System.out.println(pop.name);
  35.           for (Edge edge : pop.edges) {
  36.              edge.linked.inDegree--;
  37.              if (edge.linked.inDegree == 0) {
  38.                 queue.offer(edge.linked);
  39.              }
  40.           }
  41.        }
  42.     }
  43. }
复制代码

DFS深度遍历实现拓扑排序
给顶点加上三个状态 0未访问 1已访问 2正访问




    • 利用DFS遍历图,并为每个节点设置三种状态:白色(未访问)、灰色(正在访问)、黑色(已访问完毕)。
    • 如果在遍历过程中发现某个节点有一条边指向灰色节点(即该节点的某个子女节点),并且这个灰色节点不是上一步访问的节点,则阐明存在环。

  1. LinkedList<String> stack = new LinkedList<>();
  2.     for (Vertex vertex : graph) {
  3.        dfs(vertex, stack);
  4.     }
  5.     while (!stack.isEmpty()) {
  6.        System.out.println(stack.poll());
  7.     }
  8. }
  9. public static void dfs(Vertex v, LinkedList<String> stack){
  10.     if (v.status==1){
  11.        // 已经被访问过了
  12.        return;
  13.     }
  14.     if (v.status==2){
  15.        throw new IllegalStateException("有环");
  16.     }
  17.     v.status=2; // 在访问
  18.     for (Edge edge : v.edges) {
  19.        dfs(edge.linked, stack);
  20.     }
  21.     v.status=1;
  22.     stack.push(v.name);
  23.    
  24. }
复制代码
 
 四 Dijkstra算法

迪克斯特拉 单源最短路径算法
思绪:


  • 利用邻接表来构建图模型,每个点对应其全部邻接点,定义一个边的类,包含to(尽头),和weight(权重)
  • 定义一个dis[],表示到每个点的距离,初始时到每个点的距离为Integer.MaxValue();
  • dis[start]=0,到起点的距离为0
  • 每次从未访问的节点中选取一个距离起点最近的节点(通过优先队列实现)。然后遍历这个节点的邻接点,更新起点到新的节点的距离,如果新的距离<原来的距离,把新的节点加入到优先队列中
  • 利用visited数组标记哪个节点已经访问过,如果该节点已经访问过,则不再访问
时间复杂度: O((n+m)⋅log⁡n),此中 n 是节点数,m是边数。




    • 邻接表创建的复杂度是 O(m)。
    • 优先队列每次操作的复杂度是 log⁡n,最多会处理 m+n次操作。

空间复杂度: O(n+m)用于存储图的邻接表和辅助数组。


  1. package TuLun;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.io.StreamTokenizer;
  6. import java.util.*;
  7. public class P4779 {
  8.     public static void main(String[] args) throws IOException {
  9.         StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
  10.         st.nextToken();
  11.         int n =(int)st.nval;
  12.         st.nextToken();
  13.         int m = (int)st.nval;
  14.         st.nextToken();
  15.         int s = (int)st.nval;
  16.         List<edge>[]graphs=new List[n+1];
  17.         for (int i = 0; i < graphs.length; i++) {
  18.             graphs[i]=new ArrayList<>();
  19.         }
  20.         for (int i = 1; i <= m; i++) {
  21.             st.nextToken();
  22.             int u = (int)st.nval;
  23.             st.nextToken();
  24.             int v = (int)st.nval;
  25.             st.nextToken();
  26.             long w = (int)st.nval;
  27.             graphs[u].add(new edge(v,w));
  28.         }
  29.         long[]dis=new long[n+1];
  30.         for (int i = 1; i <= n; i++) {
  31.             dis[i]=Integer.MAX_VALUE;
  32.         }
  33.         dis[s]=0;
  34.        PriorityQueue<long[]>queue=new PriorityQueue<>(Comparator.comparingLong(longs -> longs[1]));
  35.        // 到哪个节点,距离
  36.        queue.add(new long[]{s,0});
  37.        boolean[]visited=new boolean[n+1];
  38.        while (!queue.isEmpty()){
  39.            long[] arr = queue.poll();
  40.            int node = (int)arr[0];
  41.            long curDis = arr[1];
  42.            if (visited[node])continue;
  43.            visited[node]=true;
  44.            // 遍历该节点的邻接点 然后更新start到其邻接点的距离
  45.            for (edge edge : graphs[node]) {
  46.                int nextNode = edge.to;
  47.                long nextDis = edge.weight;
  48.                if (dis[nextNode]>curDis+nextDis){
  49.                    dis[nextNode]=curDis+nextDis;
  50.                    queue.offer(new long[]{nextNode,dis[nextNode]});
  51.                }
  52.            }
  53.        }
  54.         for (int i = 1; i <=n ; i++) {
  55.             System.out.print(dis[i]+" ");
  56.         }
  57.     }
  58.     public static class edge{
  59.         int to;
  60.         long weight;
  61.         public edge(int to, long weight) {
  62.             this.to = to;
  63.             this.weight = weight;
  64.         }
  65.     }
  66. }
复制代码
 五 BellmanFord算法

BellmanFord可以处理负边,Dijkstra不可以
BellmanFord,即贝尔曼-福特算法(Bellman–Ford algorithm),是一种用于盘算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法。以下是对BellmanFord算法的具体表明:
一、基本原理

贝尔曼-福特算法通过多次迭代(松弛操作)来逐步迫近从原点到图中全部其他节点的最短路径。算法的核心在于对每一条边举行多次查抄,看是否存在通过这条边可以收缩从源点到某个节点的路径的环境。
二、算法步调


  • 初始化:将全部节点到源点的距离初始化为无穷大(除了原点到自身的距离为0)。
  • 松弛操作:对图中的每一条边举行多次(通常是V-1次,V是节点数)松弛操作。在每次松弛操作中,对于每一条边(u, v),如果通过u到达v的路径比当前已知的从源点到v的路径更短,则更新v的距离。
  • 查抄负权环:在完成V-1次松弛操作后,再举行一次额外的松弛操作。如果在这次操作中还能更新某个节点的距离,则阐明图中存在负权环,因为负权环答应无限次地减少路径的总权重。
三、算法特点


  • 支持负权边:与Dijkstra算法差别,Bellman-Ford算法能够处理图中存在负权边的环境,这是其最显著的长处之一。
  • 检测负权回路:在完玉成部边的松弛操作后,算法还能通过额外的步调检测图中是否存在负权回路(即负权环),这是其他某些算法所不具备的功能。
  • 实现简朴:算法的实现相对直观,轻易理解和编程实现。
  • 时间复杂度较高:Bellman-Ford算法的时间复杂度为O(VE),此中V是顶点数,E是边数。在边数较多的图中,算法的实行服从较低。
  • 无法处理负权环:虽然算法能检测负权环,但在图中存在负权环时,算法无法给出正确的最短路径解,因为负权环会导致路径长度无限减小。
  1. public static void  BellmanFord(List<Vertex> graph) {
  2.     // 1.进行顶点个数-1轮处理
  3.     for (int i = 0; i < graph.size()-1; i++) {
  4.         // 2.遍历所有边
  5.         for (Vertex s : graph) {
  6.             for (Edge edge : s.edges) {
  7.                 // 3. 如果存在更短路径,更新路径
  8.                 Vertex linked = edge.linked;
  9.                 if (s.distance!=Integer.MAX_VALUE && s.distance+edge.weight< linked.distance){
  10.                    linked.distance = s.distance+edge.weight;
  11.                    linked.prev = s;
  12.                 }
  13.             }
  14.         }
  15.     }
复制代码


  1. package TuLun;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.io.StreamTokenizer;
  6. import java.util.ArrayList;
  7. import java.util.Arrays;
  8. import java.util.List;
  9. public class P3385 {
  10.     public static void main(String[] args) throws IOException {
  11.         StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
  12.         st.nextToken();
  13.         int t = (int) st.nval;
  14.         for (int i = 0; i < t; i++) {
  15.             st.nextToken();
  16.             int n= (int) st.nval;
  17.             st.nextToken();
  18.             int m= (int) st.nval;
  19.             List<Edge>[]graphs=new List[n+1];
  20.             for (int j = 1; j <=n ; j++) {
  21.                 graphs[j]=new ArrayList<>();
  22.             }
  23.             for (int j = 0; j < m; j++) {
  24.                 st.nextToken();
  25.                 int u= (int) st.nval;
  26.                 st.nextToken();
  27.                 int v= (int) st.nval;
  28.                 st.nextToken();
  29.                 int w= (int) st.nval;
  30.                 if (w>=0){
  31.                     graphs[u].add(new Edge(v,w));
  32.                     graphs[v].add(new Edge(u,w));
  33.                 }else{
  34.                     graphs[u].add(new Edge(v,w));
  35.                 }
  36.             }
  37.             long[]dis=new long[n+1];
  38.             Arrays.fill(dis,Integer.MAX_VALUE);
  39.             dis[1]=0;
  40.             for (int j = 0; j < n-1; j++) {
  41.                 for (int k = 1; k <=n; k++) {
  42.                     for (Edge edge : graphs[k]) {
  43.                         int nextNode = edge.to;
  44.                         long nextDis=edge.weight;
  45.                         if (dis[k]!=Integer.MAX_VALUE  && dis[nextNode]>dis[k]+nextDis){
  46.                             dis[nextNode]=dis[k]+nextDis;
  47.                         }
  48.                     }
  49.                 }
  50.             }
  51.             boolean flag=false;
  52.             for (int k = 1; k <=n; k++) {
  53.                 for (Edge edge : graphs[k]) {
  54.                     int nextNode = edge.to;
  55.                     long nextDis=edge.weight;
  56.                     if (dis[k]!=Integer.MAX_VALUE && dis[nextNode]>dis[k]+nextDis){
  57.                         flag=true;
  58.                         break;
  59.                     }
  60.                 }
  61.                 if (flag)break;
  62.             }
  63.             if (flag){
  64.                 System.out.println("YES");
  65.             }else{
  66.                 System.out.println("NO");
  67.             }
  68.         }
  69.     }
  70.     public static class Edge{
  71.         int to;
  72.         long weight;
  73.         public Edge(int to, long weight) {
  74.             this.to = to;
  75.             this.weight = weight;
  76.         }
  77.     }
  78. }
复制代码
 六 Floyd-Warshall算法

FloydWarshall,即Floyd-Warshall算法,是一种用于求解图中求解任意两点间的最短路径的动态规划算法。以下是对Floyd-Warshall算法的具体表明:
一、算法概述

Floyd-Warshall算法可以在有向图或无向图中探求全部顶点对之间的最短路径。它不仅能够处理正权边,还能处理负权边,但图中不能包含负权循环。该算法的时间复杂度为O(V2)。
二、算法步调


  • 初始化:创建一个n×n的距离矩阵D,此中D[j]表示从顶点i到顶点j的最短路径的长度。如果(i,j)之间没有直接的边,则D[j]设置为无穷大(或某个充足大的数来表示不可达)。对于全部的顶点i,D=0,表示从自身到自身的距离为0。
  • 动态规划过程:对于每一个中间顶点k,更新距离矩阵D。具体来说,对于每一对顶点(i, j),如果D[j] > D[k] + D[k][j],则更新D[j]为D[k] + D[k][j]。这相当于查抄是否可以通过k作为中间顶点来获得从i到j的更短路径。
  • 迭代:重复上述动态规划过程,直到全部大概的中间顶点都被考虑过。
  • 效果:最终,距离矩阵D中的每个元素D[j]将包含从顶点i到顶点j的最短路径的长度。
三、算法实现

Floyd-Warshall算法的实现通常利用嵌套的三层循环来遍历全部节点对和中间节点。
查验是否有环的方法:


  • 在dist中判定对角线上是否存在负数
  • 对角线是本身到本身最小应该为0
  • 绕了一圈回到本身<0
  1. public class FloydWarshall {
  2.     public static void main(String[] args) {
  3.         Vertex v1 = new Vertex("v1");
  4.         Vertex v2 = new Vertex("v2");
  5.         Vertex v3 = new Vertex("v3");
  6.         Vertex v4 = new Vertex("v4");
  7.         v1.edges = List.of(new Edge(v3,-2));
  8.         v2.edges = List.of(new Edge(v1,4),new Edge(v3,3));
  9.         v3.edges = List.of(new Edge(v4,2));
  10.         v4.edges = List.of(new Edge(v2,-1));
  11.         List<Vertex> graph = List.of(v1,v2,v3,v4);
  12.         floydWarshall(graph);
  13.     }
  14.     /**
  15.      *      v1 v2 v3 v4
  16.      *   v1
  17.      *   v2
  18.      *   v3
  19.      *   v4
  20.      * @param graph
  21.      */
  22.     public static void floydWarshall(List<Vertex> graph){
  23.         long[][] dist = new long[graph.size()][graph.size()];
  24.         Vertex[][] prev = new Vertex[graph.size()][graph.size()];  // 路径
  25.         // 1.初始化距离
  26.         for (int i = 0; i < graph.size(); i++) {
  27.             // i起点 j终点
  28.             Map<Vertex, Integer> map = graph.get(i).edges.stream().collect(Collectors.toMap(k -> k.linked, v -> v.weight));
  29.             for (int j = 0; j < graph.size(); j++) {
  30.                 if (i == j){
  31.                     // 自己到自己距离为0
  32.                     dist[i][j] = 0;
  33.                 }else{
  34.                     // 判断起点是否和终点连接 不连接则距离无穷大
  35.                     Integer orDefault = map.getOrDefault(graph.get(j), Integer.MAX_VALUE);
  36.                     dist[i][j] = orDefault;
  37.                     // i到j j的上一个应该为i
  38.                     prev[i][j] = graph.get(i);
  39.                 }
  40.             }
  41.         }
  42.         // 2.借助中间点k从起点i到达结束点j 或者也可以从起点i出发借助中间点k到结束点j i,j,k都是一轮循环
  43.         // 2.1有几个点则进行几轮借助 每个点都去借助一轮别的点
  44.         for (int k = 0; k < graph.size(); k++) {
  45.             // 2.2 i 起点
  46.             for (int i = 0; i < graph.size(); i++) { // 多源的 所以起点也要换 每个顶点当一次起点
  47.                 // 2.3 j终点
  48.                 for (int j = 0; j < graph.size(); j++) {
  49.                     if (dist[i][k]!=Integer.MAX_VALUE && dist[k][j]!=Integer.MAX_VALUE
  50.                             && dist[i][k]+dist[k][j]<dist[i][j]){
  51.                         dist[i][j] =dist[i][k]+dist[k][j];
  52.                         prev[i][j]=graph.get(k);
  53.                     }
  54.                 }
  55.             }
  56.         }
  57.         // 3.打印结果
  58. //        for (int i = 0; i < graph.size(); i++) {
  59. //            for (int j = 0; j < graph.size(); j++) {
  60. //                System.out.print(dist[i][j]+" ");
  61. //            }
  62. //            System.out.println();
  63. //        }
  64.         for (int i = 0; i < graph.size(); i++) {
  65.             for (int j = 0; j < graph.size(); j++) {
  66.                 path(prev,graph,i,j);
  67.             }
  68.         }
  69.     }
  70.     private static void path(Vertex[][] prev, List<Vertex> graph,int i,int j) {
  71.         LinkedList<String> stack = new LinkedList<>();
  72.         System.out.print("["+graph.get(i).name+","+graph.get(j).name+"]  " );
  73.         stack.push(graph.get(j).name);
  74.         while(i!=j){
  75.             Vertex vertex = prev[i][j];
  76.             stack.push(vertex.name);
  77.             j = graph.indexOf(vertex);
  78.         }
  79.         System.out.println(String.join("->", stack));
  80.     }
  81. }
复制代码
 七 最小生成树算法

一、Prim算法

Prim算法是一种用于求解最小生成树(Minimum Spanning Tree, MST)的经典算法。最小生成树是指在一个加权无向图中,找到一棵包含全部顶点边权值之和最小的树。Prim算法的基本思想是从图的某一顶点开始,逐步扩展生成树,每次选择一条连接生成树顶点聚集未连接顶点聚集中权值最小的边,直到生成树包含全部顶点。
  1. public static void main(String[] args) {
  2.     Vertex v1 = new Vertex("v1");
  3.     Vertex v2 = new Vertex("v2");
  4.     Vertex v3 = new Vertex("v3");
  5.     Vertex v4 = new Vertex("v4");
  6.     Vertex v5 = new Vertex("v5");
  7.     Vertex v6 = new Vertex("v6");
  8.     v1.edges = List.of(new Edge(v3,9),new Edge(v2,7),new Edge(v6,14));
  9.     v2.edges = List.of(new Edge(v4,15));
  10.     v3.edges = List.of(new Edge(v4,11),new Edge(v6,2));
  11.     v4.edges = List.of(new Edge(v5,6));
  12.     v5.edges = List.of();
  13.     v6.edges = List.of(new Edge(v5,9));
  14.     List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6);
  15.     // 1.创建一个优先级队列,按照距离进行排序,默认为小顶堆
  16.     PriorityQueue<Vertex> queue = new PriorityQueue<>(Comparator.comparingInt(v->v.distance));
  17.     // 2.为每个顶点初始化距离
  18.     v1.distance = 0;
  19.     for (Vertex vertex : graph) {
  20.         if (vertex != v1) {
  21.             vertex.distance = Integer.MAX_VALUE;
  22.         }
  23.     }
  24.     for (Vertex vertex : graph) {
  25.         queue.offer(vertex);
  26.     }
  27.     // 3.每次选择距离最小的顶点,作为新的当前顶点
  28.     while (!queue.isEmpty()) {
  29.         // 3.1 取出距离最小的顶点
  30.         Vertex current = queue.peek();
  31.         // 3.2 更新距离
  32.         if (!current.visited){
  33.             updateDistance(current, queue);
  34.             current.visited = true;
  35.         }
  36.         // 3.3 从队列中删除该节点
  37.         queue.poll();
  38.     }
  39.     // 4.打印路径
  40.     for (Vertex vertex : graph) {
  41.         System.out.println(vertex.name +" "+ vertex.distance+" "+ (vertex.prev == null ? "null" : vertex.prev.name));
  42.     }
  43. }
  44. public static void updateDistance(Vertex current, PriorityQueue<Vertex> queue) {
  45.     // 3.3 遍历当前顶点的邻居
  46.     for (Edge edge : current.edges) {
  47.         Vertex n = edge.linked;
  48.         if (!n.visited){
  49.             int dist=edge.weight;
  50.             // 3.4 如果距离小于邻居的距离,则更新邻居的距离
  51.             if (dist < n.distance) {
  52.                 n.distance = dist;
  53.                 // 更新该节点的距离
  54.                 queue.offer(n);
  55.                 n.prev = current;
  56.             }
  57.         }
  58.     }
  59. }
复制代码
二、 kruskal算法

Kruskal算法,即克鲁斯卡尔算法,是一种用于在加权连通图中探求最小生成树的算法。以下是对Kruskal算法的具体表明:
一、算法思想

Kruskal算法的基本思想是按照权值从小到大的次序选择n-1条边,并保证这n-1条边不构成回路,从而构成一棵极小连通子图,该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树。
二、具体步调


  • 从加权图中找出全部的边,初始时全部边都不属于最小生成树。
  • 将全部的边按照权值从小到大举行排序。
  • 从排序后的边中依次取出边,并判定该边及其连接的两个顶点加入最小生成树是否会形成环路。




    • 若形成环路,则跳过此边,继续取下一条边举行判定。
    • 若不形成环路,则将该边及其连接的两个顶点加入最小生成树。


  • 重复上述步调,直至全部顶点均连接在一起,并且没有形成环路时,最小生成树就找到了。
三、核心问题


  • 如何对图的全部边按照权值大小举行排序?




    • 应对策略:接纳排序算法(如快速排序、归并排序等)对图的全部边按照权值大小举行排序。


  • 如何判定边及其顶点加入最小生成树是否会形成回路?




    • 应对策略:可以利用并查集(Union Find)数据结构来判定。在将边加入最小生成树之前,先判定该边的两个顶点是否已经在同一个聚集中(即是否已经连通)。若已经连通,则加入该边会形成回路;若未连通,则可以将该边加入最小生成树,并将两个顶点所在的聚集归并。

四、算法特点


  • Kruskal算法是将边作为操作对象,当加权图的边越多,要处理的边也越多,则算法的时间复杂度就越高;而顶点的数目对算法的时间复杂度无影响。因此,Kruskal算法适合处理稀疏图(边较少的图)。
  • Kruskal算法的时间复杂度重要取决于排序操作,一样平常为O(mlogm),此中m为边的数目。
五、应用场景

Kruskal算法广泛应用于网络设计、交通规划、电路布线等领域中,用于求解最小生成树问题。比方,在构建通信网络时,需要选择权值(如距离、成本等)最小的边来连接各个节点,以保证网络的连通性和最小成本。
综上所述,Kruskal算法是一种高效、实用的求解最小生成树的算法,特殊实用于处理稀疏图的环境。
  1. static void kruskal(int size, PriorityQueue<Edge> queue){
  2.     List<Edge> edges = new ArrayList<>();
  3.     DisjoinSet set=new DisjoinSet(size);
  4.     while (!queue.isEmpty()){
  5.         Edge poll = queue.poll();
  6.         int i = set.find(poll.start);// 找到起点的老大
  7.         int j = set.find(poll.end);// 找到终点的老大
  8.         if (i!=j){
  9.             // 老大索引不一致 不是一个老大
  10.             edges.add(poll);
  11.             set.union(i,j);// 相交 给原本的两个老大整个共同的新老大
  12.         }
  13.     }
  14. }
复制代码
  1. package com.shutu.Graph;
  2. public class DisjoinSet {
  3.     int[] s;
  4.     // 初始化,n个元素的并查集,初始每个元素都是一个集合,自身为老大
  5.     // 索引代表顶点
  6.     // 值代表有关系的顶点
  7.     public DisjoinSet(int n) {
  8.         s = new int[n];
  9.         for (int i = 0; i < n; i++) {
  10.             s[i] = i;
  11.         }
  12.     }
  13.     // 找到老大是谁
  14.     public int find(int x) {
  15.         if (s[x] == x) {
  16.             return x;
  17.         }
  18.         return find(s[x]);
  19.         // 路径压缩 return s[x]=find(s[x]);
  20.         
  21.     }
  22.     // 是让两个集合“相交”,即选出新老大,x、y是原老大索引
  23.     public void union(int x, int y) {
  24.         s[y]=x;
  25.     }
  26. }
复制代码



初始图



连接一些权重比较小的边 给每个点找到本身的老大 两个顶点老大雷同则阐明已相连



路径压缩 再找到他们的老大后,直接将其值改为老大值



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表