数据结构与算法--图论

发布于:2025-04-04 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

一 认识

图的表示

图的应用

 二 DFS和BFS

三 拓扑排序

 四 Dijkstra算法

 五 BellmanFord算法

一、基本原理

二、算法步骤

三、算法特点

 六 Floyd-Warshall算法

一、算法概述

二、算法步骤

三、算法实现

 七 最小生成树算法

一、Prim算法

二、 kruskal算法

一、算法思想

二、具体步骤

三、核心问题

四、算法特点

五、应用场景


一 认识

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

图的表示

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

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

图的应用

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

  • 社交网络分析:将用户作为顶点,用户之间的关系(如朋友关系)作为边。
  • 最短路径问题:如Dijkstra算法或A*算法,用于找到从一个顶点到另一个顶点的最短路径。
  • 网络流问题:如最大流问题,用于确定通过图的流量网络的最大可能流量。
  • 图的遍历:如深度优先搜索(DFS)和广度优先搜索(BFS),用于访问或搜索图的顶点。
  • 地图导航:将地点作为顶点,路径作为边,解决路径查找和规划问题。

通过学习和掌握图的相关算法,你可以解决许多实际中遇到的复杂问题。

 二 DFS和BFS

public static void dfs(Vertex v) {
    dfs1(v);
}

public static void dfs1(Vertex v) {
    v.visited=true;
    System.out.println(v.name);
    for (Edge edge:v.edges) {
       Vertex v1=edge.linked;
       if (!v1.visited) {
          dfs1(v1);
       }
    }
}

public static void dfs2(Vertex v) {
    LinkedList<Vertex> stack=new LinkedList<Vertex>();
    stack.push(v);
    while(!stack.isEmpty()) {
       Vertex v1=stack.pop();
       v1.visited=true;
       System.out.println(v1.name);
       for(Edge edge:v1.edges) {
          Vertex vv=edge.linked;
          if (!vv.visited) {
             stack.push(vv);
          }
          }
       }
    }




public static void bfs(Vertex v) {
    LinkedList<Vertex> queue=new LinkedList<Vertex>();
    queue.offer(v);
    v.visited=true;
    while (!queue.isEmpty()) {
       Vertex vv=queue.poll();
       System.out.println(vv.name);
       for(Edge edge:vv.edges) {
          if (!edge.linked.visited) {
             edge.linked.visited=true;
             queue.offer(edge.linked);
          }
       }
    }
    
    
    }

/**
 * 边
 */
public class Edge {

    Vertex linked;// 边指向的点
    int weight;

    public Edge(Vertex linked) {
        this(linked, 1);
    }

    public Edge(Vertex linked, int weight) {
        this.linked = linked;
        this.weight = weight;
    }

}
/**
 * 顶点
 */
public class Vertex {

    String name;// 顶点的名称
    List<Edge> edges;// 与其相连的边
    
    boolean visited;
    
    public Vertex(String name) {
       this.name = name;
    }
    
    public String getName() {
       return name;
    }
    
}

三 拓扑排序

  • 首先求出图中所有节点的度(即与该节点相连的边的数量)。
  • 将所有度小于等于1的节点入队。
  • 当队列不空时,弹出队首元素,并将其相邻节点的度减1。如果相邻节点的度变为1,则将该相邻节点入队。

如果拓扑排序中出现环,则是无法将环中元素输出的

下图中,Spring输出后,微服务框架对应的入度从2减1 还有1 则不会加入队列中 循环结束了

判断是否有环的方法:

判断已经访问的节点数是否等于图中的总节点数。如果相等,说明无环;否则,有环。

public class TopologicalSort {
    public static void main(String[] args) {
       Vertex v1 = new Vertex("网页基础");
       Vertex v2 = new Vertex("Java基础");
       Vertex v3 = new Vertex("JavaWeb");
       Vertex v4 = new Vertex("Spring框架");
       Vertex v5 = new Vertex("微服务框架");
       Vertex v6 = new Vertex("数据库");
       Vertex v7 = new Vertex("实战项目");

       v1.edges = List.of(new Edge(v3));
       v2.edges = List.of(new Edge(v3));
       v3.edges = List.of(new Edge(v4));
       v4.edges = List.of(new Edge(v5));
       v5.edges = List.of(new Edge(v7));
       v6.edges = List.of(new Edge(v4));
       v7.edges = List.of();

       List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6, v7);

