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

来源:互联网 发布:淘宝转链接api 编辑:程序博客网 时间:2024/06/06 01:29
        图论在数据结构中是非常有趣而复杂的,作为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 = end47                         };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                 //边为顶点减去168                 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].endEdge75                     });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         #endregion103 104         #region 边的信息105         /// <summary>106         /// 边的信息107         /// </summary>108         public class Edge109         {110             //开始边111             public char startEdge;112 113             //结束边114             public char endEdge;115 116             //权重117             public int weight;118         }119         #endregion120 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 = end167                         };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                 //边为顶点减去1188                 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].endEdge195                     });196                 }197             }198             return outputDic;199         }200         #endregion201     }202 }

 

0 0
原创粉丝点击