圖狀結構簡稱圖,是另外一種非線性結構,它比樹形結構更復雜。樹形結構中的結點是一對多的關係,結點間具備明顯的層次和分支關係。每一層的結點能夠和下一層的多個結點相關,但只能和上一層的一個結點相關。而圖中的頂點(把圖中的數據元素稱爲頂點)是多對多的關係,即頂點間的關係是任意的,圖中任意兩個頂點之間均可能相關。也就是說,圖的頂點之間無明顯的層次關係,這種關係在現實世界中大量存在。所以,圖的應用至關普遍,在天然科學、社會科學和人文科學等許多領域都有着很是普遍的應用。node
圖(Graph)是由非空的頂點(Vertex)集合和描述頂點之間的關係——邊(Edge)或弧(Arc)的集合組成。其形式化定義爲:算法
G=(V,E)數組
V={v i |v i ∈某個數據元素集合}
E={(v i ,v j )|v i ,v j ∈V∧P(v i ,v j )}或E={<v i ,v j >|v i ,v j ∈V∧P(v i ,v j )}網絡
其中,G 表示圖,V 是頂點的集合,E 是邊或弧的集合。在集合 E 中,P(vi,vj)表示頂點 vi 和頂點 vj 之間有邊或弧相連。下圖給出了圖的示例。數據結構
在圖 (a)中,V={v 1 ,v 2 ,v 3 ,v 4 ,v 5 }E={(v 1 ,v 2 ),(v 1 ,v 3 ),(v 2 ,v 4 ),(v 2 ,v 5 ),(v 3 ,v 4 ),(v 4 ,v 5 )}ide
在圖 (b)中,V={v 1 ,v 2 ,v 3 ,v 4 ,v 5 }E={<v 1 ,v 2 >,<v 1 ,v 3 >,<v 2 ,v 3 >,<v 2 ,v 5 ><v 3 ,v 4 >,<v 4 ,v 1 >,<v 4 ,v 5 >}。學習
注:無向邊用小括號「()」表示,而有向邊用尖括號「<>」表示。ui
一、無向圖:在一個圖中,若是任意兩個頂點v i 和v j 構成的偶對(v i ,v j )∈E是無序的,即頂點之間的連線沒有方向,則該圖稱爲無向圖(Undirected Graph)。圖 (a)是一個無向圖。this
二、有向圖:在一個圖中,若是任意兩個頂點v i 和v j 構成的偶對<v i ,v j >∈E是有序的,即頂點之間的連線有方向,則該圖稱爲有向圖(Directed Graph)。圖(b)是一個有向圖。spa
三、邊、弧、弧頭、弧尾:無向圖中兩個頂點之間的連線稱爲邊(Edge),邊用頂點的無序偶對(v i ,v j )表示,稱頂點v i 和頂點v j 互爲鄰接點(Adjacency Point),(v i ,v j )邊依附與頂點v i 和頂點v j 。有向圖中兩個頂點之間的連線稱爲弧(Arc),弧用頂點的有序偶對<v i ,v j >表示,有序偶對的第一個結點v i 稱爲始點(或弧尾),在圖中不帶箭頭的一端;有序偶對的第二個結點v j 稱爲終點(或弧頭),在圖中帶箭頭的一端。
四、無向徹底圖:在一個無向圖中,若是任意兩個頂點之間都有邊相連,則稱該圖爲無向徹底圖(Undirected Complete Graph)。能夠證實,在一個含有 n 個頂點的無向徹底圖中,有 n(n-1)/2 條邊。
五、有向徹底圖:在一個有向圖中,若是任意兩個頂點之間都存在方向互爲相反的兩條弧,則稱該圖爲有向徹底圖(Directed Complete Graph)。能夠證實,在一個含有 n 個頂點的有向徹底圖中,有 n(n-1)條弧。
六、頂點的度、入度、出度:在無向圖中,頂點 v 的度(Degree)是指依附於頂點 v 的邊數,一般記爲 TD(v)。在有向圖中,頂點的度等於頂點的入度(In Degree)與頂點的出度之和。頂點v的入度是指以該頂點v爲弧頭的弧的數目,記爲ID(v);頂點 v 的出度(Out Degree)是指以該頂點 v 爲弧尾的弧的數目,記爲 OD(v)。因此,頂點 v 的度 TD(v)= ID(v)+ OD(v)。
例如,在無向圖 (a)中有:
TD(v 1 )=2 TD(v 2 )=3 TD(v 3 )=2 TD(v 4 )=3 TD(v 5 )=2
在有向圖 (b)中有:
ID(v 1 )=1 OD(v 1 )=2 TD(v 1 )=3
ID(v 2 )=1 OD(v 2 )=2 TD(v 2 )=3
ID(v 3 )=2 OD(v 3 )=1 TD(v 3 )=3
ID(v 4 )=1 OD(v 4 )=2 TD(v 4 )=3
ID(v 5 )=2 OD(v 5 )=0 TD(v 5 )=2
七、權、網:有些圖的邊(或弧)附帶有一些數據信息,這些數據信息稱爲邊(或弧)的權(Weight)。在實際問題中,權能夠表示某種含義。好比,在一個地方的交通圖中,邊上的權值表示該條線路的長度或等級。在一個工程進度圖中,弧上的權值能夠表示從前一個工程到後一個工程所須要的時間或其它代價等。邊(或弧)上帶權的圖稱爲網或網絡(Network)。 下圖是權圖的示例圖。
八、子圖:設有兩個圖 G1=(V1,E1),G2=(V2,E2),若是 V1 是 V2 子集,E1 也是 E2 的子集,則稱圖 G1 是 G2 的子圖(Subgraph)。下圖是子圖的示例圖。
九、路徑、路徑長度:在無向圖G中,若存在一個頂點序列V p ,V i1 ,V i2 ,…,V im ,V q ,使得(V p ,V i1 ),(V i1 ,V i2 ),…,(V im ,V q )均屬於E(G)。則稱頂點V p 到V q 存在一條路徑(Path)。若G爲有向圖,則路徑也是有向的。它由E(G)中的弧<V p ,V i1 >,<V i1 ,V i2 >,…,<V im ,V q >組成。路徑長度(Path Length)定義爲路徑上邊或弧的數目。在圖 (a)交通網絡圖中,從頂點A到頂點B存在 4 條路徑,長度分別爲一、二、二、4。在圖 (b)施工進度圖中,從頂點 1 到頂點 7 存在 2 條路徑,長度分別爲 2 和3。
十、簡單路徑、迴路、簡單迴路:若一條路徑上頂點不重複出現,則稱此路徑爲簡單路徑(Simple Path)。第一個頂點和最後一個頂點相同的路徑稱爲迴路(Cycle)或環。除第一個頂點和最後一個頂點相同其他頂點都不重複的迴路稱爲簡單迴路(Simple Cycle),或者簡單環。
十一、連通、連通圖、連通份量:在無向圖中,若兩個頂點之間有路徑,則稱這兩個頂點是連通的(Connect)。若是無向圖 G 中任意兩個頂點之間都是連通的,則稱圖 G 是連通圖(Connected Graph)。連通份量(Connected Compenent)是無向圖 G的極大連通子圖。極大連通子圖是一個圖的連通子圖,該子圖不是該圖的其它連通子圖的子圖。圖 (a)G1和圖(b)G2是連通圖,圖的連通份量的示例見下圖所示。圖 6.4(a)中的圖 G 有兩個連通份量。
十二、強連通圖、強連通份量:在有向圖中,若圖中任意兩個頂點之間都存在從一個頂點到另外一個頂點的路徑,則稱該有向圖是強連通圖(Strongly ConnectedGraph) 。 有 向 圖 的 極 大 強 連 通 子 圖 稱 爲 強 連 通 分 量 (Strongly ConnectedComponent)。極大強連通子圖是一個有向圖的強連通子圖,該子圖不是該圖的其它強連通子圖的子圖。左圖是強連通圖,右圖是強連通份量的示例圖。圖(a)中的有向圖 G 有兩個強連通份量。
1三、生成樹:所謂連通圖 G 的生成樹(Spanning Tree)是指 G 的包含其所有頂點的一個極小連通子圖。所謂極小連通子圖是指在包含全部頂點而且保證連通的前提下包含原圖中最少的邊。一棵具備 n 個頂點的連通圖 G 的生成樹有且僅有 n-1條邊,若是少一條邊就不是連通圖,若是多一條邊就必定有環。可是,有 n-1 條邊的圖不必定是生成樹。圖 6.7 就是圖 6.3(a)的一棵生成樹。
1四、生成森林:在非連通圖中,由每一個連通份量均可獲得一個極小連通子圖,即一棵生成樹。這些連通份量的生成樹就組成了一個非連通圖的生成森林(Spanning Forest)。
圖的基本操做用一個接口來表示,爲表示圖的基本操做,同時給出了頂點類的實現。因爲頂點只保存自身信息,因此頂點類 Node<T>很簡單,裏面只有一個字段 data。
頂點的類 Node<T>的實現以下所示。
1 public class Node<T> 2 { 3 private T data; //數據域 4 //構造器 5 public Node(T v) 6 { 7 data = v; 8 } 9 //數據域屬性 10 public T Data 11 { 12 get 13 { 14 return data; 15 } 16 set 17 { 18 data = value; 19 } 20 } 21 }
圖的接口IGraph<T>的定義以下所示。
1 public interface IGraph<T> 2 { 3 //獲取頂點數 4 int GetNumOfVertex(); 5 //獲取邊或弧的數目 6 int GetNumOfEdge(); 7 //在兩個頂點之間添加權爲v的邊或弧 8 void SetEdge(Node<T> v1, Node<T> v2, int v); 9 //刪除兩個頂點之間的邊或弧 10 void DelEdge(Node<T> v1, Node<T> v2); 11 //判斷兩個頂點之間是否有邊或弧 12 bool IsEdge(Node<T> v1, Node<T> v2); 13 }
圖是一種複雜的數據結構,頂點之間是多對多的關係,即任意兩個頂點之間均可能存在聯繫。因此,沒法以頂點在存儲區的位置關係來表示頂點之間的聯繫,即順序存儲結構不能徹底存儲圖的信息,但能夠用數組來存儲圖的頂點信息。要存儲頂點之間的聯繫必須用鏈式存儲結構或者二維數組。圖的存儲結構有多種,這裏只介紹兩種基本的存儲結構:鄰接矩陣和鄰接表。
鄰接矩陣(Adjacency Matrix)是用兩個數組來表示圖,一個數組是一維數組,存儲圖中頂點的信息,一個數組是二維數組,即矩陣,存儲頂點之間相鄰的信息,也就是邊(或弧)的信息,這是鄰接矩陣名稱的由來。
假設圖G=(V, E)中有n個頂點,即V={v0,v1,…,vn-1},用矩陣A[i][j]表示邊(或弧)的信息。矩陣A[i][j]是一個n×n的矩陣,矩陣的元素爲:
若 G 是網,則鄰接矩陣可定義爲:
其中, wij 表示邊(vi,vj)或弧<vi,vj>上的權值;∞表示一個計算機容許的大於全部邊上權值的數。
下面三張圖分別對應鄰接矩陣以下:
從圖的鄰接矩陣表示法能夠看出這種表示法的特色是:
(1)無向圖或無向網的鄰接矩陣必定是一個對稱矩陣。所以,在具體存放鄰接矩陣時只需存放上(或下)三角矩陣的元素便可。
(2)能夠很方便地查找圖中任一頂點的度。對於無向圖或無向網而言,頂點 vi 的度就是鄰接矩陣中第 i 行或第 i 列中非 0 或非∞的元素的個數。對於有向圖或有向網而言,頂點 vi 的入度是鄰接矩陣中第 i 列中非 0 或非∞的元素的個數,頂點 vi 的出度是鄰接矩陣中第 i 行中非 0 或非∞的元素的個數。
(3)能夠很方便地查找圖中任一條邊或弧的權值,只要 A[i][j]爲 0 或∞,就說明頂點 vi 和 vj 之間不存在邊或弧。可是,要肯定圖中有多少條邊或弧,則必須按行、按列對每一個元素進行檢測,所花費的時間代價是很大的。這是用鄰接矩陣存儲圖的侷限性。
無向圖鄰接矩陣類 GraphAdjMatrix<T>中有三個成員字段,一個是 Node<T>類型的一維數組 nodes,存放圖中的頂點信息;一個是整型的二維數組 matirx,表示圖的鄰接矩陣,存放邊的信息;一個是整數 numEdges,表示圖中邊的數目。由於圖的鄰接矩陣存儲結構對於肯定圖中邊或弧的數目要花費很大的時間代價,因此設了這個字段。
無向圖鄰接矩陣類 GraphAdjMatrix<T>的實現以下所示。
1 /// <summary> 2 /// 無向圖鄰接矩陣類 GraphAdjMatrix<T>的實現 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class GraphAdjMatrix<T> : IGraph<T> 6 { 7 private Node<T>[] nodes;//頂點數組 8 private int[,] matrix;//鄰接矩陣數組 9 private int numEdges;//邊的數目 10 11 public GraphAdjMatrix(int n) 12 { 13 nodes = new Node<T>[n]; 14 matrix = new int[n, n]; 15 numEdges = 0; 16 } 17 18 //獲取索引爲index的頂點的信息 19 public Node<T> GetNode(int index) 20 { 21 return nodes[index]; 22 } 23 24 //設置索引爲index的頂點的信息 25 public void SetNode(int index, Node<T> v) 26 { 27 nodes[index] = v; 28 } 29 30 //獲取matrix[index1, index2]的值 31 public int GetMatrix(int index1, int index2) 32 { 33 return matrix[index1, index2]; 34 } 35 36 //設置matrix[index1, index2]的值 37 public void SetMatrix(int index1, int index2) 38 { 39 matrix[index1, index2] = 1; 40 } 41 42 //邊的數目屬性 43 public int NumEdges 44 { 45 get { return numEdges; } 46 set { numEdges = value; } 47 } 48 49 //獲取頂點的數目 50 public int GetNumOfVertex() 51 { 52 return nodes.Length; 53 } 54 55 //獲取邊的數目 56 public int GetNumOfEdge() 57 { 58 return numEdges; 59 } 60 61 //判斷v是不是圖的頂點 62 public bool IsNode(Node<T> v) 63 { 64 //遍歷頂點數組 65 foreach (Node<T> nd in nodes) 66 { 67 //若是頂點nd與v相等,則v是圖的頂點,返回true 68 if (v.Equals(nd)) 69 { 70 return true; 71 } 72 } 73 74 return false; 75 } 76 77 //獲取頂點v在頂點數組中的索引 78 public int GetIndex(Node<T> v) 79 { 80 int i = -1; 81 //遍歷頂點數組 82 for (i = 0; i < nodes.Length; ++i) 83 { 84 //若是頂點v與nodes[i]相等,則v是圖的頂點,返回索引值i。 85 if (nodes[i].Equals(v)) 86 { 87 return i; 88 } 89 } 90 return i; 91 } 92 93 //在頂點v1和v2之間添加權值爲v的邊 94 public void SetEdge(Node<T> v1, Node<T> v2, int v) 95 { 96 if (!IsNode(v1) || !IsNode(v2)) 97 { 98 Console.WriteLine("Node is not belong to Graph!"); 99 return; 100 } 101 102 //不是無向圖 103 if (v != 1) 104 { 105 Console.WriteLine("Weight is not right!"); 106 return; 107 } 108 109 //矩陣是對稱矩陣 110 matrix[GetIndex(v1), GetIndex(v2)] = v; 111 matrix[GetIndex(v2), GetIndex(v1)] = v; 112 ++numEdges; 113 } 114 115 //刪除頂點v1和v2之間的邊 116 public void DelEdge(Node<T> v1, Node<T> v2) 117 { 118 //v1或v2不是圖的頂點 119 if (!IsNode(v1) || !IsNode(v2)) 120 { 121 Console.WriteLine("Node is not belong to Graph!"); 122 return; 123 } 124 125 //頂點v1與v2之間存在邊 126 if (matrix[GetIndex(v1), GetIndex(v2)] == 1) 127 { 128 //矩陣是對稱矩陣 129 matrix[GetIndex(v1), GetIndex(v2)] = 0; 130 matrix[GetIndex(v2), GetIndex(v1)] = 0; 131 --numEdges; 132 } 133 } 134 135 //判斷頂點v1與v2之間是否存在邊 136 public bool IsEdge(Node<T> v1, Node<T> v2) 137 { 138 //v1或v2不是圖的頂點 139 if (!IsNode(v1) || !IsNode(v2)) 140 { 141 Console.WriteLine("Node is not belong to Graph!"); 142 return false; 143 } 144 145 //頂點v1與v2之間存在邊 146 if (matrix[GetIndex(v1), GetIndex(v2)] == 1) 147 { 148 return true; 149 } 150 else //不存在邊 151 { 152 return false; 153 } 154 } 155 156 }
鄰接表(Adjacency List)是圖的一種順序存儲與鏈式存儲相結合的存儲結構,相似於樹的孩子鏈表表示法。順序存儲指的是圖中的頂點信息用一個頂點數組來存儲,一個頂點數組元素是一個頂點結點,頂點結點有兩個域,一個是數據域data,存放與頂點相關的信息,一個是引用域 firstAdj,存放該頂點的鄰接表的第一個結點的地址。頂點的鄰接表是把全部鄰接於某頂點的頂點構成的一個表,它是採用鏈式存儲結構。因此,咱們說鄰接表是圖的一種順序存儲與鏈式存儲相結合的存儲結構。其中,鄰接表中的每一個結點實際上保存的是與該頂點相關的邊或弧的信息,它有兩個域,一個是鄰接頂點域 adjvex,存放鄰接頂點的信息,實際上就是鄰接頂點在頂點數組中的序號;一個是引用域 next,存放下一個鄰接頂點的結點的地址。頂點結點和鄰接表結點的結構以下圖所示。
而對於網的鄰接表結點還須要存儲邊上的信息(如權值),因此結點應增設 一個域 info。網的鄰接表結點的結構以下圖 所示。
圖 (a)的鄰接表以下圖 所示。
若無向圖中有 n 個頂點和 e 條邊,則它的鄰接表需 n 個頂點結點和 2e 個鄰接表結點,在邊稀疏 的狀況下,用鄰接表存儲圖比用鄰接矩陣節省存儲空間,當與邊相關的信息較多時更是如此。
在無向圖的鄰接表中,頂點 vi 的度恰爲第 i 個鄰接表中的結點數;而在有向圖中,第 i 的鄰接表中的結點數只是頂點 vi 的出度,爲求入度,必須遍歷整個鄰接表。在全部鄰接表中其鄰接頂點域的值爲 i 的結點的個數是頂點 vi 的入度。有時,爲了便於肯定頂點的入度或者以頂點 vi 爲頭的弧,能夠創建一個有向圖的逆鄰接表,即對每一個頂點 vi 創建一個以 vi 爲頭的弧的鄰接表。下圖(b)是(a)鄰接表和(b)逆鄰接表。
在創建鄰接表或逆鄰接表時,若輸入的頂點信息即爲頂點的編號,則創建鄰接表的時間複雜度爲 O(n+e),不然,須要查找才能獲得頂點在圖中的位置,則時間複雜度爲 O(n*e)。
在鄰接表上很容易找到任一頂點的第一個鄰接點和下一個鄰接點。但要斷定任意兩個頂點(vi 和 v j)之間是否有邊或弧相連,則需查找第 i 個或 j 個鄰接表,所以,不如鄰接矩陣方便。
下面以無向圖鄰接表類的實現來講明圖的鄰接表類的實現。
無向圖鄰接表的鄰接表結點類 adjListNode<T>有兩個成員字段,一個是adjvex,存儲鄰接頂點的信息,類型是整型;一個是 next,存儲下一個鄰接表結點的地址,類型是 adjListNode<T>。 adjListNode<T>的實現以下所示。
1 /// <summary> 2 /// 無向圖鄰接表類的實現 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class adjListNode<T> 6 { 7 private int adjvex;//鄰接頂點 8 private adjListNode<T> next;//下一個鄰接表結點 9 10 //鄰接頂點屬性 11 public int Adjvex 12 { 13 get { return adjvex; } 14 set { adjvex = value; } 15 } 16 17 //下一個鄰接表結點屬性 18 public adjListNode<T> Next 19 { 20 get { return next; } 21 set { next = value; } 22 } 23 24 public adjListNode(int vex) 25 { 26 adjvex = vex; 27 next = null; 28 } 29 }
無向圖鄰接表的頂點結點類 VexNode<T>有兩個成員字段,一個 data,它存儲圖的頂點自己的信息,類型是 Node<T>;一個是 firstAdj, 存儲頂點的鄰接表的第 1 個結點的地址,類型是 adjListNode<T>。 VexNode<T>的實現以下所示。
1 /// <summary> 2 /// 無向圖鄰接表的頂點結點類 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class VexNode<T> 6 { 7 private Node<T> data; //圖的頂點 8 private adjListNode<T> firstAdj; //鄰接表的第1個結點 9 10 public Node<T> Data 11 { 12 get { return data; } 13 set { data = value; } 14 } 15 16 //鄰接表的第1個結點屬性 17 public adjListNode<T> FirstAdj 18 { 19 get { return firstAdj; } 20 set { firstAdj = value; } 21 } 22 23 //構造器 24 public VexNode() 25 { 26 data = null; 27 firstAdj = null; 28 } 29 30 //構造器 31 public VexNode(Node<T> nd) 32 { 33 data = nd; 34 firstAdj = null; 35 } 36 37 //構造器 38 public VexNode(Node<T> nd, adjListNode<T> alNode) 39 { 40 data = nd; 41 firstAdj = alNode; 42 } 43 }
無向圖鄰接表類 GraphAdjList<T>有一個成員字段 adjList, 表示鄰接表數組,數組元素的類型是 VexNode<T>。 GraphAdjList<T>實現了接口 IGraph<T>中的方法。與無向圖鄰接矩陣類 GraphAdjMatrix<T>同樣, GraphAdjList<T>實現了兩個成員方法 IsNode 和 GetIndex。功能與 GraphAdjMatrix<T>同樣。無向圖鄰接表類 GraphAdjList<T>的實現以下所示。
1 /// <summary> 2 /// 無向圖鄰接表類 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class GraphAdjList<T> : IGraph<T> 6 { 7 //鄰接表數組 8 private VexNode<T>[] adjList; 9 10 //索引器 11 public VexNode<T> this[int index] 12 { 13 get 14 { 15 return adjList[index]; 16 } 17 set 18 { 19 adjList[index] = value; 20 } 21 } 22 23 //構造器 24 public GraphAdjList(Node<T>[] nodes) 25 { 26 adjList = new VexNode<T>[nodes.Length]; 27 for (int i = 0; i < nodes.Length; ++i) 28 { 29 adjList[i].Data = nodes[i]; 30 adjList[i].FirstAdj = null; 31 } 32 } 33 34 //獲取頂點的數目 35 public int GetNumOfVertex() 36 { 37 return adjList.Length; 38 } 39 40 //獲取邊的數目 41 public int GetNumOfEdge() 42 { 43 int i = 0; 44 45 foreach (VexNode<T> nd in adjList) 46 { 47 adjListNode<T> p = nd.FirstAdj; 48 while (p != null) 49 { 50 ++i; 51 p = p.Next; 52 } 53 } 54 55 return i / 2;//無向圖 56 } 57 58 //判斷v是不是圖的頂點 59 public bool IsNode(Node<T> v) 60 { 61 //遍歷鄰接表數組 62 foreach (VexNode<T> nd in adjList) 63 { 64 //若是v等於nd的data,則v是圖中的頂點,返回true 65 if (v.Equals(nd.Data)) 66 { 67 return true; 68 } 69 } 70 return false; 71 } 72 73 //獲取頂點v在鄰接表數組中的索引 74 public int GetIndex(Node<T> v) 75 { 76 int i = -1; 77 //遍歷鄰接表數組 78 for (i = 0; i < adjList.Length; ++i) 79 { 80 //鄰接表數組第i項的data值等於v,則頂點v的索引爲i 81 if (adjList[i].Data.Equals(v)) 82 { 83 return i; 84 } 85 } 86 return i; 87 } 88 89 //判斷v1和v2之間是否存在邊 90 public bool IsEdge(Node<T> v1, Node<T> v2) 91 { 92 //v1或v2不是圖的頂點 93 if (!IsNode(v1) || !IsNode(v2)) 94 { 95 Console.WriteLine("Node is not belong to Graph!"); 96 return false; 97 } 98 adjListNode<T> p = adjList[GetIndex(v1)].FirstAdj; 99 while (p != null) 100 { 101 if (p.Adjvex == GetIndex(v2)) 102 { 103 return true; 104 } 105 106 p = p.Next; 107 } 108 109 return false; 110 } 111 112 //在頂點v1和v2之間添加權值爲v的邊 113 public void SetEdge(Node<T> v1, Node<T> v2, int v) 114 { 115 //v1或v2不是圖的頂點或者v1和v2之間存在邊 116 if (!IsNode(v1) || !IsNode(v2) || IsEdge(v1, v2)) 117 { 118 Console.WriteLine("Node is not belong to Graph!"); 119 return; 120 } 121 122 //權值不對 123 if (v != 1) 124 { 125 Console.WriteLine("Weight is not right!"); 126 return; 127 } 128 129 //處理頂點v1的鄰接表 130 adjListNode<T> p = new adjListNode<T>(GetIndex(v2)); 131 132 if (adjList[GetIndex(v1)].FirstAdj == null) 133 { 134 adjList[GetIndex(v1)].FirstAdj = p; 135 } 136 //頂點v1有鄰接頂點 137 else 138 { 139 p.Next = adjList[GetIndex(v1)].FirstAdj; 140 adjList[GetIndex(v1)].FirstAdj = p; 141 } 142 143 //處理頂點v2的鄰接表 144 p = new adjListNode<T>(GetIndex(v1)); 145 146 //頂點v2沒有鄰接頂點 147 if (adjList[GetIndex(v2)].FirstAdj == null) 148 { 149 adjList[GetIndex(v2)].FirstAdj = p; 150 } 151 152 //頂點v2有鄰接頂點 153 else 154 { 155 p.Next = adjList[GetIndex(v2)].FirstAdj; 156 adjList[GetIndex(v2)].FirstAdj = p; 157 } 158 } 159 160 //刪除頂點v1和v2之間的邊 161 public void DelEdge(Node<T> v1, Node<T> v2) 162 { 163 //v1或v2不是圖的頂點 164 if (!IsNode(v1) || !IsNode(v2)) 165 { 166 Console.WriteLine("Node is not belong to Graph!"); 167 return; 168 } 169 170 //頂點v1與v2之間有邊 171 if (IsEdge(v1, v2)) 172 { 173 //處理頂點v1的鄰接表中的頂點v2的鄰接表結點 174 adjListNode<T> p = adjList[GetIndex(v1)].FirstAdj; 175 adjListNode<T> pre = null; 176 177 while (p != null) 178 { 179 if (p.Adjvex != GetIndex(v2)) 180 { 181 pre = p; 182 p = p.Next; 183 } 184 } 185 pre.Next = p.Next; 186 187 //處理頂點v2的鄰接表中的頂點v1的鄰接表結點 188 p = adjList[GetIndex(v2)].FirstAdj; 189 pre = null; 190 191 while (p != null) 192 { 193 if (p.Adjvex != GetIndex(v1)) 194 { 195 pre = p; 196 p = p.Next; 197 } 198 } 199 pre.Next = p.Next; 200 } 201 } 202 }
圖的遍歷是指從圖中的某個頂點出發,按照某種順序訪問圖中的每一個頂點,使每一個頂點被訪問一次且僅一次。圖的遍歷與樹的遍歷操做功能類似。圖的遍歷是圖的一種基本操做,圖的許多其餘操做都是創建在遍歷操做的基礎之上的。
然而,圖的遍歷要比樹的遍歷複雜得多。這是由於圖中的頂點之間是多對多的關係,圖中的任何一個頂點均可能和其它的頂點相鄰接。因此,在訪問了某個頂點以後,從該頂點出發,可能沿着某條路徑遍歷以後,又回到該頂點上。例如,在圖 (b)的圖中,因爲圖中存在迴路,所以在訪問了 v1、 v3、 v4 以後,沿着邊<v4, v1>又回到了 v1 上。爲了不同一頂點被訪問屢次,在遍歷圖的過程當中,必須記下每一個已訪問過的頂點。爲此,能夠設一個輔助數組 visited[n], n 爲圖中頂點的數目。數組中元素的初始值全爲 0,表示頂點都沒有被訪問過,若是頂點vi 被訪問, visited[i-1]爲 1。
圖的遍歷有深度優先遍歷和廣度優先遍歷兩種方式,它們對圖和網都適用。
圖的深度優先遍歷(Depth_First Search)相似於樹的先序遍歷,是樹的先序遍歷的推廣。
假設初始狀態是圖中全部頂點不曾被訪問過,則深度優先遍歷可從圖中某個頂點 v 出發,訪問此頂點,而後依次從 v 的未被訪問的鄰接頂點出發深度優先遍歷圖,直至圖中全部和 v 有路徑相通的頂點都被遍歷過。若此時圖中尚有未被訪問的頂點,則另選圖中一個未被訪問的頂點做爲起始點,重複上述過程,直到圖中全部頂點都被訪問到爲止。
按深度優先遍歷算法對圖 (a)進行遍歷。
圖 6.13(a)所示的無向圖的深度優先遍歷的過程如圖 6.13(b)所示。假設從頂點 v1 出發,在訪問了頂點 v1 以後,選擇鄰接頂點 v2,由於 v2 未被訪問過,所以從 v2 出發進行深度優先遍歷。依次類推,接着從 v4、 v8、 v5 出發進行深度優先遍歷。當訪問了 v5 以後,因爲 v5 的鄰接頂點 v2 和 v8 都已被訪問,因此遍歷退回到 v8。因爲一樣的理由,遍歷繼續退回到 v4、 v2 直到 v1。因爲 v1 的另外一個鄰接頂點 v3 未被訪問,因此又從 v3 開始進行深度優先遍歷,這樣獲得該圖的深度優先遍歷的頂點序列爲 v1→v2→v4→v8→v5→v3→v6→v7。
顯然,這是一個遞歸的過程。下面以無向圖的鄰接表存儲結構爲例來實現圖的深度優先遍歷算法。在類中增設了一個整型數組的成員字段 visited,它的初始值全爲 0,表示圖中全部的頂點都沒有被訪問過。若是頂點 vi 被訪問, visited[i-1]爲 1。而且,把該算法做爲無向圖的鄰接表類 GraphAdjList<T>的成員方法。
對無向圖的鄰接表類 GraphAdjList<T>進行修改後的算法實現以下:
1 /// <summary> 2 /// 無向圖鄰接表類 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class GraphAdjList<T> : IGraph<T> 6 { 7 //鄰接表數組 8 private VexNode<T>[] adjList; 9 10 //記錄圖中全部頂點的訪問狀況 11 private int[] visited; 12 13 //索引器 14 public VexNode<T> this[int index] 15 { 16 get 17 { 18 return adjList[index]; 19 } 20 set 21 { 22 adjList[index] = value; 23 } 24 } 25 26 //構造器 27 public GraphAdjList(Node<T>[] nodes) 28 { 29 adjList = new VexNode<T>[nodes.Length]; 30 for (int i = 0; i < nodes.Length; ++i) 31 { 32 adjList[i].Data = nodes[i]; 33 adjList[i].FirstAdj = null; 34 } 35 36 visited = new int[adjList.Length]; 37 for (int i = 0; i < visited.Length; ++i) 38 { 39 visited[i] = 0; 40 } 41 } 42 43 //獲取頂點的數目 44 public int GetNumOfVertex() 45 { 46 return adjList.Length; 47 } 48 49 //獲取邊的數目 50 public int GetNumOfEdge() 51 { 52 int i = 0; 53 54 foreach (VexNode<T> nd in adjList) 55 { 56 adjListNode<T> p = nd.FirstAdj; 57 while (p != null) 58 { 59 ++i; 60 p = p.Next; 61 } 62 } 63 64 return i / 2;//無向圖 65 } 66 67 //判斷v是不是圖的頂點 68 public bool IsNode(Node<T> v) 69 { 70 //遍歷鄰接表數組 71 foreach (VexNode<T> nd in adjList) 72 { 73 //若是v等於nd的data,則v是圖中的頂點,返回true 74 if (v.Equals(nd.Data)) 75 { 76 return true; 77 } 78 } 79 return false; 80 } 81 82 //獲取頂點v在鄰接表數組中的索引 83 public int GetIndex(Node<T> v) 84 { 85 int i = -1; 86 //遍歷鄰接表數組 87 for (i = 0; i < adjList.Length; ++i) 88 { 89 //鄰接表數組第i項的data值等於v,則頂點v的索引爲i 90 if (adjList[i].Data.Equals(v)) 91 { 92 return i; 93 } 94 } 95 return i; 96 } 97 98 //判斷v1和v2之間是否存在邊 99 public bool IsEdge(Node<T> v1, Node<T> v2) 100 { 101 //v1或v2不是圖的頂點 102 if (!IsNode(v1) || !IsNode(v2)) 103 { 104 Console.WriteLine("Node is not belong to Graph!"); 105 return false; 106 } 107 adjListNode<T> p = adjList[GetIndex(v1)].FirstAdj; 108 while (p != null) 109 { 110 if (p.Adjvex == GetIndex(v2)) 111 { 112 return true; 113 } 114 115 p = p.Next; 116 } 117 118 return false; 119 } 120 121 //在頂點v1和v2之間添加權值爲v的邊 122 public void SetEdge(Node<T> v1, Node<T> v2, int v) 123 { 124 //v1或v2不是圖的頂點或者v1和v2之間存在邊 125 if (!IsNode(v1) || !IsNode(v2) || IsEdge(v1, v2)) 126 { 127 Console.WriteLine("Node is not belong to Graph!"); 128 return; 129 } 130 131 //權值不對 132 if (v != 1) 133 { 134 Console.WriteLine("Weight is not right!"); 135 return; 136 } 137 138 //處理頂點v1的鄰接表 139 adjListNode<T> p = new adjListNode<T>(GetIndex(v2)); 140 141 if (adjList[GetIndex(v1)].FirstAdj == null) 142 { 143 adjList[GetIndex(v1)].FirstAdj = p; 144 } 145 //頂點v1有鄰接頂點 146 else 147 { 148 p.Next = adjList[GetIndex(v1)].FirstAdj; 149 adjList[GetIndex(v1)].FirstAdj = p; 150 } 151 152 //處理頂點v2的鄰接表 153 p = new adjListNode<T>(GetIndex(v1)); 154 155 //頂點v2沒有鄰接頂點 156 if (adjList[GetIndex(v2)].FirstAdj == null) 157 { 158 adjList[GetIndex(v2)].FirstAdj = p; 159 } 160 161 //頂點v2有鄰接頂點 162 else 163 { 164 p.Next = adjList[GetIndex(v2)].FirstAdj; 165 adjList[GetIndex(v2)].FirstAdj = p; 166 } 167 } 168 169 //刪除頂點v1和v2之間的邊 170 public void DelEdge(Node<T> v1, Node<T> v2) 171 { 172 //v1或v2不是圖的頂點 173 if (!IsNode(v1) || !IsNode(v2)) 174 { 175 Console.WriteLine("Node is not belong to Graph!"); 176 return; 177 } 178 179 //頂點v1與v2之間有邊 180 if (IsEdge(v1, v2)) 181 { 182 //處理頂點v1的鄰接表中的頂點v2的鄰接表結點 183 adjListNode<T> p = adjList[GetIndex(v1)].FirstAdj; 184 adjListNode<T> pre = null; 185 186 while (p != null) 187 { 188 if (p.Adjvex != GetIndex(v2)) 189 { 190 pre = p; 191 p = p.Next; 192 } 193 } 194 pre.Next = p.Next; 195 196 //處理頂點v2的鄰接表中的頂點v1的鄰接表結點 197 p = adjList[GetIndex(v2)].FirstAdj; 198 pre = null; 199 200 while (p != null) 201 { 202 if (p.Adjvex != GetIndex(v1)) 203 { 204 pre = p; 205 p = p.Next; 206 } 207 } 208 pre.Next = p.Next; 209 } 210 } 211 212 //無向圖的深度優先遍歷算法的實現 213 public void DFS() 214 { 215 for (int i = 0; i < visited.Length; ++i) 216 { 217 if (visited[i] == 0) 218 { 219 DFSAL(i); 220 } 221 } 222 } 223 224 //從某個頂點出發進行深度優先遍歷 225 public void DFSAL(int i) 226 { 227 visited[i] = 1; 228 adjListNode<T> p = adjList[i].FirstAdj; 229 while (p != null) 230 { 231 if (visited[p.Adjvex] == 0) 232 { 233 DFSAL(p.Adjvex); 234 } 235 p = p.Next; 236 } 237 238 } 239 }
分析上面的算法,在遍歷圖時,對圖中每一個頂點至多調用一次DFS方法,因爲一旦某個頂點被標記成已被訪問,就再也不從它出發進行遍歷。所以,遍歷圖的過程實質上是對每一個頂點查找其鄰接頂點的過程。其時間複雜度取決於所採用的存儲結構。當圖採用鄰接矩陣做爲存儲結構時,查找每一個頂點的鄰接頂點的時間複雜度爲O(n2),其中,n爲圖的頂點數。而以鄰接表做爲圖的存儲結構時,查找鄰接頂點的時間複雜度爲O(e),其中,e爲圖中邊或弧的數目。所以,當以鄰接表做爲存儲結構時,深度優先遍歷圖的時間複雜度爲O(n+e)。
圖的廣度優先遍歷(Breadth_First Search)相似於樹的層序遍歷。
假設從圖中的某個頂點 v 出發,訪問了 v 以後,依次訪問 v 的各個不曾訪問的鄰接頂點。而後分別從這些鄰接頂點出發依次訪問它們的鄰接頂點,並使「先被訪問的頂點的鄰接頂點」先於「後被訪問的頂點的鄰接頂點」被訪問,直至圖中全部已被訪問的頂點的鄰接頂點都被訪問。若此時圖中尚有頂點未被訪問,則另選圖中未被訪問的頂點做爲起點,重複上述過程,直到圖中全部的頂點都被訪問爲止。換句話說,廣度優先遍歷圖的過程是以某個頂點 v 做爲起始點,由近至遠,依次訪問和 v 有路徑相通且路徑長度爲 1, 2,…的頂點。
按廣度優先遍歷算法對上圖 (a)進行遍歷
上圖 (a)所示的無向圖的廣度優先遍歷的過程如上圖 (c)所示。假設從頂點 v1 開始進行廣度優先遍歷,首先訪問頂點 v1 和它的鄰接頂點 v2 和 v3,而後依次訪問 v2 的鄰接頂點 v4 和 v5,以及 v3 的鄰接頂點 v6 和 v7,最後訪問 v4的鄰接頂點 v8。因爲這些頂點的鄰接頂點都已被訪問,而且圖中全部頂點都已被訪問,由此完成了圖的遍歷,獲得的頂點訪問序列爲: v1→v2→v3→v4→v5→v6→v7→v8,其遍歷過程如圖 (c)所示。
和深度優先遍歷相似,在廣度優先遍歷中也須要一個訪問標記數組,咱們採用與深度優先遍歷一樣的數組。而且,爲了順序訪問路徑長度爲 1, 2,…的頂點,需在算法中附設一個隊列來存儲已被訪問的路徑長度爲 1, 2,…的頂點。
以鄰接表做爲存儲結構的無向圖的廣度優先遍歷算法的實現以下,隊列是循環順序隊列。
對無向圖的鄰接表類 GraphAdjList<T>進行修改後的算法實現以下:
1 /// <summary> 2 /// 無向圖鄰接表類 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class GraphAdjList<T> : IGraph<T> 6 { 7 //鄰接表數組 8 private VexNode<T>[] adjList; 9 10 //記錄圖中全部頂點的訪問狀況 11 private int[] visited; 12 13 //索引器 14 public VexNode<T> this[int index] 15 { 16 get 17 { 18 return adjList[index]; 19 } 20 set 21 { 22 adjList[index] = value; 23 } 24 } 25 26 //構造器 27 public GraphAdjList(Node<T>[] nodes) 28 { 29 adjList = new VexNode<T>[nodes.Length]; 30 for (int i = 0; i < nodes.Length; ++i) 31 { 32 adjList[i].Data = nodes[i]; 33 adjList[i].FirstAdj = null; 34 } 35 36 visited = new int[adjList.Length]; 37 for (int i = 0; i < visited.Length; ++i) 38 { 39 visited[i] = 0; 40 } 41 } 42 43 //獲取頂點的數目 44 public int GetNumOfVertex() 45 { 46 return adjList.Length; 47 } 48 49 //獲取邊的數目 50 public int GetNumOfEdge() 51 { 52 int i = 0; 53 54 foreach (VexNode<T> nd in adjList) 55 { 56 adjListNode<T> p = nd.FirstAdj; 57 while (p != null) 58 { 59 ++i; 60 p = p.Next; 61 } 62 } 63 64 return i / 2;//無向圖 65 } 66 67 //判斷v是不是圖的頂點 68 public bool IsNode(Node<T> v) 69 { 70 //遍歷鄰接表數組 71 foreach (VexNode<T> nd in adjList) 72 { 73 //若是v等於nd的data,則v是圖中的頂點,返回true 74 if (v.Equals(nd.Data)) 75 { 76 return true; 77 } 78 } 79 return false; 80 } 81 82 //獲取頂點v在鄰接表數組中的索引 83 public int GetIndex(Node<T> v) 84 { 85 int i = -1; 86 //遍歷鄰接表數組 87 for (i = 0; i < adjList.Length; ++i) 88 { 89 //鄰接表數組第i項的data值等於v,則頂點v的索引爲i 90 if (adjList[i].Data.Equals(v)) 91 { 92 return i; 93 } 94 } 95 return i; 96 } 97 98 //判斷v1和v2之間是否存在邊 99 public bool IsEdge(Node<T> v1, Node<T> v2) 100 { 101 //v1或v2不是圖的頂點 102 if (!IsNode(v1) || !IsNode(v2)) 103 { 104 Console.WriteLine("Node is not belong to Graph!"); 105 return false; 106 } 107 adjListNode<T> p = adjList[GetIndex(v1)].FirstAdj; 108 while (p != null) 109 { 110 if (p.Adjvex == GetIndex(v2)) 111 { 112 return true; 113 } 114 115 p = p.Next; 116 } 117 118 return false; 119 } 120 121 //在頂點v1和v2之間添加權值爲v的邊 122 public void SetEdge(Node<T> v1, Node<T> v2, int v) 123 { 124 //v1或v2不是圖的頂點或者v1和v2之間存在邊 125 if (!IsNode(v1) || !IsNode(v2) || IsEdge(v1, v2)) 126 { 127 Console.WriteLine("Node is not belong to Graph!"); 128 return; 129 } 130 131 //權值不對 132 if (v != 1) 133 { 134 Console.WriteLine("Weight is not right!"); 135 return; 136 } 137 138 //處理頂點v1的鄰接表 139 adjListNode<T> p = new adjListNode<T>(GetIndex(v2)); 140 141 if (adjList[GetIndex(v1)].FirstAdj == null) 142 { 143 adjList[GetIndex(v1)].FirstAdj = p; 144 } 145 //頂點v1有鄰接頂點 146 else 147 { 148 p.Next = adjList[GetIndex(v1)].FirstAdj; 149 adjList[GetIndex(v1)].FirstAdj = p; 150 } 151 152 //處理頂點v2的鄰接表 153 p = new adjListNode<T>(GetIndex(v1)); 154 155 //頂點v2沒有鄰接頂點 156 if (adjList[GetIndex(v2)].FirstAdj == null) 157 { 158 adjList[GetIndex(v2)].FirstAdj = p; 159 } 160 161 //頂點v2有鄰接頂點 162 else 163 { 164 p.Next = adjList[GetIndex(v2)].FirstAdj; 165 adjList[GetIndex(v2)].FirstAdj = p; 166 } 167 } 168 169 //刪除頂點v1和v2之間的邊 170 public void DelEdge(Node<T> v1, Node<T> v2) 171 { 172 //v1或v2不是圖的頂點 173 if (!IsNode(v1) || !IsNode(v2)) 174 { 175 Console.WriteLine("Node is not belong to Graph!"); 176 return; 177 } 178 179 //頂點v1與v2之間有邊 180 if (IsEdge(v1, v2)) 181 { 182 //處理頂點v1的鄰接表中的頂點v2的鄰接表結點 183 adjListNode<T> p = adjList[GetIndex(v1)].FirstAdj; 184 adjListNode<T> pre = null; 185 186 while (p != null) 187 { 188 if (p.Adjvex != GetIndex(v2)) 189 { 190 pre = p; 191 p = p.Next; 192 } 193 } 194 pre.Next = p.Next; 195 196 //處理頂點v2的鄰接表中的頂點v1的鄰接表結點 197 p = adjList[GetIndex(v2)].FirstAdj; 198 pre = null; 199 200 while (p != null) 201 { 202 if (p.Adjvex != GetIndex(v1)) 203 { 204 pre = p; 205 p = p.Next; 206 } 207 } 208 pre.Next = p.Next; 209 } 210 } 211 212 //無向圖的深度優先遍歷算法的實現 213 public void DFS() 214 { 215 for (int i = 0; i < visited.Length; ++i) 216 { 217 if (visited[i] == 0) 218 { 219 DFSAL(i); 220 } 221 } 222 } 223 224 //從某個頂點出發進行深度優先遍歷 225 public void DFSAL(int i) 226 { 227 visited[i] = 1; 228 adjListNode<T> p = adjList[i].FirstAdj; 229 while (p != null) 230 { 231 if (visited[p.Adjvex] == 0) 232 { 233 DFSAL(p.Adjvex); 234 } 235 p = p.Next; 236 } 237 238 } 239 240 //無向圖的廣度優先遍歷算法的實現 241 public void BFS() 242 { 243 for (int i = 0; i < visited.Length; ++i) 244 { 245 if (visited[i] == 0) 246 { 247 BFSAL(i); 248 } 249 } 250 } 251 252 //從某個頂點出發進行廣度優先遍歷 253 public void BFSAL(int i) 254 { 255 visited[i] = 1; 256 CSeqQueue<int> cq = new CSeqQueue<int>(visited.Length); 257 cq.In(i); 258 while (!cq.IsEmpty()) 259 { 260 int k = cq.Out(); 261 adjListNode<T> p = adjList[k].FirstAdj; 262 263 while(p != null) 264 { 265 if (visited[p.Adjvex] == 0) 266 { 267 visited[p.Adjvex] = 1; 268 cq.In(p.Adjvex); 269 } 270 271 p = p.Next; 272 } 273 } 274 } 275 }
分析上面的算法,每一個頂點至多入隊列一次。遍歷圖的過程實質上是經過邊或弧查找鄰接頂點的過程,所以,廣度優先遍歷算法的時間複雜度與深度優先遍歷相同,二者的不一樣之處在於對頂點的訪問順序不一樣。
一、 最小生成樹的基本概念
由生成樹的定義可知,無向連通圖的生成樹不是惟一的,對連通圖的不一樣遍歷就獲得不一樣的生成樹。下圖所示是圖 (a)所示的無向連通圖的部分生成樹。
若是是一個無向連通網,那麼它的全部生成樹中必有一棵邊的權值總和最小的生成樹,咱們稱這棵生成樹爲最小代價生成樹(Minimum Cost Spanning Tree),簡稱最小生成樹。
許多應用問題都是一個求無向連通網的最小生成樹問題。例如,要在 n 個城市之間鋪設光纜,鋪設光纜的費用很高,而且各個城市之間鋪設光纜的費用不一樣。一個目標是要使這 n 個城市的任意兩個之間均可以直接或間接通訊,另外一個目標是要使鋪設光纜的總費用最低。若是把 n 個城市看做是圖的 n 個頂點,兩個城市之間鋪設的光纜看做是兩個頂點之間的邊,這實際上就是求一個無向連通網的最小生成樹問題。
由最小生成樹的定義可知,構造有 n 個頂點的無向連通網的最小生成樹必須知足如下三個條件:
(1)構造的最小生成樹必須包括 n 個頂點;
(2)構造的最小生成樹有且僅有 n-1 條邊;
(3)構造的最小生成樹中不存在迴路。
構造最小生成樹的方法有許多種,典型的方法有兩種,一種是普里姆(Prim)算法,一種是克魯斯卡爾(Kruskal)算法。
二、 普里姆(Prim)算法
假設 G=(V, E)爲一無向連通網,其中, V 爲網中頂點的集合, E 爲網中邊的集合。設置兩個新的集合 U 和 T,其中, U 爲 G 的最小生成樹的頂點的集合, T 爲 G 的最小生成樹的邊的集合。普里姆算法的思想是:令集合 U 的初值爲 U={u1}(假設構造最小生成樹時從頂點 u1 開始),集合 T 的初值爲 T={}。從全部的頂點 u∈U 和頂點 v∈V-U 的帶權邊中選出具備最小權值的邊(u,v),將頂點 v 加入集合 U 中,將邊(u,v)加入集合 T 中。如此不斷地重複直到 U=V 時,最小生成樹構造完畢。此時,集合 U 中存放着最小生成樹的全部頂點,集合 T中存放着最小生成樹的全部邊。
如下圖(a)爲例,說明用普里姆算法構造圖的無向連通網的最小生成樹的過程。
爲了分析問題的方便,把圖 (a)中所示的無向連通網從新畫在下圖 中,如圖 (a)所示。初始時,算法的集合 U={A},集合 V-U={B,C,D,E},集合 T={},如圖 (b)所示。在全部 u 爲集合 U 中頂點、 v 爲集合 V-U 中頂點的邊(u,v)中尋找具備最小權值的邊,尋找到的邊是(A,D),權值爲 20,把頂點 B 加入到集合U 中,把邊(A,D)加入到集合 T 中,如圖 (c)所示。在全部 u 爲集合 U 中頂點、v 爲集合 V-U 中頂點的邊(u,v)中尋找具備最小權值的邊,尋找到的邊是(D,E),權值爲 10,把頂點 E 加入到集合 U 中,把邊(D,E)加入到集合 T 中,如圖 (d)所示。隨後依次從集合 V-U 中加入到集合 U 中的頂點爲 B、 C,依次加入到集合T 中的邊爲(A,B)(權值爲 60)、 (E,C) (權值爲 70),分別如圖 (e)、 (f)所示。最後獲得的圖 (f)所示就是原無向連通網的最小生成樹。
本 文以 無 向 網 的 鄰 接 矩 陣 類 NetAdjMatrix<T> 來 實 現 普 裏 姆 算 法 。NetAdjMatrix<T>類的成員字段與無向圖鄰接矩陣類 GraphAdjMatrix<T>的成員字段同樣,不一樣的是,當兩個頂點間有邊相鏈接時, matirx 數組中相應元素的值是邊的權值,而不是 1。
無向網鄰接矩陣類 NetAdjMatrix<T>的實現以下所示。
1 /// <summary> 2 /// 無向網鄰接矩陣類 NetAdjMatrix<T>的實現以下所示。 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class NetAdjMatrix<T> : IGraph<T> 6 { 7 private Node<T>[] nodes;//頂點數組 8 private int numEdges;//邊的數目 9 private int[,] matrix;//鄰接矩陣數組 10 11 public NetAdjMatrix(int n) 12 { 13 nodes = new Node<T>[n]; 14 matrix = new int[n, n]; 15 numEdges = 0; 16 } 17 18 //獲取索引爲index的頂點的信息 19 public Node<T> GetNode(int index) 20 { 21 return nodes[index]; 22 } 23 24 //設置索引爲index的頂點的信息 25 public void SetNode(int index, Node<T> v) 26 { 27 nodes[index] = v; 28 } 29 30 //獲取matrix[index1, index2]的值 31 public int GetMatrix(int index1, int index2) 32 { 33 return matrix[index1, index2]; 34 } 35 36 //設置matrix[index1, index2]的值 37 public void SetMatrix(int index1, int index2, int v) 38 { 39 matrix[index1, index2] = v; 40 } 41 42 //邊的數目屬性 43 public int NumEdges 44 { 45 get { return numEdges; } 46 set { numEdges = value; } 47 } 48 49 //獲取頂點的數目 50 public int GetNumOfVertex() 51 { 52 return nodes.Length; 53 } 54 55 //獲取邊的數目 56 public int GetNumOfEdge() 57 { 58 return numEdges; 59 } 60 61 //判斷v是不是網的頂點 62 public bool IsNode(Node<T> v) 63 { 64 //遍歷頂點數組 65 foreach (Node<T> nd in nodes) 66 { 67 //若是頂點nd與v相等,則v是圖的頂點,返回true 68 if (v.Equals(nd)) 69 { 70 return true; 71 } 72 } 73 74 return false; 75 } 76 77 //獲取頂點v在頂點數組中的索引 78 public int GetIndex(Node<T> v) 79 { 80 int i = -1; 81 82 //遍歷頂點數組 83 for (i = 0; i < nodes.Length; ++i) 84 { 85 //若是頂點v與nodes[i]相等,則v是網的頂點,返回索引值i。 86 if (nodes[i].Equals(v)) 87 { 88 return i; 89 } 90 } 91 return i; 92 } 93 94 //在頂點v1和v2之間添加權值爲v的邊 95 public void SetEdge(Node<T> v1, Node<T> v2, int v) 96 { 97 if (!IsNode(v1) || !IsNode(v2)) 98 { 99 Console.WriteLine("Node is not belong to Net!"); 100 return; 101 } 102 103 //矩陣是對稱矩陣 104 matrix[GetIndex(v1), GetIndex(v2)] = v; 105 matrix[GetIndex(v2), GetIndex(v1)] = v; 106 ++numEdges; 107 } 108 109 //刪除頂點v1和v2之間的邊 110 public void DelEdge(Node<T> v1, Node<T> v2) 111 { 112 //v1或v2不是無線網的頂點 113 if (!IsNode(v1) || !IsNode(v2)) 114 { 115 Console.WriteLine("Node is not belong to Net!"); 116 return; 117 } 118 119 //頂點v1與v2之間存在邊 120 if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue) 121 { 122 //矩陣是對稱矩陣 123 matrix[GetIndex(v1), GetIndex(v2)] = 0; 124 matrix[GetIndex(v2), GetIndex(v1)] = 0; 125 --numEdges; 126 } 127 } 128 129 //判斷頂點v1與v2之間是否存在邊 130 public bool IsEdge(Node<T> v1, Node<T> v2) 131 { 132 //v1或v2不是無向網的頂點 133 if (!IsNode(v1) || !IsNode(v2)) 134 { 135 Console.WriteLine("Node is not belong to Net!"); 136 return false; 137 } 138 139 //頂點v1與v2之間存在邊 140 if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue) 141 { 142 return true; 143 } 144 else //不存在邊 145 { 146 return false; 147 } 148 } 149 }
爲實現普里姆算法,須要設置兩個輔助一維數組 lowcost 和 closevex, lowcost用來保存集合 V-U 中各頂點與集合 U 中各頂點構成的邊中具備最小權值的邊的權值; closevex 用來保存依附於該邊的在集合 U 中的頂點。假設初始狀態時,U={u1}(u1 爲出發的頂點),這時有 lowcost[0]=0,它表示頂點 u1 已加入集合 U中。數組 lowcost 元素的值是頂點 u1 到其餘頂點所構成的直接邊的權值。而後不斷選取權值最小的邊(ui,uk)(ui∈U,uk∈V-U),每選取一條邊,就將 lowcost[k]置爲 0,表示頂點 uk 已加入集合 U 中。因爲頂點 uk 從集合 V-U 進入集合 U 後,這兩個集合的內容發生了變化,就須要依據具體狀況更新數組 lowcost 和 closevex中部分元素的值。把普里姆算法 Prim 做爲 NetAdjMatrix<T>類的成員方法。
普里姆算法 Prim 的實現以下:
1 public int[] Prim() 2 { 3 int[] lowcost = new int[nodes.Length];//權值數組 4 int[] closevex = new int[nodes.Length];//頂點數組 5 int mincost = int.MaxValue;//最小權值 6 7 //輔助數組初始化 8 for (int i = 1; i < nodes.Length; ++i) 9 { 10 lowcost[i] = matrix[0, i]; 11 closevex[i] = 0; 12 } 13 14 //某個頂點加入集合U 15 lowcost[0] = 0; 16 closevex[0] = 0; 17 18 for (int i = 0; i < nodes.Length; ++i) 19 { 20 int k = 1; 21 int j = 1; 22 //選取權值最小的邊和相應的頂點 23 while (j < nodes.Length) 24 { 25 if (lowcost[j] < mincost && lowcost[j] != 0) 26 { 27 k = j; 28 } 29 ++j; 30 } 31 //新頂點加入集合U 32 lowcost[k] = 0; 33 //從新計算該頂點到其他頂點的邊的權值 34 for (j = 1; j < nodes.Length; ++j) 35 { 36 if (matrix[k, j] < lowcost[j]) 37 { 38 lowcost[j] = matrix[k, j]; 39 closevex[j] = k; 40 } 41 } 42 } 43 44 return closevex; 45 }
下表給出了在用普里姆算法構造圖 (a)的最小生成樹的過程當中數組closevex 和 lowcost 及集合 U, V-U 的變化狀況,讀者可進一步加深對普里姆算法的理解。
在普里姆算法中,第一個for循環的執行次數爲n-1,第二個for循環中又包括了一個while循環和一個for循環,執行次數爲 2(n-1)2,因此普里姆算法的時間復雜度爲O(n2)。
下表在用普里姆算法構造圖 (a)的最小生成樹的過程當中參數變化
三、 克魯斯卡爾(Kruskal)算法
克魯斯卡爾算法的基本思想是:對一個有 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 條邊,因此求圖 (a)的無向連通網的最小生成樹的過程已經完成,以下圖所示。這個結果與用普里姆算法獲得的結果相同。
一、 最短路徑的概念
最短路徑問題是圖的又一個比較典型的應用問題。例如, 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)~(f)給出了狄克斯特拉算法求從頂點 A 到其他頂點的最短路徑的過程。圖中虛線表示當前可選擇的邊,實線表示算法已肯定包括到集合 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, C,B, E),其他的路徑則不變。而後,從已更新的路徑中找出路徑長度最小的頂點 E(從源點到頂點 E 的最短路徑爲 28)。
三、 有向網的鄰接矩陣類的實現
本文以有向網的鄰接矩陣類 DirecNetAdjMatrix<T>來實現狄克斯特拉算法。DirecNetAdjMatrix<T>有三個成員字段,一個是 Node<T>類型的一維數組 nodes,存放有向網中的頂點信息,一個是整型的二維數組 matirx,表示有向網的鄰接矩陣,存放弧的信息,一個是整數 numArcs,表示有向網中弧的數目,有向網的鄰接矩陣類 DirecNetAdjMatrix<T>的實現以下所示。
1 /// <summary> 2 /// 有向網的鄰接矩陣類 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class DirecNetAdjMatrix<T> : IGraph<T> 6 { 7 private Node<T>[] nodes; //有向網的頂點數組 8 private int numArcs; //弧的數目 9 private int[,] matrix; //鄰接矩陣數組 10 11 public DirecNetAdjMatrix(int n) 12 { 13 nodes = new Node<T>[n]; 14 matrix = new int[n, n]; 15 numArcs = 0; 16 } 17 18 //獲取索引爲index的頂點的信息 19 public Node<T> GetNode(int index) 20 { 21 return nodes[index]; 22 } 23 24 //設置索引爲index的頂點的信息 25 public void SetNode(int index, Node<T> v) 26 { 27 nodes[index] = v; 28 } 29 30 //獲取matrix[index1, index2]的值 31 public int GetMatrix(int index1, int index2) 32 { 33 return matrix[index1, index2]; 34 } 35 36 //設置matrix[index1, index2]的值 37 public void SetMatrix(int index1, int index2, int v) 38 { 39 matrix[index1, index2] = v; 40 } 41 42 //弧的數目屬性 43 public int NumArcs 44 { 45 get { return numArcs; } 46 set { numArcs = value; } 47 } 48 49 //獲取頂點的數目 50 public int GetNumOfVertex() 51 { 52 return nodes.Length; 53 } 54 55 //獲取弧的數目 56 public int GetNumOfEdge() 57 { 58 return numArcs; 59 } 60 61 //判斷v是不是網的頂點 62 public bool IsNode(Node<T> v) 63 { 64 //遍歷頂點數組 65 foreach (Node<T> nd in nodes) 66 { 67 //若是頂點nd與v相等,則v是網的頂點,返回true 68 if (v.Equals(nd)) 69 { 70 return true; 71 } 72 } 73 74 return false; 75 } 76 77 //獲取頂點v在頂點數組中的索引 78 public int GetIndex(Node<T> v) 79 { 80 int i = -1; 81 82 //遍歷頂點數組 83 for (i = 0; i < nodes.Length; ++i) 84 { 85 //若是頂點v與nodes[i]相等,則v是網的頂點,返回索引值i。 86 if (nodes[i].Equals(v)) 87 { 88 return i; 89 } 90 } 91 return i; 92 } 93 94 //在頂點v1和v2之間添加權值爲v的弧 95 public void SetEdge(Node<T> v1, Node<T> v2, int v) 96 { 97 if (!IsNode(v1) || !IsNode(v2)) 98 { 99 Console.WriteLine("Node is not belong to Net!"); 100 return; 101 } 102 103 //有向 104 matrix[GetIndex(v1), GetIndex(v2)] = v; 105 ++numArcs; 106 } 107 108 //刪除頂點v1和v2之間的弧 109 public void DelEdge(Node<T> v1, Node<T> v2) 110 { 111 //v1或v2不是無線網的頂點 112 if (!IsNode(v1) || !IsNode(v2)) 113 { 114 Console.WriteLine("Node is not belong to Net!"); 115 return; 116 } 117 118 //有向 119 if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue) 120 { 121 matrix[GetIndex(v1), GetIndex(v2)] = int.MaxValue; 122 --numArcs; 123 } 124 } 125 126 //判斷頂點v1與v2之間是否存在弧 127 public bool IsEdge(Node<T> v1, Node<T> v2) 128 { 129 //v1或v2不是無向網的頂點 130 if (!IsNode(v1) || !IsNode(v2)) 131 { 132 Console.WriteLine("Node is not belong to Net!"); 133 return false; 134 } 135 136 //頂點v1與v2之間存在弧 137 if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue) 138 { 139 return true; 140 } 141 else //不存在弧 142 { 143 return false; 144 } 145 } 146 }
四、 狄克斯特拉算法的實現
爲實現狄克斯特拉算法,引入兩個數組,一個一維數組 ShortPathArr,用來保存從源點到各個頂點的最短路徑的長度,一個二維數組 PathMatrixArr,用來保存從源點到某個頂點的最短路徑上的頂點,如 PathMatrix[v][w]爲 true,則 w爲從源點到頂點 v 的最短路徑上的頂點。爲了該算法的結果被其餘算法使用,把這兩個數組做爲算法的參數使用。另外,爲了表示某頂點的最短路徑是否已經找到,在算法中設了一個一維數組 final,若是 final[i]爲 true,則表示已經找到第 i 個頂點的最短路徑。 i 是該頂點在鄰接矩陣中的序號。一樣,把該算法做爲類DirecNetAdjMatrix<T>的成員方法來實現。
1 /// <summary> 2 /// 狄克斯特拉算法 3 /// </summary> 4 /// <param name="pathMatricArr">保存從源點到某個頂點的最短路徑上的頂點,如 PathMatrix[v][w]爲 true,則 w爲從源點到頂點 v 的最短路徑上的頂點</param> 5 /// <param name="shortPathArr"保存從源點到各個頂點的最短路徑的長度></param> 6 /// <param name="n">源點</param> 7 public void Dijkstra(ref bool[,] pathMatricArr, ref int[] shortPathArr, Node<T> n) 8 { 9 int k = 0; 10 bool[] final = new bool[nodes.Length]; 11 12 //初始化 13 for (int i = 0; i < nodes.Length; ++i) 14 { 15 final[i] = false; 16 shortPathArr[i] = matrix[GetIndex(n), i]; 17 18 for (int j = 0; j < nodes.Length; ++j) 19 { 20 pathMatricArr[i, j] = false; 21 } 22 23 if (shortPathArr[i] != 0 && shortPathArr[i] < int.MaxValue) 24 { 25 pathMatricArr[i, GetIndex(n)] = true; 26 pathMatricArr[i, i] = true; 27 28 } 29 } 30 31 // n爲源點 32 shortPathArr[GetIndex(n)] = 0; 33 final[GetIndex(n)] = true; 34 35 //處理從源點到其他頂點的最短路徑 36 for (int i = 0; i < nodes.Length; ++i) 37 { 38 int min = int.MaxValue; 39 40 //比較從源點到其他頂點的路徑長度 41 for (int j = 0; j < nodes.Length; ++j) 42 { 43 //從源點到j頂點的最短路徑尚未找到 44 if (!final[j]) 45 { 46 //從源點到j頂點的路徑長度最小 47 if (shortPathArr[j] < min) 48 { 49 k = j; 50 min = shortPathArr[j]; 51 } 52 } 53 } 54 55 //源點到頂點k的路徑長度最小 56 final[k] = true; 57 58 //更新當前最短路徑及距離 59 for (int j = 0; j < nodes.Length; ++j) 60 { 61 if (!final[j] && (min + matrix[k, j] < shortPathArr[j])) 62 { 63 shortPathArr[j] = min + matrix[k, j]; 64 for (int w = 0; w < nodes.Length; ++w) 65 { 66 pathMatricArr[j, w] = pathMatricArr[k, w]; 67 } 68 pathMatricArr[j, j] = true; 69 } 70 } 71 } 72 }
拓撲排序(Topological Sort)是圖中重要的運算之一,在實際中應用很普遍。例如,不少工程均可分爲若干個具備獨立性的子工程,咱們把這些子工程稱爲「活動」。每一個活動之間有時存在必定的先決條件關係,即在時間上有着必定的相互制約的關係。也就是說,有些活動必須在其它活動完成以後才能開始,即某項活動的開始必須以另外一項活動的完成爲前提。在有向圖中,若以圖中的頂點表示活動,以弧表示活動之間的優先關係,這樣的有向圖稱爲 AOV 網(Active On VertexNetwork)。
在 AOV 網中,若從頂點 vi 到頂點 vj 之間存在一條有向路徑,則稱 vi 是 vj的前驅, vj 是 vi 的後繼。若<vi,vj>是 AOV 網中的弧,則稱 vi 是 vj 的直接前驅,vj 是 vi 的直接後繼。
例如,一個軟件專業的學生必須學習一系列的基本課程(以下表所示)。其中,有些課程是基礎課,如「高等數學」、「程序設計基礎」,這些課程不須要先修課程,而另外一些課程必須在先學完某些課程以後才能開始學習。如一般在學完「程序設計基礎」和「離散數學」以後纔開始學習「數據結構」等等。所以,能夠用 AOV 網來表示各課程及其之間的關係。
在 AOV 網中,不該該出現有向環路,由於有環意味着某項活動以本身做爲先決條件,這樣就進入了死循環。若是圖 6.19 的有向圖出現了有向環路,則教學計劃將沒法編排。所以,對給定的 AOV 網應首先斷定網中是否存在環。檢測的辦法是對有向圖進行拓撲排序(Topological Sort),若網中全部頂點都在它的拓撲有序序列中,則 AOV 網中一定不存在環。
實現一個有向圖的拓撲有序序列的過程稱爲拓撲排序。能夠證實,任何一個有向無環圖,其所有頂點均可以排成一個拓撲序列,而其拓撲有序序列不必定是惟一的。例如,上圖的有向圖的拓撲有序序列有多個,這裏列舉兩個以下:
(c1,c2,c3,c4,c5,c7,c8,c9,c10,c11,c6,c12,c8)和 (c9,c10,c11,c6,c1,c12,c4,c2,c3,c5,c7,c8)
由上面兩個序列可知,對於圖中沒有弧相連的兩個頂點,它們在拓撲排序的序列中出現的次序沒有要求。例如,第一個序列中 c1 先於 c9,第二個則反之。拓撲排序的任何一個序列都是一個可行的活動執行順序,它能夠檢測到圖中是否存在環,由於若是有環,則環中的頂點沒法輸出,因此獲得的拓撲有序序列沒有包含圖中全部的頂點。
下面是拓撲排序算法的描述:
(1)在有向圖中選擇一個入度爲 0 的頂點(即沒有前驅的頂點),因爲該頂點沒有任何先決條件,輸出該頂點;
(2)從圖中刪除全部以它爲尾的弧;
(3)重複執行(1)和(2),直到找不到入度爲 0 的頂點,拓撲排序完成。若是圖中仍有頂點存在,卻沒有入度爲 0 的頂點,說明 AOV 網中有環路,不然沒有環路。
若是圖中仍有頂點存在,卻沒有入度爲 0 的頂點,說明 AOV 網中有環路,不然沒有環路。
如下圖 (a)爲例求出它的一個拓撲有序序列。
第一步:在圖 (a)所示的有向圖中選取入度爲 0 的頂點 c4,刪除頂點 c4及與它相關聯的弧<c4,c3>, <c4,c5>,獲得圖 (b)所示的結果,並獲得第一個拓撲有序序列頂點 c4。
第二步:再在圖 (b)中選取入度爲 0 的頂點 c5,刪除頂點 c5 及與它相關聯的弧<c5,c6>,獲得圖 (c)所示的結果,並獲得兩個拓撲有序序列頂點 c4,c5。
第三步:再在圖 (c)中選取入度爲 0 的頂點 c1,刪除頂點 c1 及與它相關聯的弧<c1,c2>, <c1,c3>,獲得圖 (d)所示的結果,並獲得三個拓撲有序序列頂點 c4, c5, c1。
第四步:再在圖 (d)中選取入度爲 0 的頂點 c2,刪除頂點 c2 及與它相關聯的弧<c2,c6>,獲得圖 (e)所示的結果,並獲得四個拓撲有序序列頂點 c4,c5, c1, c2。
第五步:再在圖 (e)中選取入度爲 0 的頂點 c3,刪除頂點 c3 及與它相關聯的弧<c3,c6>,獲得圖 (f)所示的結果,並獲得五個拓撲有序序列頂點 c4,c5, c1, c2, c3。
第六步:最後選取僅剩下的最後一個頂點 c6,拓撲排序結束,獲得圖 (a)的一個拓撲有序序列(c4, c5, c1, c2, c3, c6)。
圖是另外一種比樹形結構更復雜的非線性數據結構,圖中的數據元素稱爲頂點,頂點之間是多對多的關係。圖分爲有向圖和無向圖,帶權值的圖稱爲網。
圖的存儲結構不少,通常採用數組存儲圖中頂點的信息,鄰接矩陣採用矩陣也就是二維數組存儲頂點之間的關係。無向圖的鄰接矩陣是對稱的,因此在存儲時能夠只存儲上三角矩陣或下三角矩陣的數據;有向圖的鄰接矩陣不是對稱的。鄰接表用一個鏈表來存儲頂點之間的關係,因此鄰接表是順序存儲與鏈式存儲相結合的存儲結構。
圖的遍歷方法有兩種:深度優先遍歷和廣度優先遍歷。圖的深度優先遍歷類似於樹的先序遍歷,是樹的先序遍歷的推廣,它訪問頂點的順序是後進先出,與棧同樣。圖的廣度優先遍歷相似於樹的層序遍歷,它訪問頂點的順序是先進先出,與隊列同樣。
圖的應用很廣,本章重點介紹了三個方面的應用。最小生成樹是一個無向連通網中邊的權值總和最小的生成樹。構造最小生成樹必須包括 n 個頂點、 n-1 條邊及不存在迴路。構造最小生成樹的經常使用算法有普里姆算法和克魯斯卡爾算法兩種。
最短路徑問題是圖的一個比較典型的應用問題。最短路徑是網中求一個頂點到另外一個頂點的全部路徑中邊的權值之和最小的路徑。能夠求從一個頂點到網中其他頂點的最短路徑,這稱之爲單源點問題,也能夠求網中任意兩個頂點之間的最短路徑。本章只討論了單源點問題。解決單源點問題的算法是狄克斯特拉算法。
拓撲排序是圖中重要的運算之一,在實際中應用很普遍。 AOV 網是頂點之間存在優先關係的有向圖。拓撲排序是解決 AOV 網中是否存在環路的有效手段,若拓撲有序序列中包含 AOV 網中全部的頂點,則 AOV 網中不存在環路,不然存在環路。