       // 1.统计每个顶点的入度
       for (Vertex vertex : graph) {
          for (Edge edge : vertex.edges) {
             edge.linked.inDegree++;
          }
       }
       // 2. 找到入度为0的顶点,输出,并将其邻接点的入度减1
       LinkedList<Vertex> queue = new LinkedList<>();
       for (Vertex vertex : graph) {
          if (vertex.inDegree == 0) {
             queue.offer(vertex);
          }
       }
       // 3.队列中不断移除顶点,移除顶点的邻接点的入度--,并将入度为0的顶点加入队列
       while (!queue.isEmpty()) {
          Vertex pop = queue.poll();
          System.out.println(pop.name);
          for (Edge edge : pop.edges) {
             edge.linked.inDegree--;
             if (edge.linked.inDegree == 0) {
                queue.offer(edge.linked);
             }
          }
       }
    }
}

DFS深度遍历实现拓扑排序

给顶点加上三个状态 0未访问 1已访问 2正访问

    • 使用DFS遍历图,并为每个节点设置三种状态:白色(未访问)、灰色(正在访问)、黑色(已访问完毕)。
    • 如果在遍历过程中发现某个节点有一条边指向灰色节点(即该节点的某个后代节点),并且这个灰色节点不是上一步访问的节点,则说明存在环。
LinkedList<String> stack = new LinkedList<>();
    for (Vertex vertex : graph) {
       dfs(vertex, stack);
    }
    while (!stack.isEmpty()) {
       System.out.println(stack.poll());
    }
}

public static void dfs(Vertex v, LinkedList<String> stack){
    if (v.status==1){
       // 已经被访问过了
       return;
    }
    if (v.status==2){
       throw new IllegalStateException("有环");
    }
    v.status=2; // 在访问
    for (Edge edge : v.edges) {
       dfs(edge.linked, stack);
    }
    v.status=1;
    stack.push(v.name);
    
}

 

 四 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)用于存储图的邻接表和辅助数组。

package TuLun;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.*;

public class P4779 {
    public static void main(String[] args) throws IOException {
        StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        st.nextToken();
        int n =(int)st.nval;
        st.nextToken();
        int m = (int)st.nval;
        st.nextToken();
        int s = (int)st.nval;
        List<edge>[]graphs=new List[n+1];
        for (int i = 0; i < graphs.length; i++) {
            graphs[i]=new ArrayList<>();
        }
        for (int i = 1; i <= m; i++) {
            st.nextToken();
            int u = (int)st.nval;
            st.nextToken();
            int v = (int)st.nval;
            st.nextToken();
            long w = (int)st.nval;
            graphs[u].add(new edge(v,w));
        }
        long[]dis=new long[n+1];
        for (int i = 1; i <= n; i++) {
            dis[i]=Integer.MAX_VALUE;
        }
        dis[s]=0;
       PriorityQueue<long[]>queue=new PriorityQueue<>(Comparator.comparingLong(longs -> longs[1]));
       // 到哪个节点,距离
       queue.add(new long[]{s,0});
       boolean[]visited=new boolean[n+1];
       while (!queue.isEmpty()){
           long[] arr = queue.poll();
           int node = (int)arr[0];
           long curDis = arr[1];

           if (visited[node])continue;
           visited[node]=true;

           // 遍历该节点的邻接点 然后更新start到其邻接点的距离
           for (edge edge : graphs[node]) {
               int nextNode = edge.to;
               long nextDis = edge.weight;
               if (dis[nextNode]>curDis+nextDis){
                   dis[nextNode]=curDis+nextDis;
                   queue.offer(new long[]{nextNode,dis[nextNode]});
               }
           }
       }
        for (int i = 1; i <=n ; i++) {
            System.out.print(dis[i]+" ");
        }
    }

    public static class edge{
        int to;
        long weight;

        public edge(int to, long weight) {
            this.to = to;
            this.weight = weight;
        }
    }

}

 五 BellmanFord算法

BellmanFord可以处理负边,Dijkstra不可以

BellmanFord,即贝尔曼-福特算法(Bellman–Ford algorithm),是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法。以下是对BellmanFord算法的详细解释:

一、基本原理

