這篇我們看看第二種生成樹的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 }