或許在生活中,經常會碰到針對某一個問題,在眾多的限制條件下,如何去尋找一個最優解?可能大家想到了很多諸如“線性規划”,“動態規划”
這些經典策略,當然有的問題我們可以用貪心來尋求整體最優解,在圖論中一個典型的貪心法求最優解的例子就莫過於“最短路徑”的問題。
一:概序
從下圖中我要尋找V0到V3的最短路徑,你會發現通往他們的兩點路徑有很多:V0->V4->V3,V0->V1->V3,當然你會認為前者是你要找的最短
路徑,那如果說圖的頂點非常多,你還會這么輕易的找到嗎?下面我們就要將剛才我們那點貪心的思維系統的整理下。
二:構建
如果大家已經了解Prim算法,那么Dijkstra算法只是在它的上面延伸了下,其實也是很簡單的。
1.邊節點
這里有點不一樣的地方就是我在邊上面定義一個vertexs來記錄貪心搜索到某一個節點時曾經走過的節點,比如從V0貪心搜索到V3時,我們V3
的vertexs可能存放着V0,V4,V3這些曾今走過的節點,或許最后這三個節點就是我們要尋找的最短路徑。
1 #region 邊的信息 2 /// <summary> 3 /// 邊的信息 4 /// </summary> 5 public class Edge 6 { 7 //開始邊 8 public int startEdge; 9 10 //結束邊 11 public int endEdge; 12 13 //權重 14 public int weight; 15 16 //是否使用 17 public bool isUse; 18 19 //累計頂點 20 public HashSet<int> vertexs = new HashSet<int>(); 21 } 22 #endregion
2.Dijkstra算法
首先我們分析下Dijkstra算法的步驟:
有集合M={V0,V1,V2,V3,V4}這樣5個元素,我們用
TempVertex表示該頂點是否使用。
Weight表示該Path的權重(默認都為MaxValue)。
Path表示該頂點的總權重。
①. 從集合M中挑選頂點V0為起始點。給V0的所有鄰接點賦值,要賦值的前提是要賦值的weight要小於原始的weight,並且排除已經訪問過
的頂點,然后挑選當前最小的weight作為下一次貪心搜索的起點,就這樣V0V1為挑選為最短路徑,如圖2。
②. 我們繼續從V1這個頂點開始給鄰接點以同樣的方式賦值,最后我們發現V0V4為最短路徑。也就是圖3。
。。。
③. 最后所有頂點的最短路徑就這樣求出來了 。
1 #region Dijkstra算法 2 /// <summary> 3 /// Dijkstra算法 4 /// </summary> 5 public Dictionary<int, Edge> Dijkstra() 6 { 7 //收集頂點的相鄰邊 8 Dictionary<int, Edge> dic_edges = new Dictionary<int, Edge>(); 9 10 //weight=MaxValue:標識沒有邊 11 for (int i = 0; i < graph.vertexsNum; i++) 12 { 13 //起始邊 14 var startEdge = i; 15 16 dic_edges.Add(startEdge, new Edge() { weight = int.MaxValue }); 17 } 18 19 //取第一個頂點 20 var start = 0; 21 22 for (int i = 0; i < graph.vertexsNum; i++) 23 { 24 //標記該頂點已經使用過 25 dic_edges[start].isUse = true; 26 27 for (int j = 1; j < graph.vertexsNum; j++) 28 { 29 var end = j; 30 31 //取到相鄰邊的權重 32 var weight = graph.edges[start, end]; 33 34 //賦較小的權重 35 if (weight < dic_edges[end].weight) 36 { 37 //與上一個頂點的權值累加 38 var totalweight = dic_edges[start].weight == int.MaxValue ? weight : dic_edges[start].weight + weight; 39 40 if (totalweight < dic_edges[end].weight) 41 { 42 //將該頂點的相鄰邊加入到集合中 43 dic_edges[end] = new Edge() 44 { 45 startEdge = start, 46 endEdge = end, 47 weight = totalweight 48 }; 49 50 //將上一個邊的節點的vertex累加 51 dic_edges[end].vertexs = new HashSet<int>(dic_edges[start].vertexs); 52 53 dic_edges[end].vertexs.Add(start); 54 dic_edges[end].vertexs.Add(end); 55 } 56 } 57 } 58 59 var min = int.MaxValue; 60 61 //下一個進行比較的頂點 62 int minkey = 0; 63 64 //取start鄰接邊中的最小值 65 foreach (var key in dic_edges.Keys) 66 { 67 //取當前 最小的 key(使用過的除外) 68 if (min > dic_edges[key].weight && !dic_edges[key].isUse) 69 { 70 min = dic_edges[key].weight; 71 minkey = key; 72 } 73 } 74 75 //從鄰接邊的頂點再開始找 76 start = minkey; 77 } 78 79 return dic_edges; 80 } 81 #endregion
總的代碼:復雜度很爛O(N2)。。。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading; using System.IO; using System.Threading.Tasks; namespace ConsoleApplication2 { public class Program { public static void Main() { Dictionary<int, string> dic = new Dictionary<int, string>(); MatrixGraph graph = new MatrixGraph(); graph.Build(); var result = graph.Dijkstra(); Console.WriteLine("各節點的最短路徑為:"); foreach (var key in result.Keys) { Console.WriteLine("{0}", string.Join("->", result[key].vertexs)); } Console.Read(); } } #region 定義矩陣節點 /// <summary> /// 定義矩陣節點 /// </summary> public class MatrixGraph { Graph graph = new Graph(); public class Graph { /// <summary> /// 頂點信息 /// </summary> public int[] vertexs; /// <summary> /// 邊的條數 /// </summary> public int[,] edges; /// <summary> /// 頂點個數 /// </summary> public int vertexsNum; /// <summary> /// 邊的個數 /// </summary> public int edgesNum; } #region 矩陣的構建 /// <summary> /// 矩陣的構建 /// </summary> public void Build() { //頂點數 graph.vertexsNum = 5; //邊數 graph.edgesNum = 6; graph.vertexs = new int[graph.vertexsNum]; graph.edges = new int[graph.vertexsNum, graph.vertexsNum]; //構建二維數組 for (int i = 0; i < graph.vertexsNum; i++) { //頂點 graph.vertexs[i] = i; for (int j = 0; j < graph.vertexsNum; j++) { graph.edges[i, j] = int.MaxValue; } } //定義 6 條邊 graph.edges[0, 1] = graph.edges[1, 0] = 2; graph.edges[0, 2] = graph.edges[2, 0] = 5; graph.edges[0, 4] = graph.edges[4, 0] = 3; graph.edges[1, 3] = graph.edges[3, 1] = 4; graph.edges[2, 4] = graph.edges[4, 2] = 5; graph.edges[3, 4] = graph.edges[4, 3] = 2; } #endregion #region 邊的信息 /// <summary> /// 邊的信息 /// </summary> public class Edge { //開始邊 public int startEdge; //結束邊 public int endEdge; //權重 public int weight; //是否使用 public bool isUse; //累計頂點 public HashSet<int> vertexs = new HashSet<int>(); } #endregion #region Dijkstra算法 /// <summary> /// Dijkstra算法 /// </summary> public Dictionary<int, Edge> Dijkstra() { //收集頂點的相鄰邊 Dictionary<int, Edge> dic_edges = new Dictionary<int, Edge>(); //weight=MaxValue:標識沒有邊 for (int i = 0; i < graph.vertexsNum; i++) { //起始邊 var startEdge = i; dic_edges.Add(startEdge, new Edge() { weight = int.MaxValue }); } //取第一個頂點 var start = 0; for (int i = 0; i < graph.vertexsNum; i++) { //標記該頂點已經使用過 dic_edges[start].isUse = true; for (int j = 1; j < graph.vertexsNum; j++) { var end = j; //取到相鄰邊的權重 var weight = graph.edges[start, end]; //賦較小的權重 if (weight < dic_edges[end].weight) { //與上一個頂點的權值累加 var totalweight = dic_edges[start].weight == int.MaxValue ? weight : dic_edges[start].weight + weight; if (totalweight < dic_edges[end].weight) { //將該頂點的相鄰邊加入到集合中 dic_edges[end] = new Edge() { startEdge = start, endEdge = end, weight = totalweight }; //將上一個邊的節點的vertex累加 dic_edges[end].vertexs = new HashSet<int>(dic_edges[start].vertexs); dic_edges[end].vertexs.Add(start); dic_edges[end].vertexs.Add(end); } } } var min = int.MaxValue; //下一個進行比較的頂點 int minkey = 0; //取start鄰接邊中的最小值 foreach (var key in dic_edges.Keys) { //取當前 最小的 key(使用過的除外) if (min > dic_edges[key].weight && !dic_edges[key].isUse) { min = dic_edges[key].weight; minkey = key; } } //從鄰接邊的頂點再開始找 start = minkey; } return dic_edges; } #endregion } #endregion }