贝尔曼-福特算法通过多次迭代(松弛操作)来逐步逼近从原点到图中所有其他节点的最短路径。算法的核心在于对每一条边进行多次检查,看是否存在通过这条边可以缩短从源点到某个节点的路径的情况。

二、算法步骤

  1. 初始化:将所有节点到源点的距离初始化为无穷大(除了原点到自身的距离为0)。
  2. 松弛操作:对图中的每一条边进行多次(通常是V-1次,V是节点数)松弛操作。在每次松弛操作中,对于每一条边(u, v),如果通过u到达v的路径比当前已知的从源点到v的路径更短,则更新v的距离。
  3. 检查负权环:在完成V-1次松弛操作后,再进行一次额外的松弛操作。如果在这次操作中还能更新某个节点的距离,则说明图中存在负权环,因为负权环允许无限次地减少路径的总权重。

三、算法特点

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

package TuLun;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class P3385 {
    public static void main(String[] args) throws IOException {
        StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        st.nextToken();
        int t = (int) st.nval;
        for (int i = 0; i < t; i++) {
            st.nextToken();
            int n= (int) st.nval;
            st.nextToken();
            int m= (int) st.nval;
            List<Edge>[]graphs=new List[n+1];
            for (int j = 1; j <=n ; j++) {
                graphs[j]=new ArrayList<>();
            }
            for (int j = 0; j < m; j++) {
                st.nextToken();
                int u= (int) st.nval;
                st.nextToken();
                int v= (int) st.nval;
                st.nextToken();
                int w= (int) st.nval;
                if (w>=0){
                    graphs[u].add(new Edge(v,w));
                    graphs[v].add(new Edge(u,w));
                }else{
                    graphs[u].add(new Edge(v,w));
                }
            }
            long[]dis=new long[n+1];
            Arrays.fill(dis,Integer.MAX_VALUE);
            dis[1]=0;
            for (int j = 0; j < n-1; j++) {
                for (int k = 1; k <=n; k++) {
                    for (Edge edge : graphs[k]) {
                        int nextNode = edge.to;
                        long nextDis=edge.weight;
                        if (dis[k]!=Integer.MAX_VALUE  && dis[nextNode]>dis[k]+nextDis){
                            dis[nextNode]=dis[k]+nextDis;
                        }
                    }
                }
            }
            boolean flag=false;
            for (int k = 1; k <=n; k++) {
                for (Edge edge : graphs[k]) {
                    int nextNode = edge.to;
                    long nextDis=edge.weight;
                    if (dis[k]!=Integer.MAX_VALUE && dis[nextNode]>dis[k]+nextDis){
                        flag=true;
                        break;
                    }
                }
                if (flag)break;
            }
            if (flag){
                System.out.println("YES");
            }else{
                System.out.println("NO");
            }
        }

    }
    public static class Edge{
        int to;
        long weight;

        public Edge(int to, long weight) {
            this.to = to;
            this.weight = weight;
        }
    }
}

 六 Floyd-Warshall算法

FloydWarshall,即Floyd-Warshall算法,是一种用于求解图中求解任意两点间的最短路径的动态规划算法。以下是对Floyd-Warshall算法的详细解释:

一、算法概述

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

二、算法步骤

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

三、算法实现

Floyd-Warshall算法的实现通常使用嵌套的三层循环来遍历所有节点对和中间节点。

检验是否有环的方法:

  • 在dist中判断对角线上是否存在负数
  • 对角线是自己到自己最小应该为0
  • 绕了一圈回到自己<0
public class FloydWarshall {
    public static void main(String[] args) {
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");

        v1.edges = List.of(new Edge(v3,-2));
        v2.edges = List.of(new Edge(v1,4),new Edge(v3,3));
        v3.edges = List.of(new Edge(v4,2));
        v4.edges = List.of(new Edge(v2,-1));

        List<Vertex> graph = List.of(v1,v2,v3,v4);
        floydWarshall(graph);

    }

