这篇我们看看第二种生成树的 Kruskal 演算法,这个演算法的魅力在于我们可以打一下演算法和资料结构的组合拳,很有意思的。
一:思想
若存在 M={0,1,2,3,4,5} 这样 6 个节点,我们知道 Prim 演算法构建生成树是从” 顶点” 这个角度来思考的,然后采用 “贪心思想”
来一步步扩大化,最后形成整体最优解,而 Kruskal 演算法有点意思,它是站在” 边 “这个角度在思考的,首先我有两个集合。
1. 顶点集合 (vertexs):
比如 M 集合中的每个元素都可以认为是一个独根树(是不是想到了并查集?)。
2. 边集合 (edges):
对图中的每条边按照权值大小进行排序。(是不是想到了优先伫列?)
好了,下面该如何操作呢?
首先:我们从 edges 中选出权值最小的一条边来作为生成树的一条边,然后将该边的两个顶点合并为一个新的树。
然后:我们继续从 edges 中选出次小的边作为生成树的第二条边,但是前提就是边的两个顶点一定是属于两个集合中,如果不是
则剔除该边继续选下一条次小边。
最后:经过反复操作,当我们发现 n 个顶点的图中生成树已经有 n-1 边的时候,此时生成树构建完毕。

从图中我们还是很清楚的看到 Kruskal 演算法构建生成树的详细过程,同时我们也看到了” 并查集 “和 “优先伫列 “这两个神器
来加速我们的生成树构建。
 
二:构建
1.Build 方法
这里我灌的是一些测试资料,同时在矩阵构建完毕后,将顶点资讯放入并查集,同时将边的资讯放入优先伫列,方便我们在
做生成树的时候秒杀。

1 #region 矩阵的构建
2 ///

3 /// 矩阵的构建
4 ///

