普里姆(Prim)算法之加权连通图的最小生成树问题

1.图的几个概念

(1)连通图:在无向图中,若任意两个顶点vi与vj都有路径相通,则称该无向图为连通图
(2)强连通图:在有向图中,若任意两个顶点vi与vj都有路径相通,则称该有向图为强连通图
(3)连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数值,称为,权代表着连接两个顶点的代价,称这种连通图叫做连通网
(4)生成树:一个连通图的生成树是指一个连通子图,它含有图中全部 个顶点,但只有足以构成一棵树的 n-1 条边。一棵有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环
(5)最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树(Minimum Cost Spanning Tree),简称 MST

è¿éåå¾çæè¿°

2.普里姆(Prim)算法的概述

用于求解图的最小生成树
贪心策略:每次都选择权值最小的边作为最小生成树的边

3.普里姆(Prim)算法的基本思路

(1) 设 G=(V,E)是连通网,T=(U,D)是最小生成树,V,U 是顶点集合,E,D 是边的集合
(2) 若从顶点 i 开始构造最小生成树,则从集合 V 中取出顶点 i 放入集合 U 中,标记顶点 i 为已被访问,即 visited[i] = true
(3) 若集合 U 中顶点 ui 与集合 V-U(差集) 中的顶点 vj 之间存在边,则寻找这些边中权值最小的边,但不能构成回路,
将顶点 vj 加入集合 U 中,将边(ui,vj)加入集合 D 中,标记 visited[vj] = true
(4) 重复步骤(3),直到 U 与 V 相等,即所有顶点都被标记为访问过,此时 D 中有 n-1 条边,得到最小生成树

注意在使用普里姆算法(Prim)构造最小生成树的过程中,最小生成树必定不会构成回路,因为顶点 ui 是从已经被访问过的顶点集合 U 中获取到的顶点,顶点 vj 是从未被访问过的顶点集合 V-U(差集) 中获取到的顶点,(ui,vj)构成的边加入到最小生成树中必定不会构成回路,但是使用 克鲁斯卡尔算法(Kruskal) 构造最小生成树时,添加边到最小生成树中,存在构成回路的可能,因此要判断加入的边是否会构成回路,使用 并查集 判断是否会构成回路

4.普里姆(Prim)算法的代码实现

普里姆(Prim)算法之加权连通图的最小生成树问题_第1张图片

 

package com.zzb.algorithm.prim;

import java.io.Serializable;
import java.util.Arrays;

/**
 * @Auther: Administrator
 * @Date: 2020/3/31 12:56
 * @Description: 普里姆算法:求加权连通图的最小生成树的算法
 * 贪心策略:每次都选择权值最小的边作为最小生成树的边
 */
public class Prim {
    public static void main(String[] args) {
        // 顶点值数组
        String[] vertexArray = {"A", "B", "C", "D", "E", "F", "G"};
        // 邻接矩阵
        final int N = Integer.MAX_VALUE/2; // 表示不可以连接
        int[][] matrix = new int[vertexArray.length][vertexArray.length];
        matrix[0]=new int[]{N,5,7,N,N,N,2};
        matrix[1]=new int[]{5,N,N,9,N,N,3};
        matrix[2]=new int[]{7,N,N,N,8,N,N};
        matrix[3]=new int[]{N,9,N,N,N,4,N};
        matrix[4]=new int[]{N,N,8,N,N,5,4};
        matrix[5]=new int[]{N,N,N,4,5,N,6};
        matrix[6]=new int[]{2,3,N,N,4,6,N};
        // 创建图对象
        Graph graph = new Graph(vertexArray, matrix);
        // 构造最小生成树
        Graph mst = graph.createMST("G");
        int[][] edgeArray = mst.getEdgeArray();
        // 总权值
        int sum = 0;
        for(int i = 0 ; i < edgeArray.length; i++) {
            for(int j = i+1; j < edgeArray.length; j++) {
                if(edgeArray[i][j] != Integer.MAX_VALUE/2) {
                    System.out.println("边 <" + mst.getVertexArray()[i] + "," + mst.getVertexArray()[j] + "> " + "权值 " + edgeArray[i][j]);
                    sum += edgeArray[i][j];
                }
            }
        }
        System.out.println("总权值 == " + sum);
        /*
        边  权值 7
        边  权值 2
        边  权值 3
        边  权值 4
        边  权值 5
        边  权值 4
        总权值 == 25*/
    }
}

/**
 * 图类
 */
class Graph implements Serializable {
    private static final long serialVersionUID = 8866902589112221411L;