    /**
     *      v1 v2 v3 v4
     *   v1
     *   v2
     *   v3
     *   v4
     * @param graph
     */
    public static void floydWarshall(List<Vertex> graph){
        long[][] dist = new long[graph.size()][graph.size()];
        Vertex[][] prev = new Vertex[graph.size()][graph.size()];  // 路径
        // 1.初始化距离
        for (int i = 0; i < graph.size(); i++) {
            // i起点 j终点
            Map<Vertex, Integer> map = graph.get(i).edges.stream().collect(Collectors.toMap(k -> k.linked, v -> v.weight));
            for (int j = 0; j < graph.size(); j++) {
                if (i == j){
                    // 自己到自己距离为0
                    dist[i][j] = 0;
                }else{
                    // 判断起点是否和终点连接 不连接则距离无穷大
                    Integer orDefault = map.getOrDefault(graph.get(j), Integer.MAX_VALUE);
                    dist[i][j] = orDefault;
                    // i到j j的上一个应该为i
                    prev[i][j] = graph.get(i);
                }
            }
        }
        // 2.借助中间点k从起点i到达结束点j 或者也可以从起点i出发借助中间点k到结束点j i,j,k都是一轮循环
        // 2.1有几个点则进行几轮借助 每个点都去借助一轮别的点
        for (int k = 0; k < graph.size(); k++) {
            // 2.2 i 起点
            for (int i = 0; i < graph.size(); i++) { // 多源的 所以起点也要换 每个顶点当一次起点
                // 2.3 j终点
                for (int j = 0; j < graph.size(); j++) {
                    if (dist[i][k]!=Integer.MAX_VALUE && dist[k][j]!=Integer.MAX_VALUE
                            && dist[i][k]+dist[k][j]<dist[i][j]){
                        dist[i][j] =dist[i][k]+dist[k][j];
                        prev[i][j]=graph.get(k);
                    }
                }
            }
        }

        // 3.打印结果
//        for (int i = 0; i < graph.size(); i++) {
//            for (int j = 0; j < graph.size(); j++) {
//                System.out.print(dist[i][j]+" ");
//            }
//            System.out.println();
//        }
        for (int i = 0; i < graph.size(); i++) {
            for (int j = 0; j < graph.size(); j++) {
                path(prev,graph,i,j);
            }

        }

    }

    private static void path(Vertex[][] prev, List<Vertex> graph,int i,int j) {
        LinkedList<String> stack = new LinkedList<>();
        System.out.print("["+graph.get(i).name+","+graph.get(j).name+"]  " );
        stack.push(graph.get(j).name);
        while(i!=j){
            Vertex vertex = prev[i][j];
            stack.push(vertex.name);
            j = graph.indexOf(vertex);
        }
        System.out.println(String.join("->", stack));




    }


}

 七 最小生成树算法

一、Prim算法

Prim算法是一种用于求解最小生成树(Minimum Spanning Tree, MST)的经典算法。最小生成树是指在一个加权无向图中,找到一棵包含所有顶点边权值之和最小的树。Prim算法的基本思想是从图的某一顶点开始,逐步扩展生成树,每次选择一条连接生成树顶点集合未连接顶点集合中权值最小的边,直到生成树包含所有顶点。

public static void main(String[] args) {
    Vertex v1 = new Vertex("v1");
    Vertex v2 = new Vertex("v2");
    Vertex v3 = new Vertex("v3");
    Vertex v4 = new Vertex("v4");
    Vertex v5 = new Vertex("v5");
    Vertex v6 = new Vertex("v6");

    v1.edges = List.of(new Edge(v3,9),new Edge(v2,7),new Edge(v6,14));
    v2.edges = List.of(new Edge(v4,15));
    v3.edges = List.of(new Edge(v4,11),new Edge(v6,2));
    v4.edges = List.of(new Edge(v5,6));
    v5.edges = List.of();
    v6.edges = List.of(new Edge(v5,9));


    List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6);
    // 1.创建一个优先级队列,按照距离进行排序,默认为小顶堆
    PriorityQueue<Vertex> queue = new PriorityQueue<>(Comparator.comparingInt(v->v.distance));
    // 2.为每个顶点初始化距离
    v1.distance = 0;
    for (Vertex vertex : graph) {
        if (vertex != v1) {
            vertex.distance = Integer.MAX_VALUE;
        }
    }
    for (Vertex vertex : graph) {
        queue.offer(vertex);
    }

    // 3.每次选择距离最小的顶点,作为新的当前顶点
    while (!queue.isEmpty()) {
        // 3.1 取出距离最小的顶点
        Vertex current = queue.peek();
        // 3.2 更新距离
        if (!current.visited){
            updateDistance(current, queue);
            current.visited = true;
        }
        // 3.3 从队列中删除该节点
        queue.poll();
    }

    // 4.打印路径
    for (Vertex vertex : graph) {
        System.out.println(vertex.name +" "+ vertex.distance+" "+ (vertex.prev == null ? "null" : vertex.prev.name));
    }

}
public static void updateDistance(Vertex current, PriorityQueue<Vertex> queue) {
    // 3.3 遍历当前顶点的邻居
    for (Edge edge : current.edges) {
        Vertex n = edge.linked;
        if (!n.visited){
            int dist=edge.weight;
            // 3.4 如果距离小于邻居的距离,则更新邻居的距离
            if (dist < n.distance) {
                n.distance = dist;
                // 更新该节点的距离
                queue.offer(n);
                n.prev = current;
            }
        }


    }
}

