仔细的把图论全部过一遍。
一:最小生成树
图中有一个好玩的东西叫做生成树,就是用边来把所有的顶点联通起来,前提条件是最后形成的联通图中不能存在回路,所以就形成这样一个
推理:假设图中的顶点有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 }