普里姆 & 克鲁斯卡尔算法

一.简介

连通图:任意2节点之间都有路径相通

最小生成树:最小权重生成树
一个 n 结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边(n-1)。 最小生成树可以用prim(普里姆)算法或kruskal(克鲁斯卡尔)算法求出。

二.实现

普里姆 & 克鲁斯卡尔算法_第1张图片

该图的最小生成树权值和为:19

普里姆 & 克鲁斯卡尔算法_第2张图片

1.普里姆算法

设T为最小生成树集合,V为节点集合,U为还未放入T集合的节点集合(U=V-T)

1.先选取任意节点放入T

2.获取T 与 U 集合中最小权值节点v’,并加入T集合

3.循环2直到集合T中有n-1条边

2.克鲁斯卡尔算法

按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路(不让新选择边的2节点再次被选择)。


package com.vincent;


import java.util.*;

public class Main {
    //定义边
    static  class Edge{
        int vertex1;
        int vertex2;
        int weight;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Edge edge = (Edge) o;
            return vertex1 == edge.vertex1 &&
                    vertex2 == edge.vertex2;
        }

        @Override
        public int hashCode() {
            return Objects.hash(vertex1, vertex2);
        }

        @Override
        public String toString() {
            return String.format("(%d,%d,%d)",vertex1,vertex2,weight);
        }
    }

    public static void main(String[] args) throws Exception {
        char[] datas = {'a','b','c','d','e','f'};
        int[][] graph = new int[datas.length][datas.length];
        graph[0][1] = 2;
        graph[0][2] = 3;
        graph[0][5] = 5;

        graph[1][0] = 2;
        graph[1][3] = 4;

        graph[2][0] = 3;
        graph[2][4] = 5;

        graph[3][1] = 4;
        graph[3][5] = 6;

        graph[4][2] = 5;
        graph[4][5] = 8;

        graph[5][3] = 6;
        graph[5][4] = 8;
        graph[5][0] = 5;
        for(int i=0;i<datas.length;i++){
            System.out.println(Arrays.toString(graph[i]));
        }

        System.out.println("------------------");
        algOfPrim(graph,datas,0);

        System.out.println("------------------");
        algOfKruskal(graph,datas);
    }

    /**
     * 普利姆算法
     * @param graph     图的邻接矩阵存储,值表示节点之间的权值
     * @param datas     对应节点的值
     * @param from      最小生成树的开始节点索引
     */
    public static void algOfPrim(int[][] graph,char[] datas,int from){
        //记录节点是否访问过
        int[] book = new int[datas.length];

        //保存节点信息
        List<Integer> rst = new ArrayList<>();

        book[from] = 1;//标记为已访问
        rst.add(from);

        //n个节点的最小生成树有n-1条边
        for(int i=1;i<datas.length;i++){
            int weight = Integer.MAX_VALUE;

            //记录已选择索引,记录未选择索引,选择索引节点与未选择索引节点组合是当前子集中权值最小的
            int minSel = -1,minUnsel = -1;

            for(int j=0;j<rst.size();j++){
                for(int k=0;k<datas.length;k++){
                    if(book[k] == 0 && graph[rst.get(j)][k] != 0 && graph[rst.get(j)][k] < weight){
                        weight = graph[rst.get(j)][k];
                        minSel = j;
                        minUnsel = k;
                    }
                }
            }
            rst.add(minUnsel);
            book[minUnsel] = 1;
            System.out.printf("%c->%c weight=%d\n",datas[minSel],datas[minUnsel],graph[minSel][minUnsel]);
        }
    }

    /**
     * 克鲁斯卡尔算法
     * @param graph
     * @param datas
     */
    public static void algOfKruskal(int[][] graph,char[] datas){
        //图转化为Edge结构
        List<Edge> edges = new ArrayList<>();
        for(int i=0;i<graph.length;i++){
            for(int j=i+1;j<graph[i].length;j++){
                if(graph[i][j] != 0) {
                    Edge edge = new Edge();
                    edge.vertex1 = i;
                    edge.vertex2 = j;
                    edge.weight = graph[i][j];

                    edges.add(edge);
                }
            }
        }
        Collections.sort(edges,(a,b)->{
            return a.weight-b.weight;
        });
        System.out.println(edges);

        //记录选择的节点
        Set<Integer> bookSet = new HashSet<>();
        List<Edge> rst = new ArrayList<>();
        for(int i=0;i<edges.size();i++){
            Edge edge = edges.get(i);
            //判断是否有回路(当前边的节点已经包含在记录集合)
            if(bookSet.contains(edge.vertex1) && bookSet.contains(edge.vertex2)){
                continue;
            }
            bookSet.add(edge.vertex1);
            bookSet.add(edge.vertex2);
            rst.add(edge);
        }
        System.out.println(rst);
    }
}

效果:

普里姆 & 克鲁斯卡尔算法_第3张图片

三.总结

普里姆算法/克鲁斯卡尔算法是解决图的最小联通的有效方法

你可能感兴趣的:(数据结构与算法)