二、 kruskal算法

Kruskal算法,即克鲁斯卡尔算法,是一种用于在加权连通图中寻找最小生成树的算法。以下是对Kruskal算法的详细解释:

一、算法思想

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

二、具体步骤
  1. 从加权图中找出所有的边,初始时所有边都不属于最小生成树。
  2. 将所有的边按照权值从小到大进行排序。
  3. 从排序后的边中依次取出边,并判断该边及其连接的两个顶点加入最小生成树是否会形成环路。
    • 若形成环路,则跳过此边,继续取下一条边进行判断。
    • 若不形成环路,则将该边及其连接的两个顶点加入最小生成树。
  1. 重复上述步骤,直至所有顶点均连接在一起,并且没有形成环路时,最小生成树就找到了。
三、核心问题
  1. 如何对图的所有边按照权值大小进行排序?
    • 应对策略:采用排序算法(如快速排序、归并排序等)对图的所有边按照权值大小进行排序。
  1. 如何判断边及其顶点加入最小生成树是否会形成回路?
    • 应对策略:可以使用并查集(Union Find)数据结构来判断。在将边加入最小生成树之前,先判断该边的两个顶点是否已经在同一个集合中(即是否已经连通)。若已经连通,则加入该边会形成回路;若未连通,则可以将该边加入最小生成树,并将两个顶点所在的集合合并。
四、算法特点
  1. Kruskal算法是将边作为操作对象,当加权图的边越多,要处理的边也越多,则算法的时间复杂度就越高;而顶点的数量对算法的时间复杂度无影响。因此,Kruskal算法适合处理稀疏图(边较少的图)。
  2. Kruskal算法的时间复杂度主要取决于排序操作,一般为O(mlogm),其中m为边的数量。
五、应用场景

Kruskal算法广泛应用于网络设计、交通规划、电路布线等领域中,用于求解最小生成树问题。例如,在构建通信网络时,需要选择权值(如距离、成本等)最小的边来连接各个节点,以保证网络的连通性和最小成本。

综上所述,Kruskal算法是一种高效、实用的求解最小生成树的算法,特别适用于处理稀疏图的情况。

static void kruskal(int size, PriorityQueue<Edge> queue){
    List<Edge> edges = new ArrayList<>();
    DisjoinSet set=new DisjoinSet(size);
    while (!queue.isEmpty()){
        Edge poll = queue.poll();
        int i = set.find(poll.start);// 找到起点的老大
        int j = set.find(poll.end);// 找到终点的老大
        if (i!=j){
            // 老大索引不一致 不是一个老大
            edges.add(poll);
            set.union(i,j);// 相交 给原本的两个老大整个共同的新老大
        }
    }
}
package com.shutu.Graph;

public class DisjoinSet {

    int[] s;

    // 初始化,n个元素的并查集,初始每个元素都是一个集合,自身为老大
    // 索引代表顶点
    // 值代表有关系的顶点
    public DisjoinSet(int n) {
        s = new int[n];
        for (int i = 0; i < n; i++) {
            s[i] = i;
        }
    }

    // 找到老大是谁
    public int find(int x) {
        if (s[x] == x) {
            return x;
        }
        return find(s[x]);
        // 路径压缩 return s[x]=find(s[x]);
        
    }

    // 是让两个集合“相交”,即选出新老大,x、y是原老大索引
    public void union(int x, int y) {
        s[y]=x;
    }




}

初始图

连接一些权重比较小的边 给每个点找到自己的老大 两个顶点老大相同则说明已相连

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