    // 存储图中各个顶点的集合
    private String[] vertexArray;
    // 存储图中各条边的邻接矩阵
    private int[][] edgeArray;
    // 图中某个顶点是否被访问过,初始化为都未被访问
    private boolean[] visited;

    /**
     * 构造器初始化
     *
     * @param vertexArray 顶点
     * @param edgeArray 连接矩阵
     */
    public Graph(String[] vertexArray, int[][] edgeArray) {
        this.vertexArray = vertexArray;
        this.edgeArray = edgeArray;
        // 初始化为都未被访问
        this.visited = new boolean[vertexArray.length];
    }

    public Graph() {

    }

    /**
     * 构造最小生成树
     *
     * @param initVertex 从哪个顶点开始构造最小生成树
     * @return 以图的方式保存最小生成树
     */
    public Graph createMST(String initVertex) {
        // 普里姆算法
        Graph mst = prim(this, initVertex);
        return mst;
    }

    /**
     * 普里姆算法
     * 贪心策略:每次都选择权值最小的边作为最小生成树的边
     * 
     * @param graph 由哪个图的哪个顶点开始来构造最小生成树
     * @param vertex 由哪个图的哪个顶点开始来构造最小生成树
     * @return 以图的方式保存最小生成树
     */
    private Graph prim(Graph graph, String vertex) {
        // 初始化最小生成树对应的图不连通
        Graph mst = new Graph(); // 最小生成树
        mst.setVertexArray(graph.getVertexArray()); // 顶点
        int[][] edgeArray = new int[graph.getVertexArray().length][graph.getVertexArray().length];
        mst.setEdgeArray(edgeArray); // 边
        for(int i = 0; i < mst.getVertexArray().length; i++) {
            // Integer.MAX_VALUE/2 表示不连通
            Arrays.fill(mst.getEdgeArray()[i], Integer.MAX_VALUE/2);
        }

        // 设置顶点 vertex 已被访问
        int indexOfVertex = graph.getIndexOfVertex(vertex);
        graph.getVisited()[indexOfVertex] = true;

        // 默认两点之间的最小值(对应图graph中两点之间不连通的值 Integer.MAX_VALUE/2),实时更新
        int min = Integer.MAX_VALUE/2;
        // 被访问过的顶点索引
        int index1 = -1;
        // 未被访问的顶点索引
        int index2 = -1;
        int length = graph.vertexArray.length;
        for(int k = 1; k < length; k++) { // 构成最小生成树所需的循环次数
            // 在每一次构造最小生成树的过程中,已经被访问过的所有顶点中,哪一个顶点 与 未被访问的顶点 距离最短,
            // 然后作为最小生成树的一条边
            for(int i = 0; i < length; i++) { // i 代表顶点集合中已经被访问过的顶点,遍历判断确定是否被访问过
                for(int j = 0; j < length; j++) { // j 代表顶点集合中未被访问的顶点,遍历判断确定是否未被访问
                    if(graph.getVisited()[i] && !graph.getVisited()[j] && graph.getEdgeArray()[i][j] < min) {
                        min = graph.getEdgeArray()[i][j];
                        index1 = i;
                        index2 = j;
                    }
                }
            }
            // 设置顶点已被访问
            graph.getVisited()[index2] = true;
            // 构成最小生成树的一条边(因为是无向图)
            mst.getEdgeArray()[index1][index2] = min;
            mst.getEdgeArray()[index2][index1] = min;
            // 重置最小值,进行下一轮最小权值边的选择
            min = Integer.MAX_VALUE/2;
        }
        // 最小生成树
        return mst;
    }

    /**
     * 获取各个定点所对应的索引
     *
     * @param vertex 顶点所在集合的值
     * @return 获取各个顶点所对应的索引
     */
    public int getIndexOfVertex(String vertex) {
        for(int i = 0; i < this.vertexArray.length; i++) {
            if(vertex.equals(this.vertexArray[i])) {
                return i;
            }
        }
        return -1;
    }

    public String[] getVertexArray() {
        return vertexArray;
    }

    public void setVertexArray(String[] vertexArray) {
        this.vertexArray = vertexArray;
    }

    public int[][] getEdgeArray() {
        return edgeArray;
    }

    public void setEdgeArray(int[][] edgeArray) {
        this.edgeArray = edgeArray;
    }

    public boolean[] getVisited() {
        return visited;
    }

    public void setVisited(boolean[] visited) {
        this.visited = visited;
    }
}

 

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