zoukankan      html  css  js  c++  java
  • 普里姆算法与修路问题

    应用场景-修路问题

     

    看一个应用场景和问题:

     

    1. 有胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通
    2. 各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里
    3. 问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?

    最小生成树

    修路问题本质就是就是最小生成树问题, 先介绍一下最小生成树(Minimum Cost Spanning Tree),简称MST。

    1. 给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树
    2. N个顶点,一定有N-1条边
    3. 包含全部顶点
    4. N-1条边都在图中
      1. 举例说明(如图:)

    求最小生成树的算法主要是普里姆

    算法和克鲁斯卡尔算法

    思路: 将10条边,连接即可,但是总的里程数不是最小.

    正确的思路,就是尽可能的选择少的路线,并且每条路线最小,保证总里程数最少. 

    普里姆算法介绍

    1. 普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
    2. 普利姆的算法如下:
    3. 设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合 
    4. 若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1
    5. 若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1
    6. 重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边
    7. 提示: 单独看步骤很难理解,我们通过代码来讲解,比较好理解.

    普里姆算法最佳实践(修路问题)

    1. 有胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通
    2. 各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里
    3. 问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?

     

    普里姆算法的图解分析

     

    邻接矩阵的关系

    核心代码  不理解就 对着图(邻接矩阵的关系或显示的那个邻接矩阵  其实二者是一样的) 走一遍流程  要理解那三层for循环  和那个if的意思 

     

        //编写prim算法,得到最小生成树
        /**
         * 
         * @param graph 图
         * @param v 表示从图的第几个顶点开始生成'A'->0 'B'->1...
         */
        public void prim(MGraph graph, int v) { //传过来一个图 和从那个结点开始访问
                //visited[] 标记结点(顶点)是否被访问过
                int visited[] = new int[graph.verxs]; //和顶点个数是一样的
                //visited[] 默认元素的值都是0, 表示没有访问过
        //        for(int i =0; i <graph.verxs; i++) {
        //            visited[i] = 0;
        //        }
        
        //把当前这个结点标记为已访问
        visited[v] = 1; //之前输入了结点 就表示已经访问过了
        //h1 和 h2 记录两个顶点的下标
        int h1 = -1;
        int h2 = -1;
        int minWeight = 10000; //最小权   //将 minWeight 初始成一个大数,后面在遍历过程中,会被替换
        for(int k = 1; k < graph.verxs; k++) {//因为有 graph.verxs顶点,普利姆算法结束后,有 graph.verxs-1边
                
                //这两个for 就是A B C D E F G-所有可访问的结点 找一个最小的权
                  //外层 A B C  D E F G 循环结点
                        //内层-所有可访问的结点 找一个最小的权 A [0][1] [0][2][0][3]…一个个比较
                //两层for走完 找到一个权最少的边
                //这个是确定每一次生成的子图 ,和哪个结点的距离最近 
                // 子图   画图就是这样的  A-C  A-G A-B等等  代码中这样的 [0][1] [0][2] [0][3] [0][4] 
                
                //第一次 只有v[0]=1 第一次完了才有v[6]=1所以第二次 就有v[0]=1 v[6]=1 代表 这两个结点 被访问了 
                //不然 那个if 进不去 
                
                for(int i = 0; i < graph.verxs; i++) {// i结点表示被访问过的结点  
                        for(int j = 0; j< graph.verxs;j++) {//j结点表示还没有访问过的结点
                                if(visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
                                        //替换minWeight(寻找已经访问过的结点和未访问过的结点间的权值最小的边)
                                        minWeight = graph.weight[i][j];
                                        h1 = i;
                                        h2 = j;
                                }
                        }
                }
                //找到一条边是最小
                System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);
                //将当前这个结点标记为已经访问
                visited[h2] = 1;
                //minWeight 重新设置为最大值 10000
                minWeight = 10000;
        }
        
        }
        

    修路问题完整代码

        package com.atguigu.prim;
        
        import java.util.Arrays;
        
        public class PrimAlgorithm {
        
                public static void main(String[] args) {
                        //测试看看图是否创建ok
                        char[] data = new char[]{'A','B','C','D','E','F','G'};
                        int verxs = data.length;
                        //邻接矩阵的关系使用二维数组表示,10000这个大数,表示两个点不联通
                        //为啥邻接矩阵是自己输入的?不应该是生成的吗  
                        //这是邻接矩阵的关系  这里面放的就是 那个结点 和那个结点 连通 并且权值是多少
                        int [][]weight=new int[][]{
                    {10000,5,7,10000,10000,10000,2},
                    {5,10000,10000,9,10000,10000,3},
                    {7,10000,10000,10000,8,10000,10000},
                    {10000,9,10000,10000,10000,4,10000},
                    {10000,10000,8,10000,10000,5,4},
                    {10000,10000,10000,4,5,10000,6},
                    {2,3,10000,10000,4,6,10000},};
                    
                //创建MGraph对象
                MGraph graph = new MGraph(verxs);
                //创建一个MinTree对象
                MinTree minTree = new MinTree();
                minTree.createGraph(graph, verxs, data, weight);
                //输出
                minTree.showGraph(graph);
                //测试普利姆算法
                minTree.prim(graph, 1);// 
                }
        
        }
        
        //创建最小生成树->村庄的图
        class MinTree {
                //创建图的邻接矩阵
                /**
                 * 
                 * @param graph 图对象
                 * @param verxs 图对应的顶点个数
                 * @param data 图的各个顶点的值
                 * @param weight 图的邻接矩阵
                 */
                //根据上面的邻接矩阵的关系创建邻接矩阵
                public void createGraph(MGraph graph, int verxs, char data[], int[][] weight) {
                        int i, j;
                        for(i = 0; i < verxs; i++) {//顶点
                                graph.data[i] = data[i];
                                for(j = 0; j < verxs; j++) {
                                        graph.weight[i][j] = weight[i][j];
                                }
                        }
                }
                
                //显示图的邻接矩阵
                public void showGraph(MGraph graph) {
                        for(int[] link: graph.weight) {//如遍历weight[2] 就是第二行 得到的就是第二行的所有值
                                System.out.println(Arrays.toString(link)); //输出的就是那行的所有值
                        }
                }
                
                //编写prim算法,得到最小生成树
                /**
                 * 
                 * @param graph 图
                 * @param v 表示从图的第几个顶点开始生成'A'->0 'B'->1...
                 */
                public void prim(MGraph graph, int v) { //传过来一个图 和从那个结点开始访问
                        //visited[] 标记结点(顶点)是否被访问过
                        int visited[] = new int[graph.verxs]; //和顶点个数是一样的
                        //visited[] 默认元素的值都是0, 表示没有访问过
        //        for(int i =0; i <graph.verxs; i++) {
        //            visited[i] = 0;
        //        }
                
                //把当前这个结点标记为已访问
                visited[v] = 1; //之前输入了结点 就表示已经访问过了
                //h1 和 h2 记录两个顶点的下标
                int h1 = -1;
                int h2 = -1;
                int minWeight = 10000; //最小权   //将 minWeight 初始成一个大数,后面在遍历过程中,会被替换
                for(int k = 1; k < graph.verxs; k++) {//因为有 graph.verxs顶点,普利姆算法结束后,有 graph.verxs-1边
                        
                        //这两个for 就是A B C D E F G-所有可访问的结点 找一个最小的权
                          //外层 A B C  D E F G 循环结点
                                //内层-所有可访问的结点 找一个最小的权 A [0][1] [0][2][0][3]…一个个比较
                        //两层for走完 找到一个权最少的边
                        //这个是确定每一次生成的子图 ,和哪个结点的距离最近 
                        // 子图   画图就是这样的  A-C  A-G A-B等等  代码中这样的 [0][1] [0][2] [0][3] [0][4] 
                        
                        //第一次 只有v[0]=1 第一次完了才有v[6]=1所以第二次 就有v[0]=1 v[6]=1 代表 这两个结点 被访问了 
                        //不然 那个if 进不去 
                        
                        for(int i = 0; i < graph.verxs; i++) {// i结点表示被访问过的结点  
                                for(int j = 0; j< graph.verxs;j++) {//j结点表示还没有访问过的结点
                                        if(visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
                                                //替换minWeight(寻找已经访问过的结点和未访问过的结点间的权值最小的边)
                                                minWeight = graph.weight[i][j];
                                                h1 = i;
                                                h2 = j;
                                        }
                                }
                        }
                        //找到一条边是最小
                        System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);
                        //将当前这个结点标记为已经访问
                        visited[h2] = 1;
                        //minWeight 重新设置为最大值 10000
                        minWeight = 10000;
                }
                
                }
        }
        
        //这个图的类
        class MGraph {
                int verxs; //表示图的节点个数
                char[] data;//存放结点数据 结点名称
                int[][] weight; //存放边,就是我们的邻接矩阵
                
                public MGraph(int verxs) {
                        this.verxs = verxs;
                        data = new char[verxs];
                        weight = new int[verxs][verxs];
                }
        }
        
  • 相关阅读:
    2. Add Two Numbers
    1. Two Sum
    22. Generate Parentheses (backTracking)
    21. Merge Two Sorted Lists
    20. Valid Parentheses (Stack)
    19. Remove Nth Node From End of List
    18. 4Sum (通用算法 nSum)
    17. Letter Combinations of a Phone Number (backtracking)
    LeetCode SQL: Combine Two Tables
    LeetCode SQL:Employees Earning More Than Their Managers
  • 原文地址:https://www.cnblogs.com/cnng/p/12339882.html
Copyright © 2011-2022 走看看