這篇我們看看第二種生成樹的 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 ///
4 ///
5 public void Build() 5 public List 最後是總的程式碼: View Code 1 using System; 11 public class DisjointSet 17 public class Node 22 public T parent; 27 public int rank; 42 public void Init(T[] c) 60 ///
61 ///
62 /// 73 ///
74 /// 90 ///
91 ///
92 /// 16 private List 22 public class HeapNode 27 public T t { get; set; } 32 public int level { get; set; } 48 public void Eequeue(T t, int level = 1) 69 ///
70 /// 非葉子節點的之後指標(這裏要注意:我們 117 /// 143 ///
144 /// 非葉子節點的之後指標(這裏要注意:我們 191 /// 214 /// 226 /// 文章來自互聯網博客網站:http://www.cnblogs.com/huangxincheng/archive/2012/12/17/2821132.html,版權歸作者所有。
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
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 ///
4 ///
6 {
7 //最後收集到的最小生成樹的邊
8 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
並查集:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace ConsoleApplication2
7 {
8 ///
10 ///
12 {
13 #region 樹節點
14 ///
16 ///
18 {
19 ///
21 ///
23
24 ///
26 ///
28 }
29 #endregion
30
31 Dictionary
32
33 public DisjointSet(T[] c)
34 {
35 Init(c);
36 }
37
38 #region 做單一集合的初始化操作
39 ///
41 ///
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 ///
59 ///
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 ///
72 ///
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 ///
89 ///
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
12 {
13 ///
15 ///
17
18 #region 堆節點定義
19 ///
21 ///
23 {
24 ///
26 ///
28
29 ///
31 ///
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 ///
47 ///
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 ///
68 ///
71 /// 的篩操作時針對非葉節點的)
72 ///
73 public void UpHeapAdjust(List
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 ///
116 ///
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 ///
142 ///
145 /// 的篩操作時針對非葉節點的)
146 ///
147 public void DownHeapAdjust(List
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 ///
190 ///
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 ///
213 ///
215 public HeapNode GetAndDownPriority()
216 {
217 //下降一個優先順序
218 return GetAndDownPriority(int.MinValue);
219 }
220 #endregion
221
222 #region 返回當前優先佇列中的元素個數
223 ///
225 ///
227 public int Count()
228 {
229 return nodeList.Count;
230 }
231 #endregion
232 }
233 }