• 经典算法题每日演练——第十四题 Prim算法


            图论在数据结构中是非常有趣而复杂的,作为web码农的我,在实际开发中一直没有找到它的使用场景,不像树那样的频繁使用,不过还是准备

    仔细的把图论全部过一遍。

    一:最小生成树

           图中有一个好玩的东西叫做生成树,就是用边来把所有的顶点联通起来,前提条件是最后形成的联通图中不能存在回路,所以就形成这样一个

    推理:假设图中的顶点有n个,则生成树的边有n-1条,多一条会存在回路,少一路则不能把所有顶点联通起来,如果非要在图中加上权重,则生成树

    中权重最小的叫做最小生成树。

    对于上面这个带权无向图来说,它的生成树有多个,同样最小生成树也有多个,因为我们比的是权重的大小。

    二:Prim算法

    求最小生成树的算法有很多,常用的是Prim算法和Kruskal算法,为了保证单一职责,我把Kruskal算法放到下一篇,那么Prim算法的思想

    是什么呢?很简单,贪心思想。

    如上图:现有集合M={A,B,C,D,E,F},再设集合N={}。

    第一步:挑选任意节点(比如A),将其加入到N集合,同时剔除M集合的A。

    第二步:寻找A节点权值最小的邻节点(比如F),然后将F加入到N集合,此时N={A,F},同时剔除M集合中的F。

    第三步:寻找{A,F}中的权值最小的邻节点(比如E),然后将E加入到N集合,此时N={A,F,E},同时剔除M集合的E。

    。。。

    最后M集合为{}时,生成树就构建完毕了,是不是非常的简单,这种贪心做法我想大家都能想得到,如果算法配合一个好的数据结构,就会

    如虎添翼。

    三:代码

    1. 图的存储

      图的存储有很多方式,邻接矩阵,邻接表,十字链表等等,当然都有自己的适合场景,下面用邻接矩阵来玩玩,邻接矩阵需要采用两个数组,

    ①. 保存顶点信息的一维数组,

    ②. 保存边信息的二维数组。

     1 public class Graph
     2         {
     3             /// <summary>
     4             /// 顶点个数
     5             /// </summary>
     6             public char[] vertexs;
     7 
     8             /// <summary>
     9             /// 边的条数
    10             /// </summary>
    11             public int[,] edges;
    12 
    13             /// <summary>
    14             /// 顶点个数
    15             /// </summary>
    16             public int vertexsNum;
    17 
    18             /// <summary>
    19             /// 边的个数
    20             /// </summary>
    21             public int edgesNum;
    22         }

     2:矩阵构建

     矩阵构建很简单,这里把上图中的顶点和权的信息保存在矩阵中。

     1 #region 矩阵的构建
     2         /// <summary>
     3         /// 矩阵的构建
     4         /// </summary>
     5         public void Build()
     6         {
     7             //顶点数
     8             graph.vertexsNum = 6;
     9 
    10             //边数
    11             graph.edgesNum = 8;
    12 
    13             graph.vertexs = new char[graph.vertexsNum];
    14 
    15             graph.edges = new int[graph.vertexsNum, graph.vertexsNum];
    16 
    17             //构建二维数组
    18             for (int i = 0; i < graph.vertexsNum; i++)
    19             {
    20                 //顶点
    21                 graph.vertexs[i] = (char)(i + 65);
    22 
    23                 for (int j = 0; j < graph.vertexsNum; j++)
    24                 {
    25                     graph.edges[i, j] = int.MaxValue;
    26                 }
    27             }
    28 
    29             graph.edges[0, 1] = graph.edges[1, 0] = 80;
    30             graph.edges[0, 3] = graph.edges[3, 0] = 100;
    31             graph.edges[0, 5] = graph.edges[5, 0] = 20;
    32             graph.edges[1, 2] = graph.edges[2, 1] = 90;
    33             graph.edges[2, 5] = graph.edges[5, 2] = 70;
    34             graph.edges[3, 2] = graph.edges[2, 3] = 100;
    35             graph.edges[4, 5] = graph.edges[5, 4] = 40;
    36             graph.edges[3, 4] = graph.edges[4, 3] = 60;
    37             graph.edges[2, 3] = graph.edges[3, 2] = 10;
    38         }
    39         #endregion

    3:Prim

    要玩Prim,我们需要两个字典。

    ①:保存当前节点的字典,其中包含该节点的起始边和终边以及权值,用weight=-1来记录当前节点已经访问过,用weight=int.MaxValue表示

          两节点没有边。

    ②:输出节点的字典,存放的就是我们的N集合。

    当然这个复杂度玩高了,为O(N2),寻找N集合的邻边最小权值时,我们可以玩玩AVL或者优先队列来降低复杂度。

     1 #region prim算法
     2         /// <summary>
     3         /// prim算法
     4         /// </summary>
     5         public Dictionary<char, Edge> Prim()
     6         {
     7             Dictionary<char, Edge> dic = new Dictionary<char, Edge>();
     8 
     9             //统计结果
    10             Dictionary<char, Edge> outputDic = new Dictionary<char, Edge>();
    11 
    12             //weight=MaxValue:标识没有边
    13             for (int i = 0; i < graph.vertexsNum; i++)
    14             {
    15                 //起始边
    16                 var startEdge = (char)(i + 65);
    17 
    18                 dic.Add(startEdge, new Edge() { weight = int.MaxValue });
    19             }
    20 
    21             //取字符的开始位置
    22             var index = 65;
    23 
    24             //取当前要使用的字符
    25             var start = (char)(index);
    26 
    27             for (int i = 0; i < graph.vertexsNum; i++)
    28             {
    29                 //标记开始边已使用过
    30                 dic[start].weight = -1;
    31 
    32                 for (int j = 1; j < graph.vertexsNum; j++)
    33                 {
    34                     //获取当前 c 的 邻边
    35                     var end = (char)(j + index);
    36 
    37                     //取当前字符的权重
    38                     var weight = graph.edges[(int)(start) - index, j];
    39 
    40                     if (weight < dic[end].weight)
    41                     {
    42                         dic[end] = new Edge()
    43                         {
    44                             weight = weight,
    45                             startEdge = start,
    46                             endEdge = end
    47                         };
    48                     }
    49                 }
    50 
    51                 var min = int.MaxValue;
    52 
    53                 char minkey = ' ';
    54 
    55                 foreach (var key in dic.Keys)
    56                 {
    57                     //取当前 最小的 key(使用过的除外)
    58                     if (min > dic[key].weight && dic[key].weight != -1)
    59                     {
    60                         min = dic[key].weight;
    61                         minkey = key;
    62                     }
    63                 }
    64 
    65                 start = minkey;
    66 
    67                 //边为顶点减去1
    68                 if (outputDic.Count < graph.vertexsNum - 1 && !outputDic.ContainsKey(minkey))
    69                 {
    70                     outputDic.Add(minkey, new Edge()
    71                     {
    72                         weight = dic[minkey].weight,
    73                         startEdge = dic[minkey].startEdge,
    74                         endEdge = dic[minkey].endEdge
    75                     });
    76                 }
    77             }
    78             return outputDic;
    79         }
    80         #endregion

    4:最后我们来测试一下,看看找出的最小生成树。

     1     public static void Main()
     2         {
     3             MatrixGraph martix = new MatrixGraph();
     4 
     5             martix.Build();
     6 
     7             var dic = martix.Prim();
     8 
     9             Console.WriteLine("最小生成树为:");
    10 
    11             foreach (var key in dic.Keys)
    12             {
    13                 Console.WriteLine("({0},{1})({2})", dic[key].startEdge, dic[key].endEdge, dic[key].weight);
    14             }
    15 
    16             Console.Read();
    17         }

    View Code
      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Diagnostics;
      6 using System.Threading;
      7 using System.IO;
      8 using SupportCenter.Test.ServiceReference2;
      9 using System.Threading.Tasks;
     10 
     11 namespace ConsoleApplication2
     12 {
     13     public class Program
     14     {
     15         public static void Main()
     16         {
     17             MatrixGraph martix = new MatrixGraph();
     18 
     19             martix.Build();
     20 
     21             var dic = martix.Prim();
     22 
     23             Console.WriteLine("最小生成树为:");
     24 
     25             foreach (var key in dic.Keys)
     26             {
     27                 Console.WriteLine("({0},{1})({2})", dic[key].startEdge, dic[key].endEdge, dic[key].weight);
     28             }
     29 
     30             Console.Read();
     31         }
     32     }
     33 
     34     /// <summary>
     35     /// 定义矩阵节点
     36     /// </summary>
     37     public class MatrixGraph
     38     {
     39         Graph graph = new Graph();
     40 
     41         public class Graph
     42         {
     43             /// <summary>
     44             /// 顶点个数
     45             /// </summary>
     46             public char[] vertexs;
     47 
     48             /// <summary>
     49             /// 边的条数
     50             /// </summary>
     51             public int[,] edges;
     52 
     53             /// <summary>
     54             /// 顶点个数
     55             /// </summary>
     56             public int vertexsNum;
     57 
     58             /// <summary>
     59             /// 边的个数
     60             /// </summary>
     61             public int edgesNum;
     62         }
     63 
     64         #region 矩阵的构建
     65         /// <summary>
     66         /// 矩阵的构建
     67         /// </summary>
     68         public void Build()
     69         {
     70             //顶点数
     71             graph.vertexsNum = 6;
     72 
     73             //边数
     74             graph.edgesNum = 8;
     75 
     76             graph.vertexs = new char[graph.vertexsNum];
     77 
     78             graph.edges = new int[graph.vertexsNum, graph.vertexsNum];
     79 
     80             //构建二维数组
     81             for (int i = 0; i < graph.vertexsNum; i++)
     82             {
     83                 //顶点
     84                 graph.vertexs[i] = (char)(i + 65);
     85 
     86                 for (int j = 0; j < graph.vertexsNum; j++)
     87                 {
     88                     graph.edges[i, j] = int.MaxValue;
     89                 }
     90             }
     91 
     92             graph.edges[0, 1] = graph.edges[1, 0] = 80;
     93             graph.edges[0, 3] = graph.edges[3, 0] = 100;
     94             graph.edges[0, 5] = graph.edges[5, 0] = 20;
     95             graph.edges[1, 2] = graph.edges[2, 1] = 90;
     96             graph.edges[2, 5] = graph.edges[5, 2] = 70;
     97             graph.edges[3, 2] = graph.edges[2, 3] = 100;
     98             graph.edges[4, 5] = graph.edges[5, 4] = 40;
     99             graph.edges[3, 4] = graph.edges[4, 3] = 60;
    100             graph.edges[2, 3] = graph.edges[3, 2] = 10;
    101         }
    102         #endregion
    103 
    104         #region 边的信息
    105         /// <summary>
    106         /// 边的信息
    107         /// </summary>
    108         public class Edge
    109         {
    110             //开始边
    111             public char startEdge;
    112 
    113             //结束边
    114             public char endEdge;
    115 
    116             //权重
    117             public int weight;
    118         }
    119         #endregion
    120 
    121         #region prim算法
    122         /// <summary>
    123         /// prim算法
    124         /// </summary>
    125         public Dictionary<char, Edge> Prim()
    126         {
    127             Dictionary<char, Edge> dic = new Dictionary<char, Edge>();
    128 
    129             //统计结果
    130             Dictionary<char, Edge> outputDic = new Dictionary<char, Edge>();
    131 
    132             //weight=MaxValue:标识没有边
    133             for (int i = 0; i < graph.vertexsNum; i++)
    134             {
    135                 //起始边
    136                 var startEdge = (char)(i + 65);
    137 
    138                 dic.Add(startEdge, new Edge() { weight = int.MaxValue });
    139             }
    140 
    141             //取字符的开始位置
    142             var index = 65;
    143 
    144             //取当前要使用的字符
    145             var start = (char)(index);
    146 
    147             for (int i = 0; i < graph.vertexsNum; i++)
    148             {
    149                 //标记开始边已使用过
    150                 dic[start].weight = -1;
    151 
    152                 for (int j = 1; j < graph.vertexsNum; j++)
    153                 {
    154                     //获取当前 c 的 邻边
    155                     var end = (char)(j + index);
    156 
    157                     //取当前字符的权重
    158                     var weight = graph.edges[(int)(start) - index, j];
    159 
    160                     if (weight < dic[end].weight)
    161                     {
    162                         dic[end] = new Edge()
    163                         {
    164                             weight = weight,
    165                             startEdge = start,
    166                             endEdge = end
    167                         };
    168                     }
    169                 }
    170 
    171                 var min = int.MaxValue;
    172 
    173                 char minkey = ' ';
    174 
    175                 foreach (var key in dic.Keys)
    176                 {
    177                     //取当前 最小的 key(使用过的除外)
    178                     if (min > dic[key].weight && dic[key].weight != -1)
    179                     {
    180                         min = dic[key].weight;
    181                         minkey = key;
    182                     }
    183                 }
    184 
    185                 start = minkey;
    186 
    187                 //边为顶点减去1
    188                 if (outputDic.Count < graph.vertexsNum - 1 && !outputDic.ContainsKey(minkey))
    189                 {
    190                     outputDic.Add(minkey, new Edge()
    191                     {
    192                         weight = dic[minkey].weight,
    193                         startEdge = dic[minkey].startEdge,
    194                         endEdge = dic[minkey].endEdge
    195                     });
    196                 }
    197             }
    198             return outputDic;
    199         }
    200         #endregion
    201     }
    202 }
  • 相关阅读:
    【HDU1166】敌兵布阵-单点修改和区间求和
    【HDU1166】敌兵布阵-单点修改和区间求和
    【Ural1028】Stars-线段树和树状数组入门题
    【Ural1028】Stars-线段树和树状数组入门题
    【NOIP2014提高组T3】飞扬的小鸟-完全背包
    【NOIP2014提高组T3】飞扬的小鸟-完全背包
    【POJ2528】Mayor's Posters-线段树+离散化
    【POJ2528】Mayor's Posters-线段树+离散化
    perl use utf8
    encode_utf8 把字符编码成字节 decode_utf8解码UTF-8到字符
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/2815214.html
Copyright © 2020-2023  润新知