5 public void Build()
6 {
7 //顶点数
8 graph.vertexsNum = 6;
9
10 //边数
11 graph.edgesNum = 8;
12
13 graph.vertexs = new int[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] = i; 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[4, 5] = graph.edges[5, 4] = 40; 35 graph.edges[3, 4] = graph.edges[4, 3] = 60; 36 graph.edges[2, 3] = graph.edges[3, 2] = 10; 37 38 //优先伫列,存放树中的边 39 queue = new PriorityQueue();
40
41 //并查集
42 set = new DisjointSet(graph.vertexs);
43
44 //将对角线读入到优先伫列
45 for (int i = 0; i < graph.vertexsNum; i++) 46 { 47 for (int j = i; j < graph.vertexsNum; j++) 48 { 49 //说明该边有权重 50 if (graph.edges[i, j] != int.MaxValue) 51 { 52 queue.Eequeue(new Edge() 53 { 54 startEdge = i, 55 endEdge = j, 56 weight = graph.edges[i, j] 57 }, graph.edges[i, j]); 58 } 59 } 60 } 61 } 62 #endregion   2:Kruskal 演算法 并查集,优先伫列都有资料了,下面我们只要出队操作就行了,如果边的顶点不在一个集合中,我们将其收集作为最小生成树的一条边, 按著这样的方式,最终生成树构建完毕,怎么样,组合拳打的爽不爽? 1 #region Kruskal 演算法 2 ///

3 /// Kruskal 演算法
4 ///

5 public List Kruskal()
6 {
7 //最后收集到的最小生成树的边
8 List list = new List();
9
10 //回圈伫列
11 while (queue.Count() > 0)
12 {
13 var edge = queue.Dequeue();
14
15 //如果该两点是同一个集合,则剔除该集合
16 if (set.IsSameSet(edge.t.startEdge, edge.t.endEdge))
17 continue;
18
19 list.Add(edge.t);
20
21 //然后将 startEdge 和 endEdge Union 起来,表示一个集合
22 set.Union(edge.t.startEdge, edge.t.endEdge);
23
24 //如果 n 个节点有 n-1 边的时候,此时生成树已经构建完毕,提前退出
25 if (list.Count == graph.vertexsNum – 1)
26 break;
27 }
28
29 return list;
30 }
31 #endregion

最后是总的程式码:
View Code
并查集:

View Code

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace ConsoleApplication2
7 {
8 ///

9 /// 并查集
10 ///

11 public class DisjointSet where T : IComparable
12 {
13 #region 树节点
14 ///

15 /// 树节点
16 ///

17 public class Node
18 {
19 ///

20 /// 父节点
21 ///

22 public T parent;
23
24 ///

25 /// 节点的秩
26 ///

27 public int rank;
28 }
29 #endregion
30
31 Dictionary dic = new Dictionary();
32
33 public DisjointSet(T[] c)
34 {
35 Init(c);
36 }
37
38 #region 做单一集合的初始化操作
39 ///

40 /// 做单一集合的初始化操作
41 ///

42 public void Init(T[] c)
43 {
44 //预设的不想交集合的父节点指向自己
45 for (int i = 0; i < c.Length; i++) 46 { 47 dic.Add(c[i], new Node() 48 { 49 parent = c[i], 50 rank = 0 51 }); 52 } 53 } 54 #endregion 55 56 #region 判断两元素是否属于同一个集合 57 ///

58 /// 判断两元素是否属于同一个集合
59 ///

60 /// 61 /// 62 ///
63 public bool IsSameSet(T root1, T root2)
64 {
65 return Find(root1).CompareTo(Find(root2)) == 0;
66 }
67 #endregion
68
69 #region 查询 x 所属的集合
70 ///

71 /// 查询 x 所属的集合
72 ///

73 /// 74 ///
75 public T Find(T x)
76 {
77 //如果相等,则说明已经到根节点了,返回根节点元素
78 if (dic[x].parent.CompareTo(x) == 0)
79 return x;
80
81 //路径压缩 (回溯的时候赋值,最终的值就是上面返回的”x”,也就是一条路径上全部被修改了)
82 return dic[x].parent = Find(dic[x].parent);
83 }
84 #endregion
85
86 #region 合并两个不相交集合
87 ///

88 /// 合并两个不相交集合
89 ///

90 /// 91 /// 92 ///
93 public void Union(T root1, T root2)
94 {
95 T x1 = Find(root1);
96 T y1 = Find(root2);
97
98 //如果根节点相同则说明是同一个集合
99 if (x1.CompareTo(y1) == 0)
100 return;
101
102 //说明左集合的深度 < 右集合 103 if (dic[x1].rank < dic[y1].rank) 104 { 105 //将左集合指向右集合 106 dic[x1].parent = y1; 107 } 108 else 109 { 110 //如果 秩 相等,则将 y1 并入到 x1 中,并将 x1++ 111 if (dic[x1].rank == dic[y1].rank) 112 dic[x1].rank++; 113 114 dic[y1].parent = x1; 115 } 116 } 117 #endregion 118 } 119 } 优先伫列: 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 9 namespace ConsoleApplication2 10 { 11 public class PriorityQueue where T : class
12 {
13 ///

14 /// 定义一个阵列来存放节点
15 ///

16 private List nodeList = new List();
17
18 #region 堆节点定义
19 ///

20 /// 堆节点定义
21 ///

22 public class HeapNode
23 {
24 ///

25 /// 实体资料
26 ///

27 public T t { get; set; }
28
29 ///

30 /// 优先顺序别 1-10 个级别 (优先顺序别递增)
31 ///

32 public int level { get; set; }
33
34 public HeapNode(T t, int level)
35 {
36 this.t = t;
37 this.level = level;
38 }
39
40 public HeapNode() { }
41 }
42 #endregion
43
44 #region 新增操作
45 ///

46 /// 新增操作
47 ///

48 public void Eequeue(T t, int level = 1)
49 {
50 //将当前节点追加到堆尾
51 nodeList.Add(new HeapNode(t, level));
52
53 //如果只有一个节点,则不需要进行筛操作
54 if (nodeList.Count == 1)
55 return;
56
57 //获取最后一个非叶子节点
58 int parent = nodeList.Count / 2 – 1;
59
60 //堆调整
61 UpHeapAdjust(nodeList, parent);
62 }
63 #endregion
64
65 #region 对堆进行上滤操作,使得满足堆性质
66 ///

67 /// 对堆进行上滤操作,使得满足堆性质
68 ///

69 /// 70 /// 非叶子节点的之后指标(这里要注意:我们
71 /// 的筛操作时针对非叶节点的)
72 /// 73 public void UpHeapAdjust(List nodeList, int parent)
74 {
75 while (parent >= 0)
76 {
77 //当前 index 节点的左孩子
78 var left = 2 * parent + 1;
79
80 //当前 index 节点的右孩子
81 var right = left + 1;
82
83 //parent 子节点中最大的孩子节点,方便于 parent 进行比较
84 //预设为 left 节点
85 var min = left;
86
87 //判断当前节点是否有右孩子
88 if (right < nodeList.Count) 89 { 90 //判断 parent 要比较的最大子节点 91 min = nodeList[left].level < nodeList[right].level ? left : right; 92 } 93 94 //如果parent节点大于它的某个子节点的话,此时筛操作 95 if (nodeList[parent].level > nodeList[min].level)
96 {
97 //子节点和父节点进行交换操作
98 var temp = nodeList[parent];
99 nodeList[parent] = nodeList[min];
100 nodeList[min] = temp;
101
102 //继续进行更上一层的过滤
103 parent = (int)Math.Ceiling(parent / 2d) – 1;
104 }
105 else
106 {
107 break;
108 }
109 }
110 }
111 #endregion
112
113 #region 优先伫列的出队操作
114 ///

115 /// 优先伫列的出队操作
116 ///

117 ///
118 public HeapNode Dequeue()
119 {
120 if (nodeList.Count == 0)
121 return null;
122
123 //出伫列操作,弹出资料头元素
124 var pop = nodeList[0];
125
126 //用尾元素填充头元素
127 nodeList[0] = nodeList[nodeList.Count – 1];
128
129 //删除尾节点
130 nodeList.RemoveAt(nodeList.Count – 1);
131
132 //然后从根节点下滤堆
133 DownHeapAdjust(nodeList, 0);
134
135 return pop;
136 }
137 #endregion
138
139 #region 对堆进行下滤操作,使得满足堆性质
140 ///

141 /// 对堆进行下滤操作,使得满足堆性质
142 ///

143 /// 144 /// 非叶子节点的之后指标(这里要注意:我们
145 /// 的筛操作时针对非叶节点的)
146 /// 147 public void DownHeapAdjust(List nodeList, int parent)
148 {
149 while (2 * parent + 1 < nodeList.Count) 150 { 151 //当前 index 节点的左孩子 152 var left = 2 * parent + 1; 153 154 //当前 index 节点的右孩子 155 var right = left + 1; 156 157 //parent 子节点中最大的孩子节点,方便于 parent 进行比较 158 //预设为 left 节点 159 var min = left; 160 161 //判断当前节点是否有右孩子 162 if (right < nodeList.Count) 163 { 164 //判断 parent 要比较的最大子节点 165 min = nodeList[left].level < nodeList[right].level ? left : right; 166 } 167 168 //如果parent节点小于它的某个子节点的话,此时筛操作 169 if (nodeList[parent].level > nodeList[min].level)
170 {
171 //子节点和父节点进行交换操作
172 var temp = nodeList[parent];
173 nodeList[parent] = nodeList[min];
174 nodeList[min] = temp;
175
176 //继续进行更下一层的过滤
177 parent = min;
178 }
179 else
180 {
181 break;
182 }
183 }
184 }
185 #endregion
186
187 #region 获取元素并下降到指定的 level 级别
188 ///

189 /// 获取元素并下降到指定的 level 级别
190 ///

191 ///
192 public HeapNode GetAndDownPriority(int level)
193 {
194 if (nodeList.Count == 0)
195 return null;
196
197 //获取头元素
198 var pop = nodeList[0];
199
200 //设定指定优先顺序(如果为 MinValue 则为 — 操作)
201 nodeList[0].level = level == int.MinValue ? –nodeList[0].level : level;
202
203 //下滤堆
204 DownHeapAdjust(nodeList, 0);
205
206 return nodeList[0];
207 }
208 #endregion
209
210 #region 获取元素并下降优先顺序
211 ///

212 /// 获取元素并下降优先顺序
213 ///

214 ///
215 public HeapNode GetAndDownPriority()
216 {
217 //下降一个优先顺序
218 return GetAndDownPriority(int.MinValue);
219 }
220 #endregion
221
222 #region 返回当前优先伫列中的元素个数
223 ///

224 /// 返回当前优先伫列中的元素个数
225 ///

226 ///
227 public int Count()
228 {
229 return nodeList.Count;
230 }
231 #endregion
232 }
233 }

文章来自互联网博客网站:http://www.cnblogs.com/huangxincheng/archive/2012/12/17/2821132.html,版权归作者所有。