經典算法題每日演練——第十四題 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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM