原文:经典算法题每日演练——第十六题 Kruskal算法

这篇我们看看第二种生成树的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 /// <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 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<Edge>();
40
41 //并查集
42 set = new DisjointSet<int>(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 /// <summary>
3 /// Kruskal算法
4 /// </summary>
5 public List<Edge> Kruskal()
6 {
7 //最后收集到的最小生成树的边
8 List<Edge> list = new List<Edge>();
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

最后是总的代码:

  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 System.Threading.Tasks;
9
10 namespace ConsoleApplication2
11 {
12 public class Program
13 {
14 public static void Main()
15 {
16 MatrixGraph graph = new MatrixGraph();
17
18 graph.Build();
19
20 var edges = graph.Kruskal();
21
22 foreach (var edge in edges)
23 {
24 Console.WriteLine("({0},{1})({2})", edge.startEdge, edge.endEdge, edge.weight);
25 }
26
27 Console.Read();
28 }
29 }
30
31 #region 定义矩阵节点
32 /// <summary>
33 /// 定义矩阵节点
34 /// </summary>
35 public class MatrixGraph
36 {
37 Graph graph = new Graph();
38
39 PriorityQueue<Edge> queue;
40
41 DisjointSet<int> set;
42
43 public class Graph
44 {
45 /// <summary>
46 /// 顶点信息
47 /// </summary>
48 public int[] vertexs;
49
50 /// <summary>
51 /// 边的条数
52 /// </summary>
53 public int[,] edges;
54
55 /// <summary>
56 /// 顶点个数
57 /// </summary>
58 public int vertexsNum;
59
60 /// <summary>
61 /// 边的个数
62 /// </summary>
63 public int edgesNum;
64 }
65
66 #region 矩阵的构建
67 /// <summary>
68 /// 矩阵的构建
69 /// </summary>
70 public void Build()
71 {
72 //顶点数
73 graph.vertexsNum = 6;
74
75 //边数
76 graph.edgesNum = 8;
77
78 graph.vertexs = new int[graph.vertexsNum];
79
80 graph.edges = new int[graph.vertexsNum, graph.vertexsNum];
81
82 //构建二维数组
83 for (int i = 0; i < graph.vertexsNum; i++)
84 {
85 //顶点
86 graph.vertexs[i] = i;
87
88 for (int j = 0; j < graph.vertexsNum; j++)
89 {
90 graph.edges[i, j] = int.MaxValue;
91 }
92 }
93
94 graph.edges[0, 1] = graph.edges[1, 0] = 80;
95 graph.edges[0, 3] = graph.edges[3, 0] = 100;
96 graph.edges[0, 5] = graph.edges[5, 0] = 20;
97 graph.edges[1, 2] = graph.edges[2, 1] = 90;
98 graph.edges[2, 5] = graph.edges[5, 2] = 70;
99 graph.edges[4, 5] = graph.edges[5, 4] = 40;
100 graph.edges[3, 4] = graph.edges[4, 3] = 60;
101 graph.edges[2, 3] = graph.edges[3, 2] = 10;
102
103 //优先队列,存放树中的边
104 queue = new PriorityQueue<Edge>();
105
106 //并查集
107 set = new DisjointSet<int>(graph.vertexs);
108
109 //将对角线读入到优先队列
110 for (int i = 0; i < graph.vertexsNum; i++)
111 {
112 for (int j = i; j < graph.vertexsNum; j++)
113 {
114 //说明该边有权重
115 if (graph.edges[i, j] != int.MaxValue)
116 {
117 queue.Eequeue(new Edge()
118 {
119 startEdge = i,
120 endEdge = j,
121 weight = graph.edges[i, j]
122 }, graph.edges[i, j]);
123 }
124 }
125 }
126 }
127 #endregion
128
129 #region 边的信息
130 /// <summary>
131 /// 边的信息
132 /// </summary>
133 public class Edge
134 {
135 //开始边
136 public int startEdge;
137
138 //结束边
139 public int endEdge;
140
141 //权重
142 public int weight;
143 }
144 #endregion
145
146 #region Kruskal算法
147 /// <summary>
148 /// Kruskal算法
149 /// </summary>
150 public List<Edge> Kruskal()
151 {
152 //最后收集到的最小生成树的边
153 List<Edge> list = new List<Edge>();
154
155 //循环队列
156 while (queue.Count() > 0)
157 {
158 var edge = queue.Dequeue();
159
160 //如果该两点是同一个集合,则剔除该集合
161 if (set.IsSameSet(edge.t.startEdge, edge.t.endEdge))
162 continue;
163
164 list.Add(edge.t);
165
166 //然后将startEdge 和 endEdge Union起来,表示一个集合
167 set.Union(edge.t.startEdge, edge.t.endEdge);
168
169 //如果n个节点有n-1边的时候,此时生成树已经构建完毕,提前退出
170 if (list.Count == graph.vertexsNum - 1)
171 break;
172 }
173
174 return list;
175 }
176 #endregion
177 }
178 #endregion
179 }

并查集:

  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace ConsoleApplication2
7 {
8 /// <summary>
9 /// 并查集
10 /// </summary>
11 public class DisjointSet<T> where T : IComparable
12 {
13 #region 树节点
14 /// <summary>
15 /// 树节点
16 /// </summary>
17 public class Node
18 {
19 /// <summary>
20 /// 父节点
21 /// </summary>
22 public T parent;
23
24 /// <summary>
25 /// 节点的秩
26 /// </summary>
27 public int rank;
28 }
29 #endregion
30
31 Dictionary<T, Node> dic = new Dictionary<T, Node>();
32
33 public DisjointSet(T[] c)
34 {
35 Init(c);
36 }
37
38 #region 做单一集合的初始化操作
39 /// <summary>
40 /// 做单一集合的初始化操作
41 /// </summary>
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 /// <summary>
58 /// 判断两元素是否属于同一个集合
59 /// </summary>
60 /// <param name="root1"></param>
61 /// <param name="root2"></param>
62 /// <returns></returns>
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 /// <summary>
71 /// 查找x所属的集合
72 /// </summary>
73 /// <param name="x"></param>
74 /// <returns></returns>
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 /// <summary>
88 /// 合并两个不相交集合
89 /// </summary>
90 /// <param name="root1"></param>
91 /// <param name="root2"></param>
92 /// <returns></returns>
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 }

优先队列:

  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<T> where T : class
12 {
13 /// <summary>
14 /// 定义一个数组来存放节点
15 /// </summary>
16 private List<HeapNode> nodeList = new List<HeapNode>();
17
18 #region 堆节点定义
19 /// <summary>
20 /// 堆节点定义
21 /// </summary>
22 public class HeapNode
23 {
24 /// <summary>
25 /// 实体数据
26 /// </summary>
27 public T t { get; set; }
28
29 /// <summary>
30 /// 优先级别 1-10个级别 (优先级别递增)
31 /// </summary>
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 /// <summary>
46 /// 添加操作
47 /// </summary>
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 /// <summary>
67 /// 对堆进行上滤操作,使得满足堆性质
68 /// </summary>
69 /// <param name="nodeList"></param>
70 /// <param name="index">非叶子节点的之后指针(这里要注意:我们
71 /// 的筛操作时针对非叶节点的)
72 /// </param>
73 public void UpHeapAdjust(List<HeapNode> 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 /// <summary>
115 /// 优先队列的出队操作
116 /// </summary>
117 /// <returns></returns>
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 /// <summary>
141 /// 对堆进行下滤操作,使得满足堆性质
142 /// </summary>
143 /// <param name="nodeList"></param>
144 /// <param name="index">非叶子节点的之后指针(这里要注意:我们
145 /// 的筛操作时针对非叶节点的)
146 /// </param>
147 public void DownHeapAdjust(List<HeapNode> 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 /// <summary>
189 /// 获取元素并下降到指定的level级别
190 /// </summary>
191 /// <returns></returns>
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 /// <summary>
212 /// 获取元素并下降优先级
213 /// </summary>
214 /// <returns></returns>
215 public HeapNode GetAndDownPriority()
216 {
217 //下降一个优先级
218 return GetAndDownPriority(int.MinValue);
219 }
220 #endregion
221
222 #region 返回当前优先队列中的元素个数
223 /// <summary>
224 /// 返回当前优先队列中的元素个数
225 /// </summary>
226 /// <returns></returns>
227 public int Count()
228 {
229 return nodeList.Count;
230 }
231 #endregion
232 }
233 }

经典算法题每日演练——第十六题 Kruskal算法的更多相关文章

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

    原文:经典算法题每日演练--第十四题 Prim算法 图论在数据结构中是非常有趣而复杂的,作为web码农的我,在实际开发中一直没有找到它的使用场景,不像树那样的频繁使用,不过还是准备 仔细的把图论全部过 ...

  2. codeforces水题100道 第二十六题 Codeforces Beta Round #95 (Div. 2) A. cAPS lOCK (strings)

    题目链接:http://www.codeforces.com/problemset/problem/131/A题意:字符串大小写转换.C++代码: #include <cstdio> #i ...

  3. codeforces水题100道 第十六题 Codeforces Round #164 (Div. 2) A. Games (brute force)

    题目链接:http://www.codeforces.com/problemset/problem/268/A题意:足球比赛中如果主场球队的主场球衣和客队的客队球衣颜色一样,那么要求主队穿上他们的可对 ...

  4. 经典算法题每日演练——第六题 协同推荐SlopeOne 算法

    原文:经典算法题每日演练--第六题 协同推荐SlopeOne 算法 相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,“商品推荐”,"猜你喜欢“,在实体店中我们有导购来为 ...

  5. 经典算法题每日演练——第十七题 Dijkstra算法

    原文:经典算法题每日演练--第十七题 Dijkstra算法 或许在生活中,经常会碰到针对某一个问题,在众多的限制条件下,如何去寻找一个最优解?可能大家想到了很多诸如“线性规划”,“动态规划” 这些经典 ...

  6. 经典算法题每日演练——第十一题 Bitmap算法

    原文:经典算法题每日演练--第十一题 Bitmap算法 在所有具有性能优化的数据结构中,我想大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量时间,多么的简洁优美, 但是在特定的场 ...

  7. 经典算法题每日演练——第八题 AC自动机

    原文:经典算法题每日演练--第八题 AC自动机 上一篇我们说了单模式匹配算法KMP,现在我们有需求了,我要检查一篇文章中是否有某些敏感词,这其实就是多模式匹配的问题. 当然你也可以用KMP算法求出,那 ...

  8. 经典算法题每日演练——第七题 KMP算法

    原文:经典算法题每日演练--第七题 KMP算法 在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的, 确实kmp算法还是有点饶人的,如果说红黑树 ...

  9. CTF---Web入门第十六题 天下武功唯快不破

    天下武功唯快不破分值:10 来源: 北邮天枢战队 难度:易 参与人数:10787人 Get Flag:2264人 答题人数:3373人 解题通过率:67% 看看响应头 格式:CTF{ } 解题链接: ...

随机推荐

  1. Apache HTTP Server 与 Tomcat 的三种连接方式介绍(转)

    首先我们先介绍一下为什么要让 Apache 与 Tomcat 之间进行连接.事实上 Tomcat 本身已经提供了 HTTP 服务,该服务默认的端口是 8080,装好 tomcat 后通过 8080 端 ...

  2. hbase开放lzo压缩

    hbase仅仅支持对gzip的压缩,对lzo压缩支持不好. 在io成为系统瓶颈的情况下,一般开启lzo压缩会提高系统的吞吐量. 但这须要參考详细的应用场景,即是否值得进行压缩.压缩率是否足够等等.  ...

  3. jwt (JSON Web Token)官方说明

    http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#appendix-A 相关文章 http://haomou.net/2014 ...

  4. linuxIO刷新机制fsync和fdatasync详细解释

    前言: Linux,unix在内核中设有 缓冲区快速缓冲或页面快速缓冲.大多数磁盘I/O都通过缓冲进行,採用延迟写技术. sync:将全部改动过的快缓存区排入写队列.然后返回.并不等待实际写磁盘操作结 ...

  5. 仿微沟道效应,主要actionbar有些知识

    仿微沟道效应,主要actionbar有些知识 1.新actionBar的menu <menu xmlns:android="http://schemas.android.com/apk ...

  6. Source insight 3572安装和版本号An invalid source insight serial number was detected解

    Source insight最新版本3572 下载链接:http://www.sourceinsight.com/down35.html,   http://www.sourceinsight.com ...

  7. R语言做文本挖掘 Part4文本分类

    Part4文本分类 Part3文本聚类提到过.与聚类分类的简单差异. 那么,我们需要理清训练集的分类,有明白分类的文本:測试集,能够就用训练集来替代.预測集,就是未分类的文本.是分类方法最后的应用实现 ...

  8. SQLServer数据类型优先级对性能的影响

    原文:SQLServer数据类型优先级对性能的影响 译自: http://www.mssqltips.com/sqlservertip/2749/sql-server-data-type-preced ...

  9. Leetcode:maximum_depth_of_binary_tree题解

    一.     题目 给定一个二叉树,求它的最大深度.最大深度是沿从根节点,到叶节点最长的路径. 二.     分析 (做到这里发现接连几道题都是用递归,可能就是由于自己时挑的简单的做的吧.) 找出最深 ...

  10. mysql数据库的安装以及常见优化设置

    原文请详见:http://www.ucai.cn/blogdetail/7036?mid=1&f=5 能够在线执行查看效果哦! 本文依据优才网课程整理,面向web开发人员,内容以有用为主,专业 ...