這節,我們主要討論,一下克魯斯卡爾算法實現 最小生成樹。
克魯斯卡爾算法的基本思想是:對一個有 n個頂點的無向連通網,將圖中的邊按權值大小依次選取,若選取的邊使生成樹不形成回路,則把它加入到樹中;若形成回路,則將它舍 棄。如此進行下去,直到樹中包含有 n-1條邊為止。
以下圖 (a)為例說明用克魯斯卡爾算法求無向連通網最小生成樹的過程。
第一步:首先比較網中所有邊的權值,找到最小的權值的邊(D,E),加入到生成樹的邊集 TE 中,TE={(D,E)}。
第二步:再比較圖中除邊(D,E)的邊的權值,又找到最小權值的邊(A,D)並且不會形成回路,加入到生成樹的邊集 TE 中,TE={(A,D),(D,E)}。
第三步: 再比較圖中除 TE 以外的所有邊的權值, 找到最小的權值的邊(A,B) 並且不會形成回路,加入到生成樹的邊集 TE 中,TE={(A,D),(D,E),(A,B)}。
第四步:再比較圖中除 TE 以外的所有邊的權值,找到最小的權值的邊(E,C) 並且不會形成回路,加入到生成樹的邊集 TE 中,TE={(A,D),(D,E),(A,B),(E,C)}。 此時,邊集 TE 中已經有 n-1條邊,如下圖(b) 所示。這個結果與用普里姆算法得到的結果相同。
實現源代碼如下:
public int[] Klausi() { int[] lowcost = new int[nodes.Length]; //權值數組 保存權值的數組 int[] closevex = new int[nodes.Length]; //頂點數組 保存 相應各個頂點的數組 int mincost = int.MaxValue; //最小權值 默認是 int的最大值 表示無窮大 //輔助數組初始化 對摸個 權值數組賦值 保存 最小值 for (int i = 1; i < nodes.Length; ++i) { lowcost[i] = matrix[0, i]; closevex[i] = 0; } //某個頂點加入集合U lowcost[0] = 0; closevex[0] = 0; //判斷最小的權值通過的頂點的循環就此開始 for(int i=0; i<nodes.Length; ++i) { int k = 1; int j = 1; //選取權值最小的邊和相應的頂點 while(j < nodes.Length) { if (lowcost[j] < mincost && lowcost[j] != 0) { k = j; } ++j; } //新頂點加入集合U lowcost[k] = 0; //重新計算該頂點到其余頂點的邊的權值 for (j = 1; j < nodes.Length; ++j) { if (matrix[k, j] < lowcost[j]) { lowcost[j] = matrix[k, j]; closevex[j] = k; } } } return closevex; } //我們明顯的看出來,由於用到了雙重循環,其算法的時間的復雜度是O(n^2)
介紹了最短路徑的概念
最短路徑問題是圖的又一個比較典型的應用問題。例如,n個城市之間的一個公路網,給定這些城市之間的公路的距離,能否找到城市 A 到城市 B 之間一條距離最近的通路呢?如果城市用頂點表示,城市間的公路用邊表示,公路的長度作為邊的權值。那么,這個問題就可歸結為在網中求頂點 A 到頂點 B 的所有路徑中邊的權值之和最小的那一條路徑, 這條路徑就是兩個頂點之間的最短路徑(Shortest Path),並稱路徑上的第一個頂點為源點(Source) ,最后一個頂點為終點(Destination) 。在不帶權的圖中,最短路徑是指兩個頂點之間經歷的邊數最少的路徑。 最短路徑可以是求某個源點出發到其它頂點的最短路徑, 也可以是求網中任意兩個頂點之間的最短路徑。這里只討論單源點的最短路徑問題,感興趣的讀者可參考有關文獻,了解每一對頂點之間的最短路徑。 網分為無向網和有向網,當把無向網中的每一條邊(vi,vj)都定義為弧<vi,vj>和弧<vj,vi>,則有向網就變成了無向網。因此,不失一般性,我們這里只討論有向網上的最短路徑問題。 下圖是一個有向網及其鄰接矩陣。該網從頂點 A 到頂點 D 有 4條路徑,分別是:路徑(A,D) ,其帶權路徑長度為 30;路徑(A,C,F,D) ,其帶權路徑長度為 22;路徑(A,C,B,E,D) ,其帶權路徑長度為 32;路徑(A,C,F,E,D) ,其帶權路徑長度為 34。路徑(A,C,F,D)稱為最短路徑,其帶權路徑長度 22稱為最短距離。
他用的是狄克斯特拉(Dikastra)算法
對於求單源點的最短路徑問題,狄克斯特拉(Dikastra)提出了一個按路徑長度遞增的順序逐步產生最短路徑的構造算法。狄克斯特拉的算法思想是:設置兩個頂點的集合 S 和T, 集合 S 中存放已找到最短路徑的頂點, 集合 T 中存放當前還未找到最短路徑的頂點。初始狀態時,集合 S 中只包含源點,設為 v0,然后從集合 T 中選擇到源點 v0路徑長度最短的頂點 u加入到集合 S 中,集合 S 中每加入一個新的頂點 u都要修改源點 v0到集合 T 中剩余頂點的當前最短路徑長度值,集合 T 中各頂點的新的最短路徑長度值為原來的當前最短路徑長度值與從源點過頂點 u到達該頂點的新的最短路徑長度中的較小者。此過程不斷重復,直到集合 T 中的頂點全部加到集合 S 中為止。
以下圖為例說明用狄克斯特拉算法求有向網的從某個頂點到其余頂點最短路徑的過程。
第一步:列出頂點 A 到其余各頂點的路徑長度,它們分別為 0、∞、5、30、∞、∞。從中選取路徑長度最小的頂點 C(從源點到頂點 C 的最短路徑為 5) 。
第二步:找到頂點 C 后,再觀察從源點經頂點 C 到各個頂點的路徑是否比第一步所找到的路徑要小(已選取的頂點則不必考慮) ,可發現,源點到頂點 B的路徑長度更新為 20(A,C,B) ,源點到頂點 F 的路徑長度更新為 12(A,C,F) , 其余的路徑則不變。 然后, 從已更新的路徑中找出路徑長度最小的頂點 F (從源點到頂點 F 的最短路徑為 12) 。
第三步:找到頂點 C、F 以后,再觀察從源點經頂點 C、F 到各頂點的路徑是否比第二步所找到的路徑要小(已被選取的頂點不必考慮) ,可發現,源點到頂點 D 的路徑長度更新為 22(A,C,F,D) ,源點到頂點 E 的路徑長度更新為30(A,C,F,E) ,其余的路徑不變。然后,從已更新的路徑中找出路徑長短最小的頂點 D(從源點到頂點 D 的最短路徑為 22) 。
第四步:找到頂點 C、F、D 后,現在只剩下最后一個頂點 E 沒有找到最短路徑了,再觀察從源點經頂點 C、F、D 到頂點 E 的路徑是否比第三步所找到的路徑要小(已選取的頂點則不必考慮) ,可以發現,源點到頂點 E 的路徑長度更新為 28(A,B,E) ,其余的路徑則不變。然后,從已更新的路徑中找出路徑長度最小的頂點 E(從源點到頂點 E 的最短路徑為 28)。
、有向網的鄰接矩陣類的實現
本書以有向網的鄰接矩陣類 DirecNetAdjMatrix<T>來實現狄克斯特拉算法。DirecNetAdjMatrix<T>有三個成員字段, 一個是 Node<T>類型的一維數組 nodes,存放有向網中的頂點信息,一個是整型的二維數組 matirx,表示有向網的鄰接矩陣,存放弧的信息,一個是整數 numArcs,表示有向網中弧的數目,有向網的鄰接矩陣類 DirecNetAdjMatrix<T>源代碼的實現如下所示。
public class DirecNetAdjMatrix<T> : IGraph<T>//繼承圖形的接口 { private Node<T>[] nodes; //頂點數組 存取相應的結點的 泛型數組 private int numEdges; //邊的數目 上圖邊數字是6 private int[,] matrix; //鄰接矩陣數組 存取相應的互相的權值 //構造器 進行數據的初始化 邊的數目是0 public NetAdjMatrix (int n) { nodes = new Node<T>[n]; matrix = new int[n,n]; numEdges = 0; } //獲取索引為index的頂點的信息 算法的時間復雜度是O(1) public Node<T> GetNode(int index) { return nodes[index]; } //設置索引為index的頂點的信息 算法的時間復雜度是O(1) public void SetNode(int index, Node<T> v) { nodes[index] = v; } //邊的數目屬性 可讀可寫的屬性 public int NumEdges { get { return numEdges; } set { numEdges = value; } } //獲取matrix[index1, index2]的值 算法的時間復雜度是O(1) public int GetMatrix(int index1, int index2) { return matrix[index1, index2]; } //設置matrix[index1, index2]的值 算法的復雜度是O(1) public void SetMatrix(int index1, int index2, int v) { matrix[index1, index2] = v; } //獲取頂點的數目 算法的時間的復雜度是O(1) public int GetNumOfVertex() { return nodes.Length; } //獲取邊的數目 算法的時間的復雜度是O(1) public int GetNumOfEdge() { return numEdges; } //v是否是無向網的頂點 //如果包含這個頂點 返回為真,否則返回為假。 //由於這是一層循環,算法的復雜度是O(n) public bool IsNode(Node<T> v) { //遍歷頂點數組 foreach (Node<T> nd in nodes) { //如果頂點nd與v相等,則v是圖的頂點,返回true if (v.Equals(nd)) { return true; } } return false; } //獲得頂點v在頂點數組中的索引 // 如果相等,返回相應的索引。 //由於是一層循環,時間的復雜度是O(n) public int GetIndex(Node<T> v) { int i = -1; //遍歷頂點數組 for (i = 0; i < nodes.Length; ++i) { //如果頂點nd與v相等,則v是圖的頂點,返回索引值 if (nodes[i].Equals(v)) { return i; } } return i; } //在頂點v1、v2之間添加權值為v的邊 //添加相應的權值的v的邊, 這是一個對稱矩陣。 public void SetEdge(Node<T> v1, Node<T> v2, int v) { //v1或v2不是無向網的頂點 if (!IsNode(v1) || !IsNode(v2)) { Console.WriteLine("Node is not belong to Graph!"); return; } //矩陣是對稱矩陣 matrix[GetIndex(v1), GetIndex(v2)] = v; matrix[GetIndex(v2), GetIndex(v1)] = v; ++numEdges; } //刪除v1和v2之間的邊 // 刪除對稱矩陣。 public void DelEdge(Node<T> v1, Node<T> v2) { //v1或v2不是無向網的頂點 if (!IsNode(v1) || !IsNode(v2)) { Console.WriteLine("Node is not belong to Graph!"); return; } //v1和v2之間存在邊 if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue) { //矩陣是對稱矩陣 matrix[GetIndex(v1), GetIndex(v2)] = int.MaxValue; matrix[GetIndex(v2), GetIndex(v1)] = int.MaxValue; --numEdges; } } //判斷v1和v2之間是否存在邊 //判斷相應 不是 最大值 返回為真 否則 為假 算法的時間復雜度O(1) public bool IsEdge(Node<T> v1, Node<T> v2) { //v1或v2不是無向網的頂點 if (!IsNode(v1) || !IsNode(v2)) { Console.WriteLine("Node is not belong to Graph!"); return false; } //v1和v2之間存在邊 if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue) { return true; } Else //v1和v2之間不存在邊 { return false; } } }
為實現狄克斯特拉算法,引入兩個數組,一個一維數組 ShortPathArr,用來保存從源點到各個頂點的最短路徑的長度,一個二維數組 PathMatrixArr,用來保存從源點到某個頂點的最短路徑上的頂點,如 PathMatrix[v][w]為 true,則 w從源點到頂點 v 的最短路徑上的頂點。為了該算法的結果被其他算法使用,把這兩個數組作為算法的參數使用。另外,為了表示某頂點的最短路徑是否已經找到,在算法中設了一個一維數組 final,如果 final[i]為 true,則表示已經找到第 i 個頂點的最短路徑。i 是該頂點在鄰接矩陣中的序號。同樣,把該算法作為類DirecNetAdjMatrix<T>的成員方法來實現。
//pathMatricArr用來保存從源點到某個頂點的最短路徑上的頂點
//ShortPathArr用來保存從源點到各個頂點的最短路徑的長度
//n 結點的泛型數組
1 public void Dijkstra(ref bool[,] pathMatricArr, 2 ref int[] shortPathArr, Node<T> n) 3 { 4 int k = 0;
//結點是否訪問 5 bool[] final = new bool[nodes.Length]; 6 7 //初始化 8 for (int i = 0; i < nodes.Length; ++i) 9 { 10 final[i] = false; 11 shortPathArr[i] = matrix[GetIndex(n),i]; 12 13 for (int j = 0; j < nodes.Length; ++j) 14 { 15 pathMatricArr[i,j] = false; 16 } 17 if (shortPathArr[i] != 0 && shortPathArr[i] < int.MaxValue) 18 { 19 pathMatricArr[i,GetIndex(n)] = true; 20 pathMatricArr[i,i] = true; 21 } 22 } 23 24 // n為源點 25 shortPathArr[GetIndex(n)] = 0; 26 final[GetIndex(n)] = true; 27 28 //處理從源點到其余頂點的最短路徑 29 for (int i = 0; i < nodes.Length; ++i) 30 { 31 int min = int.MaxValue; 32 33 //比較從源點到其余頂點的路徑長度 34 for (int j = 0; j < nodes.Length; ++j) 35 { 36 //從源點到j頂點的最短路徑還沒有找到 37 if (!final[j]) 38 { 39 /從源點到j頂點的路徑長度最小 40 if (shortPathArr[j] < min) 41 { 42 k = j; 43 min = shortPathArr[j]; 44 } 45 } 46 } 47 48 //源點到頂點k的路徑長度最小 49 final[k] = true; 50 51 //更新當前最短路徑及距離 52 for (int j = 0; j < nodes.Length; ++j) 53 { 54 if (!final[j] && (min + matrix[k,j] < shortPathArr[j])) 55 { 56 shortPathArr[j] = min + matrix[k,j]; 57 for (int w = 0; w < nodes.Length; ++w) 58 { 59 pathMatricArr[j,w] = pathMatricArr[k,w]; 60 } 61 pathMatricArr[j,j] = true; 62 } 63 } 64 } 65 }
//由於是,雙層循環 時間復雜度是O(n2)
如圖所示:
當然,圖的應用還有很多了,點到為止。
至於圖的總結是:
所謂的圖是圖狀結構簡稱圖,是另一種非線性結構,它比樹形結構更復雜。樹形結構中的結點是一對多的關系,結點間具有明顯的層次和分支關系。每一層的結點可以和下一層的多個結點相關,但只能和上一層的一個結點相關。而圖中的頂點(把圖中的數據元素稱為頂點)是多對多的關系,即頂點間的關系是任意的,圖中任意兩個頂點之間都可能相關。也就是說,圖的頂點之間無明顯的層次關系,這種關系在現實世界中大量存在。因此,圖的應用相當廣泛,在自然科學、社會科學和人文科學等許多領域都有着非常廣泛的應用。例如搜索引擎,地圖等等。