這篇我們看看第二種生成樹的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
最后是總的代碼:
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 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 }
並查集:
View Code
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 }
優先隊列:
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<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 }

View Code