圖論在數據結構中是非常有趣而復雜的,作為web碼農的我,在實際開發中一直沒有找到它的使用場景,不像樹那樣的頻繁使用,不過還是准備
仔細的把圖論全部過一遍。
一:最小生成樹
圖中有一個好玩的東西叫做生成樹,就是用邊來把所有的頂點聯通起來,前提條件是最后形成的聯通圖中不能存在回路,所以就形成這樣一個
推理:假設圖中的頂點有n個,則生成樹的邊有n-1條,多一條會存在回路,少一路則不能把所有頂點聯通起來,如果非要在圖中加上權重,則生成樹
中權重最小的叫做最小生成樹。
對於上面這個帶權無向圖來說,它的生成樹有多個,同樣最小生成樹也有多個,因為我們比的是權重的大小。
二:Prim算法
求最小生成樹的算法有很多,常用的是Prim算法和Kruskal算法,為了保證單一職責,我把Kruskal算法放到下一篇,那么Prim算法的思想
是什么呢?很簡單,貪心思想。
如上圖:現有集合M={A,B,C,D,E,F},再設集合N={}。
第一步:挑選任意節點(比如A),將其加入到N集合,同時剔除M集合的A。
第二步:尋找A節點權值最小的鄰節點(比如F),然后將F加入到N集合,此時N={A,F},同時剔除M集合中的F。
第三步:尋找{A,F}中的權值最小的鄰節點(比如E),然后將E加入到N集合,此時N={A,F,E},同時剔除M集合的E。
。。。
最后M集合為{}時,生成樹就構建完畢了,是不是非常的簡單,這種貪心做法我想大家都能想得到,如果算法配合一個好的數據結構,就會
如虎添翼。
三:代碼
1. 圖的存儲
圖的存儲有很多方式,鄰接矩陣,鄰接表,十字鏈表等等,當然都有自己的適合場景,下面用鄰接矩陣來玩玩,鄰接矩陣需要采用兩個數組,
①. 保存頂點信息的一維數組,
②. 保存邊信息的二維數組。
1 public class Graph 2 { 3 /// <summary> 4 /// 頂點個數 5 /// </summary> 6 public char[] vertexs; 7 8 /// <summary> 9 /// 邊的條數 10 /// </summary> 11 public int[,] edges; 12 13 /// <summary> 14 /// 頂點個數 15 /// </summary> 16 public int vertexsNum; 17 18 /// <summary> 19 /// 邊的個數 20 /// </summary> 21 public int edgesNum; 22 }
2:矩陣構建
矩陣構建很簡單,這里把上圖中的頂點和權的信息保存在矩陣中。
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 char[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] = (char)(i + 65); 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[3, 2] = graph.edges[2, 3] = 100; 35 graph.edges[4, 5] = graph.edges[5, 4] = 40; 36 graph.edges[3, 4] = graph.edges[4, 3] = 60; 37 graph.edges[2, 3] = graph.edges[3, 2] = 10; 38 } 39 #endregion
3:Prim
要玩Prim,我們需要兩個字典。
①:保存當前節點的字典,其中包含該節點的起始邊和終邊以及權值,用weight=-1來記錄當前節點已經訪問過,用weight=int.MaxValue表示
兩節點沒有邊。
②:輸出節點的字典,存放的就是我們的N集合。
當然這個復雜度玩高了,為O(N2),尋找N集合的鄰邊最小權值時,我們可以玩玩AVL或者優先隊列來降低復雜度。
1 #region prim算法 2 /// <summary> 3 /// prim算法 4 /// </summary> 5 public Dictionary<char, Edge> Prim() 6 { 7 Dictionary<char, Edge> dic = new Dictionary<char, Edge>(); 8 9 //統計結果 10 Dictionary<char, Edge> outputDic = new Dictionary<char, Edge>(); 11 12 //weight=MaxValue:標識沒有邊 13 for (int i = 0; i < graph.vertexsNum; i++) 14 { 15 //起始邊 16 var startEdge = (char)(i + 65); 17 18 dic.Add(startEdge, new Edge() { weight = int.MaxValue }); 19 } 20 21 //取字符的開始位置 22 var index = 65; 23 24 //取當前要使用的字符 25 var start = (char)(index); 26 27 for (int i = 0; i < graph.vertexsNum; i++) 28 { 29 //標記開始邊已使用過 30 dic[start].weight = -1; 31 32 for (int j = 1; j < graph.vertexsNum; j++) 33 { 34 //獲取當前 c 的 鄰邊 35 var end = (char)(j + index); 36 37 //取當前字符的權重 38 var weight = graph.edges[(int)(start) - index, j]; 39 40 if (weight < dic[end].weight) 41 { 42 dic[end] = new Edge() 43 { 44 weight = weight, 45 startEdge = start, 46 endEdge = end 47 }; 48 } 49 } 50 51 var min = int.MaxValue; 52 53 char minkey = ' '; 54 55 foreach (var key in dic.Keys) 56 { 57 //取當前 最小的 key(使用過的除外) 58 if (min > dic[key].weight && dic[key].weight != -1) 59 { 60 min = dic[key].weight; 61 minkey = key; 62 } 63 } 64 65 start = minkey; 66 67 //邊為頂點減去1 68 if (outputDic.Count < graph.vertexsNum - 1 && !outputDic.ContainsKey(minkey)) 69 { 70 outputDic.Add(minkey, new Edge() 71 { 72 weight = dic[minkey].weight, 73 startEdge = dic[minkey].startEdge, 74 endEdge = dic[minkey].endEdge 75 }); 76 } 77 } 78 return outputDic; 79 } 80 #endregion
4:最后我們來測試一下,看看找出的最小生成樹。
1 public static void Main() 2 { 3 MatrixGraph martix = new MatrixGraph(); 4 5 martix.Build(); 6 7 var dic = martix.Prim(); 8 9 Console.WriteLine("最小生成樹為:"); 10 11 foreach (var key in dic.Keys) 12 { 13 Console.WriteLine("({0},{1})({2})", dic[key].startEdge, dic[key].endEdge, dic[key].weight); 14 } 15 16 Console.Read(); 17 }

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 SupportCenter.Test.ServiceReference2; 9 using System.Threading.Tasks; 10 11 namespace ConsoleApplication2 12 { 13 public class Program 14 { 15 public static void Main() 16 { 17 MatrixGraph martix = new MatrixGraph(); 18 19 martix.Build(); 20 21 var dic = martix.Prim(); 22 23 Console.WriteLine("最小生成樹為:"); 24 25 foreach (var key in dic.Keys) 26 { 27 Console.WriteLine("({0},{1})({2})", dic[key].startEdge, dic[key].endEdge, dic[key].weight); 28 } 29 30 Console.Read(); 31 } 32 } 33 34 /// <summary> 35 /// 定義矩陣節點 36 /// </summary> 37 public class MatrixGraph 38 { 39 Graph graph = new Graph(); 40 41 public class Graph 42 { 43 /// <summary> 44 /// 頂點個數 45 /// </summary> 46 public char[] vertexs; 47 48 /// <summary> 49 /// 邊的條數 50 /// </summary> 51 public int[,] edges; 52 53 /// <summary> 54 /// 頂點個數 55 /// </summary> 56 public int vertexsNum; 57 58 /// <summary> 59 /// 邊的個數 60 /// </summary> 61 public int edgesNum; 62 } 63 64 #region 矩陣的構建 65 /// <summary> 66 /// 矩陣的構建 67 /// </summary> 68 public void Build() 69 { 70 //頂點數 71 graph.vertexsNum = 6; 72 73 //邊數 74 graph.edgesNum = 8; 75 76 graph.vertexs = new char[graph.vertexsNum]; 77 78 graph.edges = new int[graph.vertexsNum, graph.vertexsNum]; 79 80 //構建二維數組 81 for (int i = 0; i < graph.vertexsNum; i++) 82 { 83 //頂點 84 graph.vertexs[i] = (char)(i + 65); 85 86 for (int j = 0; j < graph.vertexsNum; j++) 87 { 88 graph.edges[i, j] = int.MaxValue; 89 } 90 } 91 92 graph.edges[0, 1] = graph.edges[1, 0] = 80; 93 graph.edges[0, 3] = graph.edges[3, 0] = 100; 94 graph.edges[0, 5] = graph.edges[5, 0] = 20; 95 graph.edges[1, 2] = graph.edges[2, 1] = 90; 96 graph.edges[2, 5] = graph.edges[5, 2] = 70; 97 graph.edges[3, 2] = graph.edges[2, 3] = 100; 98 graph.edges[4, 5] = graph.edges[5, 4] = 40; 99 graph.edges[3, 4] = graph.edges[4, 3] = 60; 100 graph.edges[2, 3] = graph.edges[3, 2] = 10; 101 } 102 #endregion 103 104 #region 邊的信息 105 /// <summary> 106 /// 邊的信息 107 /// </summary> 108 public class Edge 109 { 110 //開始邊 111 public char startEdge; 112 113 //結束邊 114 public char endEdge; 115 116 //權重 117 public int weight; 118 } 119 #endregion 120 121 #region prim算法 122 /// <summary> 123 /// prim算法 124 /// </summary> 125 public Dictionary<char, Edge> Prim() 126 { 127 Dictionary<char, Edge> dic = new Dictionary<char, Edge>(); 128 129 //統計結果 130 Dictionary<char, Edge> outputDic = new Dictionary<char, Edge>(); 131 132 //weight=MaxValue:標識沒有邊 133 for (int i = 0; i < graph.vertexsNum; i++) 134 { 135 //起始邊 136 var startEdge = (char)(i + 65); 137 138 dic.Add(startEdge, new Edge() { weight = int.MaxValue }); 139 } 140 141 //取字符的開始位置 142 var index = 65; 143 144 //取當前要使用的字符 145 var start = (char)(index); 146 147 for (int i = 0; i < graph.vertexsNum; i++) 148 { 149 //標記開始邊已使用過 150 dic[start].weight = -1; 151 152 for (int j = 1; j < graph.vertexsNum; j++) 153 { 154 //獲取當前 c 的 鄰邊 155 var end = (char)(j + index); 156 157 //取當前字符的權重 158 var weight = graph.edges[(int)(start) - index, j]; 159 160 if (weight < dic[end].weight) 161 { 162 dic[end] = new Edge() 163 { 164 weight = weight, 165 startEdge = start, 166 endEdge = end 167 }; 168 } 169 } 170 171 var min = int.MaxValue; 172 173 char minkey = ' '; 174 175 foreach (var key in dic.Keys) 176 { 177 //取當前 最小的 key(使用過的除外) 178 if (min > dic[key].weight && dic[key].weight != -1) 179 { 180 min = dic[key].weight; 181 minkey = key; 182 } 183 } 184 185 start = minkey; 186 187 //邊為頂點減去1 188 if (outputDic.Count < graph.vertexsNum - 1 && !outputDic.ContainsKey(minkey)) 189 { 190 outputDic.Add(minkey, new Edge() 191 { 192 weight = dic[minkey].weight, 193 startEdge = dic[minkey].startEdge, 194 endEdge = dic[minkey].endEdge 195 }); 196 } 197 } 198 return outputDic; 199 } 200 #endregion 201 } 202 }