圖論在資料結構中是非常有趣而複雜的,作為 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 ///
5 ///
6 public char[] vertexs;
7
8 ///
10 ///
11 public int[,] edges;
12
13 ///
15 ///
16 public int vertexsNum;
17
18 ///
20 ///
21 public int edgesNum;
22 }
2:矩陣構建
矩陣構建很簡單,這裏把上圖中的頂點和權的資訊儲存在矩陣中。
1 #region 矩陣的構建
2 ///
4 ///
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 ///
4 ///
5 public Dictionary 37 public class MatrixGraph 46 public char[] vertexs; 51 public int[,] edges; 56 public int vertexsNum; 61 public int edgesNum; 68 public void Build() 108 public class Edge 125 public Dictionary
6 {
7 Dictionary
8
9 //統計結果
10 Dictionary
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 }
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 ///
36 ///
38 {
39 Graph graph = new Graph();
40
41 public class Graph
42 {
43 ///
45 ///
47
48 ///
50 ///
52
53 ///
55 ///
57
58 ///
60 ///
62 }
63
64 #region 矩陣的構建
65 ///
67 ///
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 ///
107 ///
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 ///
124 ///
126 {
127 Dictionary
128
129 //統計結果
130 Dictionary
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 }
文章來自互聯網博客網站,原文連結 http://www.cnblogs.com/huangxincheng/archive/2012/12/12/2815214.html