上一章節講解了拓撲排序問題,拓撲排序是解決一個工程能否順序解決的問題,本質是一個廣度層次遍歷的過程,通過記錄頂點入度問題,進行逐步輸出的工作。在實際生活中,往往是求解工程完成需要最短時間問題。比如生活中生產一輛汽車,需要生產各種各樣的零件,最終組裝成車。例如生產輪子0.5天,發動機3天,底盤2天,其他部件2天,集中全部零件0.5天,組裝需要2天。請問組裝一輛汽車,最短需要多長時間。根據前面描述,我們構造這樣的AOV網絡圖,一看便知。

通過網絡中,我們很清晰的知道,關鍵路徑是5.5,如果發動機提高效率,那么關鍵路徑就是4.5。對於以項目關鍵問題就是尋找關鍵路徑,也許對於上圖很容易尋找,而對於下圖,就需要關鍵路徑算法,進行計算尋找。

關鍵路徑:路徑上各個活動所持續的時間之和為路徑長度,從源點到匯點具有最大長度的路徑叫關鍵路徑。
一、算法思想
在介紹關鍵路徑的時,先介紹幾個概念
- 事件最早發生時間ve(earliest time of vertex):頂點vk的最早發生時間,從始點到vi的最長(加權)路徑長度。
- 事件最晚發生時間vl(lastest time of vertex):頂點vk的最晚發生時間,在不拖延整個工期的條件下,vi的可能的最晚發生時間。。
- 活動最早發生時間e(earliest time of edge):活動ak的最早發生時間,等於事件vi的最早發生時間。
- 活動最晚發生時間l(lastest time of edge):弧ak的最晚發生時間,在不拖延整個工期的條件下,該活動的允許的最遲開始時間。
關鍵路勁算法原理:先獲取ve和vl,通過上述兩者獲取e和l,然后判斷e和l是否相等來判斷a是否是關鍵活動。
二、算法分析
關鍵路徑算法是一種典型的動態規划法,設圖G=(V, E)是個AOE網,結點編號為1,2,...,n,其中結點1與n 分別為始點和終點,ak=<i, j>∈E是G的一個活動。算法關鍵是確定活動的最早發生時間和最晚發生時間,進而獲取頂點的最早開始時間和最晚開始時間。
根據前面給出的定義,可推出活動的最早及最晚發生時間的計算方法:
e(k) = ve(i)
l(k) = vl(j) - len(i,j)
結點的最早發生時間的計算,需按拓撲次序遞推:
ve(1) = 0
ve(j) = MAX{ etv(i)+len(i, j) }
對所有<i,j> ∈E的i 結點的最晚發生時間的計算,需按逆拓撲次序遞推:
vl(n) = ve(n)
vl(i) = MIN{vl(j) - len(i, j)} 對所有<i,j>∈E的j
這種計算方法, 依賴於拓撲排序, 即計算ve( j) 前,應已求得j 的各前趨結點的ve值,而計算vl(i)前,應已求得i的各后繼結點的vl值。ve的計算可在拓撲排序過程中進行,即在每輸出一個結點i后,在刪除i的每個出邊<i,j>(即入度減1)的同時,執行
if ( ve[i]+len(i,j)) > ve[j] )
ve[j] = ve[i] + len(i,j)
三、例圖解析

所以根據算法:

四、代碼
//改進的拓撲排序,用於關鍵路徑算法 void GraphData::TopoLogicalSortAdv(GraphListAdv *pList) { EdgeNode *e; int ncount = 0; //統計輸出頂點個數,用於判斷是否有環 int nIndex ; // y用於保存度為0的坐標 stack<int> staNode; for (int i = 0;i < pList->numVertess; i++) { if (pList->vertexList[i].in == 0) { staNode.push(i); //將入度為0的頂點入棧 } } memset(this->veArray,0,pList->numVertess); while(!staNode.empty()) { nIndex = staNode.top(); // 獲取入度為0的頂點 staNode.pop(); //出棧 staEtv.push(nIndex); //將彈出的頂點序號壓入拓撲序列堆棧 printf("%c ->",pList->vertexList[nIndex].nNodeData); //打印頂點 ncount ++; //對此頂點的處理:下一個頂點入度減1,如果為0還要進棧 for (e = pList->vertexList[nIndex].pFirstNode;e;e = e->next) { //對此頂點弧表遍歷 int temp = e->nNodevex; //將temp號頂點鄰接點的入度減1,若為0,則入棧,以便於下次循環輸出 if (!(--pList->vertexList[temp].in)) { staNode.push(temp); } //求取各個頂點的事件最早發生時間。 if (veArray[nIndex] + e->nNodeWeight > veArray[temp]) { veArray[temp] = veArray[nIndex] + e->nNodeWeight; } } } if (ncount < pList->numVertess) { printf(" 圖有內環"); } } //關鍵路徑算法 void GraphData::CriticalPath(GraphListAdv *pList) { EdgeNode *node; int i,j,top,k; int ete,lte; TopoLogicalSortAdv(pList); for (i = 0; i< pList->numVertess;i++) { vlArray[i] = veArray[i]; //初始化vl } while(!staEtv.empty()) { top = staEtv.top(); staEtv.pop(); for (node = pList->vertexList[top].pFirstNode;node;node = node->next) { k = node->nNodevex; if (vlArray[k] - node->nNodeWeight < vlArray[top]) //求取頂點事件的最晚發生時間 { vlArray[top] = vlArray[k] + node->nNodeWeight; } } } //求取關鍵路徑 for (i = 0;i < pList->numVertess;i++) { for (node = pList->vertexList[i].pFirstNode; node;node = node->next) { k = node->nNodevex; ete = veArray[k]; lte = vlArray[k]; if (ete == lte) { printf("<v%c,v%c> length: %d, ",pList->vertexList[i].nNodeData,pList->vertexList[k].nNodeData,node->nNodeWeight); } } } }
五、代碼分析
程序最后的輸出結果如上圖。
在程序中首先是利用上一章節的拓撲排序進行改進,包括將頂點入棧,同時獲取頂點的最早活動時間,之后根據頂點的最早活動時間,逆序獲取頂點最晚活動時間,進而獲取項目的最晚活動時間數組。最終結果就如例圖解釋中所示,獲取到每個頂點的最早開始時間和最晚開始時間。
其中最早開始時間和最晚開始時間相等,說明在頂點的活動是沒有空閑時間的,關鍵路徑的活動各個頂點就是求取最大路徑長度,所以,兩者相等就是關鍵路徑的判斷條件,算法的關鍵是利用拓撲排序獲取頂點的最早開始時間和最晚開始時間。
六、圖相關程序代碼
1 /* 鄰接矩陣表示的圖結構*/ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <stack> 5 using namespace std; 6 7 8 #define MAXVEX 100 //最大頂點數 9 #define INFINITY 65535 //最大權值 10 11 typedef int EdgeType; //權值類型自己定義 12 typedef char VertexType; //頂點類型自己定義 13 #pragma once 14 15 #pragma region 鄰接矩陣結構體 16 typedef struct 17 { 18 VertexType vex[MAXVEX]; //頂點表 19 EdgeType arg[MAXVEX][MAXVEX]; ///權值表-鄰接矩陣 20 int numVertexes,numEdges; //圖中的邊數和頂點數 21 }GraphArray; 22 //邊集數組 23 typedef struct 24 { 25 int begin; 26 int end; 27 int weight; 28 }Edge; 29 #pragma endregion 30 31 #pragma region 鄰接表結構體 32 //邊表結點 33 typedef struct EdgeNode 34 { 35 int nNodevex; //鄰接點的點表中結點的坐標 36 EdgeType nNodeWeight; //用於網圖中邊的權值 37 EdgeNode* next; //鏈域,指向下一個鄰接點 38 }EdgeNode,*pEdgeNode; 39 //頂點表結點 40 typedef struct VertexNode 41 { 42 VertexType nNodeData; //頂點表中存儲的數據 43 pEdgeNode pFirstNode; //頂點表和邊表中關聯指針,指向邊表頭指針 44 45 }VertexNode,*pVertexNode,VertexList[MAXVEX]; 46 typedef struct VertexNodeAdv 47 { 48 int in; //頂點的度 49 VertexType nNodeData; //頂點表中存儲的數據 50 pEdgeNode pFirstNode; //頂點表和邊表中關聯指針,指向邊表頭指針 51 }VertexNodeAdv,*pVertexNodeAdv,VertexListAdv[MAXVEX];; 52 //圖結構 53 typedef struct 54 { 55 VertexList vertexList; 56 int numVertess,numEdges; 57 }GraphList; 58 59 //圖結構 60 typedef struct 61 { 62 VertexListAdv vertexList; 63 int numVertess,numEdges; 64 }GraphListAdv; 65 #pragma endregion 66 67 class GraphData 68 { 69 public: 70 GraphData(void); 71 ~GraphData(void); 72 #pragma region 創建鄰接矩陣 73 void CreateGraphArray(GraphArray* pGraphArray,int numVer,int numEdegs); 74 int GetGraphLocation(GraphArray* pGraphArrray,char chpoint); 75 #pragma endregion 76 77 #pragma region 創建鄰接表 78 void CreateGraphList(GraphList* pList,int numVer,int numEdegs); 79 void CreateGraphListAdv(GraphListAdv* pList,int numVer,int numEdegs); 80 int GetGraphListLocation(GraphList* pList,char chpoint); 81 int GetGraphAdvListLocation(GraphListAdv* pList,char chpoint); 82 #pragma endregion 83 84 #pragma region 圖的遍歷 85 //鄰接表的深度遞歸算法 86 void DFS(GraphList *pList,int i); 87 //鄰接表的深度遍歷操作 88 void DFSTraverse(GraphList* pList); 89 //鄰接矩陣的深度遞歸算法 90 void DFS(GraphArray *pArray,int i); 91 //鄰接矩陣的深度遍歷操作 92 void DFSTraverse(GraphArray *pArray); 93 //鄰接矩陣的廣度遍歷操作 94 void BFSTraverse(GraphArray *pArray); 95 //鄰接表的廣度遍歷操作 96 void BFSTraverse(GraphList *pList); 97 #pragma endregion 98 99 #pragma region 最小生成樹 100 //prime算法 101 void MiniSpanTree_Prime(GraphArray *pArray); 102 //Kruskal算法(克魯斯卡爾) 103 void MiniSpanTree_Kruskal(GraphArray *pArray); 104 #pragma endregion 105 106 #pragma region 最短路徑 107 //Dijkstra算法 108 void ShortPath_Dijkstra(GraphArray *pArray); 109 //Floyd算法 110 void ShortPath_Floyd(GraphArray *pArray); 111 #pragma endregion 112 113 #pragma region 拓撲排序、關鍵路徑 114 //拓撲排序 115 void TopoLogicalSort(GraphListAdv *pList); 116 //改進的拓撲排序,用於關鍵路徑算法 117 void TopoLogicalSortAdv(GraphListAdv *pList); 118 //關鍵路徑算法 119 void CriticalPath(GraphListAdv *pList); 120 #pragma endregion 121 122 private: 123 bool bVisited[MAXVEX]; 124 int veArray[MAXVEX]; 125 int vlArray[MAXVEX]; 126 stack<int> staEtv; 127 128 #pragma region 私有方法 129 130 int FindLastLine(int *parent,int f); 131 132 void InsertSort(Edge *pEdge,int k); 133 134 void GraphToEdges(GraphArray *pArray,Edge *pEdge); 135 #pragma endregion 136 137 };
1 #include "GraphData.h" 2 #include <queue> 3 #include <stack> 4 using namespace std; 5 GraphData::GraphData(void) 6 { 7 } 8 9 10 GraphData::~GraphData(void) 11 { 12 } 13 14 #pragma region 創建鄰接矩陣 15 int GraphData::GetGraphLocation(GraphArray* pGraphArrray,char chpoint) 16 { 17 int i = 0; 18 for (i = 0;i< pGraphArrray->numVertexes;i++) 19 { 20 if (pGraphArrray->vex[i] == chpoint) 21 { 22 break;; 23 } 24 } 25 if (i >= pGraphArrray->numVertexes) 26 { 27 return -1; 28 } 29 return i; 30 } 31 /// <summary> 32 /// 創建鄰接矩陣 33 /// </summary> 34 void GraphData::CreateGraphArray(GraphArray* pGraphArray,int numVer,int numEdegs) 35 { 36 int weight = 0; 37 pGraphArray->numVertexes = numVer; 38 pGraphArray->numEdges = numEdegs; 39 40 //創建頂點表 41 for (int i= 0; i < numVer;i++) 42 { 43 pGraphArray->vex[i] = getchar(); 44 while(pGraphArray->vex[i] == '\n') 45 { 46 pGraphArray->vex[i] = getchar(); 47 } 48 } 49 50 //創建鄰接表的邊矩陣 51 for (int i = 0; i < numEdegs; i++) 52 { 53 for (int j = 0;j < numEdegs ; j++) 54 { 55 pGraphArray->arg[i][j] = INFINITY; 56 } 57 } 58 for(int k = 0; k < pGraphArray->numEdges; k++) 59 { 60 char p, q; 61 printf("輸入邊(vi,vj)上的下標i,下標j和權值:\n"); 62 63 p = getchar(); 64 while(p == '\n') 65 { 66 p = getchar(); 67 } 68 q = getchar(); 69 while(q == '\n') 70 { 71 q = getchar(); 72 } 73 scanf("%d", &weight); 74 75 int m = -1; 76 int n = -1; 77 m = GetGraphLocation(pGraphArray, p); 78 n = GetGraphLocation(pGraphArray, q); 79 if(n == -1 || m == -1) 80 { 81 fprintf(stderr, "there is no this vertex.\n"); 82 return; 83 } 84 //getchar(); 85 pGraphArray->arg[m][n] = weight; 86 pGraphArray->arg[n][m] = weight; //因為是無向圖,矩陣對稱 87 } 88 89 } 90 91 #pragma endregion 92 93 #pragma region 創建鄰接表 94 void GraphData::CreateGraphList(GraphList* pList,int numVer,int numEdegs) 95 { 96 int weight = 0; 97 GraphList *pGraphList = pList; 98 pGraphList->numVertess = numVer; 99 pGraphList->numEdges = numEdegs; 100 EdgeNode* firstNode,*secondNode; 101 //創建頂點表 102 for (int i= 0; i < numVer;i++) 103 { 104 pGraphList->vertexList[i].nNodeData = getchar(); 105 pGraphList->vertexList[i].pFirstNode = NULL; 106 while(pGraphList->vertexList[i].nNodeData == '\n') 107 { 108 pGraphList->vertexList[i].nNodeData = getchar(); 109 } 110 } 111 112 //創建邊表 113 for(int k = 0; k < pGraphList->numEdges; k++) 114 { 115 char p, q; 116 printf("輸入邊(vi,vj)上的下標i,下標j和權值:\n"); 117 118 p = getchar(); 119 while(p == '\n') 120 { 121 p = getchar(); 122 } 123 q = getchar(); 124 while(q == '\n') 125 { 126 q = getchar(); 127 } 128 scanf("%d", &weight); 129 130 int m = -1; 131 int n = -1; 132 m = GetGraphListLocation(pGraphList, p); 133 n = GetGraphListLocation(pGraphList, q); 134 if(n == -1 || m == -1) 135 { 136 fprintf(stderr, "there is no this vertex.\n"); 137 return; 138 } 139 //getchar(); 140 //字符p在頂點表的坐標為m,與坐標n的結點建立聯系權重為weight 141 firstNode = new EdgeNode(); 142 firstNode->nNodevex = n; 143 firstNode->next = pGraphList->vertexList[m].pFirstNode; 144 firstNode->nNodeWeight = weight; 145 pGraphList->vertexList[m].pFirstNode = firstNode; 146 147 //第二個字符second 148 secondNode = new EdgeNode(); 149 secondNode->nNodevex = m; 150 secondNode->next = pGraphList->vertexList[n].pFirstNode; 151 secondNode->nNodeWeight = weight; 152 pGraphList->vertexList[n].pFirstNode = secondNode; 153 154 } 155 } 156 157 int GraphData::GetGraphListLocation(GraphList* pList,char chpoint) 158 { 159 GraphList *pGraphList = pList; 160 int i = 0; 161 for (i = 0;i< pGraphList->numVertess;i++) 162 { 163 if (pGraphList->vertexList[i].nNodeData == chpoint) 164 { 165 break;; 166 } 167 } 168 if (i >= pGraphList->numVertess) 169 { 170 return -1; 171 } 172 return i; 173 } 174 175 int GraphData::GetGraphAdvListLocation(GraphListAdv* pList,char chpoint) 176 { 177 GraphListAdv *pGraphList = pList; 178 int i = 0; 179 for (i = 0;i< pGraphList->numVertess;i++) 180 { 181 if (pGraphList->vertexList[i].nNodeData == chpoint) 182 { 183 break;; 184 } 185 } 186 if (i >= pGraphList->numVertess) 187 { 188 return -1; 189 } 190 return i; 191 } 192 #pragma endregion 193 194 #pragma region 圖的遍歷 195 //鄰接表的深度遞歸算法 196 void GraphData::DFS(GraphList *pList,int i) 197 { 198 EdgeNode *itemNode; 199 bVisited[i] = true; 200 printf("%c",pList->vertexList[i].nNodeData); //打印頂點數據 201 itemNode = pList->vertexList[i].pFirstNode; 202 while(itemNode) 203 { 204 if (!bVisited[itemNode->nNodevex]) 205 { 206 DFS(pList,itemNode->nNodevex); 207 } 208 itemNode = itemNode->next; 209 } 210 } 211 //鄰接表的深度遍歷操作 212 void GraphData::DFSTraverse(GraphList* pList) 213 { 214 int i; 215 GraphList *pGraphList = pList; 216 for ( i = 0;pGraphList->numVertess;i++) 217 { 218 bVisited[i] = false; 219 } 220 for (i = 0;i < pGraphList->numVertess;i++) 221 { 222 if (!bVisited[i]) 223 { 224 DFS(pGraphList,i); 225 } 226 } 227 } 228 //鄰接矩陣的深度遞歸算法 229 void GraphData::DFS(GraphArray *pArray,int i) 230 { 231 int j = 0; 232 printf("%c",pArray->vex[i]); //打印頂點,也可以其他操作 233 for (j = 0; j< pArray->numVertexes;j++) 234 { 235 if (!bVisited[j]&&pArray->arg[i][j] == 1) //關鍵之前初始化時,有關系的邊賦值為1,所以由此進行判斷 236 { 237 DFS(pArray,j); //對為訪問的鄰接頂點遞歸調用 238 } 239 } 240 } 241 //鄰接矩陣的深度遍歷操作 242 void GraphData::DFSTraverse(GraphArray *pArray) 243 { 244 int i; 245 for (i = 0;i < pArray->numVertexes;i++) 246 { 247 bVisited[i] = false; 248 } 249 for(i = 0; i< pArray->numVertexes;i++) 250 { 251 if (!bVisited[i]) 252 { 253 DFS(pArray,i); 254 } 255 } 256 } 257 258 //鄰接矩陣的廣度遍歷操作 259 void GraphData::BFSTraverse(GraphArray *pArray) 260 { 261 queue<int> itemQueue; 262 int i,j; 263 for(i = 0 ;i< pArray->numVertexes;i++) 264 { 265 bVisited[i] = false; 266 } 267 for(i = 0;i< pArray->numVertexes;i++)//每個頂點循環 268 { 269 if (!bVisited[i])//訪問未被訪問的頂點 270 { 271 bVisited[i] = true; 272 printf("%c",pArray->vex[i]); 273 itemQueue.push(i); //將此結點入隊 274 while(!itemQueue.empty()) 275 { 276 int m = itemQueue.front(); //結點出隊 277 itemQueue.pop(); 278 for(j = 0;j< pArray->numVertexes;j++) 279 { 280 //判斷其他頂點與當前頂點存在邊且未被訪問過。 281 if (!bVisited[j] && pArray->arg[m][j] == 1) 282 { 283 bVisited[j] = true; 284 printf("%c",pArray->vex[j]); 285 itemQueue.push(j); 286 } 287 } 288 } 289 } 290 } 291 } 292 //鄰接表的廣度遍歷操作 293 void GraphData::BFSTraverse(GraphList *pList) 294 { 295 queue<int> itemQueue; 296 int i,j; 297 EdgeNode* pitemNode; 298 for(i = 0 ;i< pList->numVertess;i++) 299 { 300 bVisited[i] = false; 301 } 302 for(i = 0;i<pList->numVertess;i++) 303 { 304 if (!bVisited[i]) 305 { 306 bVisited[i] = true; 307 printf("%c",pList->vertexList[i].nNodeData); 308 itemQueue.push(i); 309 while(!itemQueue.empty()) 310 { 311 int m = itemQueue.front(); 312 itemQueue.pop(); 313 pitemNode = pList->vertexList[m].pFirstNode; 314 while(pitemNode) 315 { 316 if (bVisited[pitemNode->nNodevex]) 317 { 318 bVisited[pitemNode->nNodevex] = true; 319 printf("%c",pList->vertexList[pitemNode->nNodevex].nNodeData); 320 itemQueue.push(pitemNode->nNodevex); 321 } 322 pitemNode = pitemNode->next; 323 } 324 325 } 326 } 327 } 328 } 329 #pragma endregion 330 331 #pragma region 最小生成樹 332 //prime算法 333 void GraphData::MiniSpanTree_Prime(GraphArray *pArray) 334 { 335 int min,i,j,k; 336 int nNodeIndex[MAXVEX]; //保存相關頂點坐標,1就是已經遍歷訪問的過結點 337 int nNodeWeight[MAXVEX]; //保存某個頂點到各個頂點的權值,為不為0和最大值表示遍歷過了。 338 //兩個數組的初始化 339 printf("開始初始化,當前頂點邊的權值為:"); 340 for(i = 0;i<pArray->numVertexes;i++) 341 { 342 nNodeIndex[i] = 0; 343 nNodeWeight[i] = pArray->arg[0][i];//設定在矩陣中第一個頂點為初始點。 344 printf(" %c",nNodeWeight[i]); 345 } 346 nNodeWeight[0] = 0; //選取坐標點0為起始點。 347 //Prime算法思想 348 for (i = 1;i< pArray->numVertexes;i++) 349 { 350 min = INFINITY; //初始化權值為最大值; 351 j = 1; 352 k = 0; 353 // 循環全部頂點,尋找與初始點邊權值最小的頂點,記下權值和坐標 354 while(j < pArray->numVertexes) 355 { 356 //如果權值不為0,且權值小於min,為0表示本身 357 if (nNodeWeight[j] != 0&&nNodeWeight[j] < min) 358 { 359 min = nNodeWeight[j]; 360 k = j; //保存上述頂點的坐標值 361 } 362 j++; 363 } 364 printf("當前頂點邊中權值最小邊(%d,%d)\n",nNodeIndex[k] , k); //打印當前頂點邊中權值最小 365 nNodeWeight[k] = 0; //將當前頂點的權值設置為0,表示此頂點已經完成任務 366 367 for (j = 1;j< pArray->numVertexes;j++) //循環所有頂點,查找與k頂點的最小邊 368 { 369 //若下標為k的頂點各邊權值小於此前這些頂點未被加入的生成樹權值 370 if (nNodeWeight[j] != 0&&pArray->arg[k][j] < nNodeWeight[j]) 371 { 372 nNodeWeight[j] = pArray->arg[k][j]; 373 nNodeIndex[j] = k; //將下標為k的頂點存入adjvex 374 } 375 } 376 //打印當前頂點狀況 377 printf("坐標點數組為:"); 378 for(j = 0;j< pArray->numVertexes;j++) 379 { 380 printf("%3d ",nNodeIndex[j]); 381 } 382 printf("\n"); 383 printf("權重數組為:"); 384 for(j = 0;j< pArray->numVertexes;j++) 385 { 386 printf("%3d ",nNodeWeight[j]); 387 } 388 printf("\n"); 389 } 390 391 } 392 //Kruskal算法(克魯斯卡爾) 393 void GraphData::MiniSpanTree_Kruskal(GraphArray *pArray) 394 { 395 int i,n,m; 396 int parent[MAXVEX]; //定義邊集數組 397 Edge edges[MAXVEX]; //定義一數組用來判斷邊與邊是否形成環 398 //鄰接矩陣轉為邊集數組,並按照權值大小排序 399 GraphToEdges(pArray,edges); 400 401 for (i =0; i< pArray->numVertexes;i++) 402 { 403 parent[i] = 0; //初始化數組數值為0 404 405 } 406 407 //算法關鍵實現 408 for (i = 0;i < pArray->numVertexes;i++) //循環每條邊 409 { 410 //根據邊集數組,查找出不為0的邊 411 n = FindLastLine(parent,edges[i].begin); 412 m = FindLastLine(parent,edges[i].end); 413 printf("邊%d的開始序號為:%d,結束為:%d)",i,n,m); 414 if(n != m) //假如n與m不等,說明此邊沒有與現有生成樹形成環路 415 { 416 parent[n] = m; //將此邊的結尾頂點放入下標為起點的parent中 417 //表示此頂點已經在生成樹集合中 418 printf("(%d,%d) %d ", edges[i].begin, edges[i].end, edges[i].weight); 419 } 420 } 421 printf("\n"); 422 } 423 #pragma endregion 424 425 #pragma region 最短路徑 426 //Dijkstra算法 427 void GraphData::ShortPath_Dijkstra(GraphArray *pArray) 428 { 429 //Dijkstra算法和最小生成樹的算法,在某種程度是相似的 430 int min,i,j,k; 431 int nNodeIndex[MAXVEX]; //保存相關頂點坐標,1就是已經遍歷訪問的過結點(在最小生成樹中為數值表示遍歷過同時值與坐標是一條邊) 432 int nNodeWeight[MAXVEX]; //保存某個頂點到各個頂點的權值,為不為0和最大值表示遍歷過了。 433 int nPathLength[MAXVEX]; //坐標和元素表示為同時值和坐標表示一邊,與Primes有相同的 434 //兩個數組的初始化 435 printf("開始初始化,當前頂點邊的權值為:"); 436 for(i = 0;i<pArray->numVertexes;i++) 437 { 438 nNodeIndex[i] = 0; 439 nNodeWeight[i] = pArray->arg[0][i];//設定在矩陣中第一個頂點為初始點。 440 nPathLength[i] = 0; 441 printf(" %c",nNodeWeight[i]); 442 } 443 nNodeWeight[0] = 0; //選取坐標點0為起始點。 444 nNodeIndex[0] = 1; //這樣0就是起始點,設為1.(和Prime的不同) 445 //算法思想 446 for (i = 1;i< pArray->numVertexes;i++) 447 { 448 min = INFINITY; //初始化權值為最大值; 449 j = 1; 450 k = 0; 451 // 循環全部頂點,尋找與初始點邊權值最小的頂點,記下權值和坐標 452 while(j < pArray->numVertexes) 453 { 454 //如果權值不為0,且權值小於min,為0表示本身 455 if (!nNodeIndex[j]&&nNodeWeight[j] < min) //這里Prime是權重中不為0, 456 { 457 min = nNodeWeight[j]; 458 k = j; //保存上述頂點的坐標值 459 } 460 j++; 461 } 462 printf("當前頂點邊中權值最小邊(%d,%d)\n",nNodeIndex[k] , k); //打印當前頂點邊中權值最小 463 //nNodeWeight[k] = 0; //將當前頂點的權值設置為0,表示此頂點已經完成任務 464 nNodeIndex[k] = 1; //將目前找到的最近的頂點置為1 465 466 //for (j = 1;j< pArray->numVertexes;j++) //循環所有頂點,查找與k頂點的最小邊 467 //{ 468 // //若下標為k的頂點各邊權值小於此前這些頂點未被加入的生成樹權值 469 // if (nNodeWeight[j] != 0&&pArray->arg[k][j] < nNodeWeight[j]) 470 // { 471 // nNodeWeight[j] = pArray->arg[k][j]; 472 // nNodeIndex[j] = k; //將下標為k的頂點存入adjvex 473 // } 474 //} 475 //修正當前最短路徑及距離 476 for (j = 1;j< pArray->numVertexes;j++) //循環所有頂點,查找與k頂點的最小邊 477 { 478 //若下標為k的頂點各邊權值小於此前這些頂點未被加入的生成樹權值 479 if (!nNodeIndex[j] && pArray->arg[k][j] + min< nNodeWeight[j]) 480 { 481 nNodeWeight[j] = pArray->arg[k][j] + min; 482 nPathLength[j] = k; //將下標為k的頂點存入adjvex 483 } 484 } 485 //打印當前頂點狀況 486 printf("坐標點數組為:"); 487 for(j = 0;j< pArray->numVertexes;j++) 488 { 489 printf("%3d ",nPathLength[j]); 490 } 491 printf("\n"); 492 printf("權重數組為:"); 493 for(j = 0;j< pArray->numVertexes;j++) 494 { 495 printf("%3d ",nNodeWeight[j]); 496 } 497 printf("\n"); 498 } 499 500 } 501 //Floyd算法 502 void GraphData::ShortPath_Floyd(GraphArray *pArray) 503 { 504 int i,j,m,k; 505 int nNodeIndex[MAXVEX][MAXVEX]; 506 int nNodeWeight[MAXVEX][MAXVEX]; 507 508 for ( i = 0;i< pArray->numVertexes;i++) 509 { 510 for (j = 0;j< pArray->numVertexes;j++) 511 { 512 nNodeIndex[i][j] = j; /* 初始化 */ 513 nNodeWeight[i][j] = pArray->arg[i][j]; /* [i][j]值即為對應點間的權值 */ 514 } 515 } 516 for (i = 0;i< pArray->numVertexes;i++) 517 { 518 for (j = 0;j< pArray->numVertexes;j++) 519 { 520 for (k = 0;k<pArray->numVertexes;k++) 521 { 522 if (pArray->arg[j][k] > pArray->arg[j][i] + pArray->arg[i][k]) 523 { 524 /* 如果經過下標為k頂點路徑比原兩點間路徑更短 */ 525 nNodeWeight[j][k] = pArray->arg[j][i] + pArray->arg[i][k]; /* 將當前兩點間權值設為更小的一個 */ 526 nNodeIndex[j][k] = nNodeIndex[j][i]; /* 路徑設置為經過下標為k的頂點 */ 527 } 528 } 529 } 530 } 531 for (i = 0; i< pArray->numVertexes;i++) 532 { 533 for (j = 0;j< pArray->numVertexes;j++) 534 { 535 printf("v%d-v%d weight: %d",i,j,nNodeWeight[i][j]); 536 m = nNodeIndex[i][j]; //獲得第一個路徑點的頂點下標 537 printf("path :%d",i); //打印源點 538 while(m!=j) 539 { 540 printf(" -> %d",m); //打印路徑頂點 541 m = nNodeIndex[m][j]; //獲取下一個路徑頂點下標 542 } 543 printf(" -> %d\n",m); //打印路徑終點。 544 } 545 546 printf("\n"); 547 } 548 } 549 #pragma endregion 550 551 #pragma region 拓撲排序、關鍵路徑 552 void GraphData::CreateGraphListAdv(GraphListAdv* pList,int numVer,int numEdegs) 553 { 554 int weight = 0; 555 GraphListAdv *pGraphList = pList; 556 pGraphList->numVertess = numVer; 557 pGraphList->numEdges = numEdegs; 558 EdgeNode* firstNode,*secondNode; 559 //創建頂點表 560 for (int i= 0; i < numVer;i++) 561 { 562 pGraphList->vertexList[i].nNodeData = getchar(); 563 pGraphList->vertexList[i].pFirstNode = NULL; 564 while(pGraphList->vertexList[i].nNodeData == '\n') 565 { 566 pGraphList->vertexList[i].nNodeData = getchar(); 567 } 568 } 569 570 //創建邊表 571 for(int k = 0; k < pGraphList->numEdges; k++) 572 { 573 char p, q; 574 printf("輸入邊(vi,vj)上的下標i,下標j和權值:\n"); 575 576 p = getchar(); 577 while(p == '\n') 578 { 579 p = getchar(); 580 } 581 q = getchar(); 582 while(q == '\n') 583 { 584 q = getchar(); 585 } 586 scanf("%d", &weight); 587 588 int m = -1; 589 int n = -1; 590 m = GetGraphAdvListLocation(pGraphList, p); 591 n = GetGraphAdvListLocation(pGraphList, q); 592 if(n == -1 || m == -1) 593 { 594 fprintf(stderr, "there is no this vertex.\n"); 595 return; 596 } 597 //getchar(); 598 //字符p在頂點表的坐標為m,與坐標n的結點建立聯系權重為weight 599 firstNode = new EdgeNode(); 600 firstNode->nNodevex = n; 601 firstNode->next = pGraphList->vertexList[m].pFirstNode; 602 pGraphList->vertexList[n].in++; 603 firstNode->nNodeWeight = weight; 604 pGraphList->vertexList[m].pFirstNode = firstNode; 605 606 ////第二個字符second 607 //secondNode = new EdgeNode(); 608 //secondNode->nNodevex = m; 609 //secondNode->next = pGraphList->vertexList[n].pFirstNode; 610 //pGraphList->vertexList[n].in++; 611 //secondNode->nNodeWeight = weight; 612 //pGraphList->vertexList[n].pFirstNode = secondNode; 613 614 } 615 } 616 void GraphData::TopoLogicalSort(GraphListAdv *pList) 617 { 618 EdgeNode *e; 619 int ncount = 0; //統計輸出頂點個數,用於判斷是否有環 620 int nIndex ; // y用於保存度為0的坐標 621 stack<int> staNode; 622 for (int i = 0;i < pList->numVertess; i++) 623 { 624 if (pList->vertexList[i].in == 0) 625 { 626 staNode.push(i); //將入度為0的頂點入棧 627 } 628 } 629 while(!staNode.empty()) 630 { 631 nIndex = staNode.top(); // 獲取入度為0的頂點 632 staNode.pop(); //出棧 633 printf("%c ->",pList->vertexList[nIndex].nNodeData); //打印頂點 634 ncount ++; 635 //對此頂點的處理:下一個頂點入度減1,如果為0還要進棧 636 for (e = pList->vertexList[nIndex].pFirstNode;e;e = e->next) 637 { 638 //對此頂點弧表遍歷 639 int temp = e->nNodevex; 640 //將temp號頂點鄰接點的入度減1,若為0,則入棧,以便於下次循環輸出 641 if (!(--pList->vertexList[temp].in)) 642 { 643 staNode.push(temp); 644 } 645 } 646 } 647 if (ncount < pList->numVertess) 648 { 649 printf(" 圖有內環"); 650 } 651 652 } 653 //改進的拓撲排序,用於關鍵路徑算法 654 void GraphData::TopoLogicalSortAdv(GraphListAdv *pList) 655 { 656 EdgeNode *e; 657 int ncount = 0; //統計輸出頂點個數,用於判斷是否有環 658 int nIndex ; // y用於保存度為0的坐標 659 stack<int> staNode; 660 661 for (int i = 0;i < pList->numVertess; i++) 662 { 663 if (pList->vertexList[i].in == 0) 664 { 665 staNode.push(i); //將入度為0的頂點入棧 666 } 667 } 668 memset(this->veArray,0,pList->numVertess); 669 670 while(!staNode.empty()) 671 { 672 nIndex = staNode.top(); // 獲取入度為0的頂點 673 staNode.pop(); //出棧 674 staEtv.push(nIndex); //將彈出的頂點序號壓入拓撲序列堆棧 675 printf("%c ->",pList->vertexList[nIndex].nNodeData); //打印頂點 676 ncount ++; 677 //對此頂點的處理:下一個頂點入度減1,如果為0還要進棧 678 for (e = pList->vertexList[nIndex].pFirstNode;e;e = e->next) 679 { 680 //對此頂點弧表遍歷 681 int temp = e->nNodevex; 682 //將temp號頂點鄰接點的入度減1,若為0,則入棧,以便於下次循環輸出 683 if (!(--pList->vertexList[temp].in)) 684 { 685 staNode.push(temp); 686 } 687 //求取各個頂點的事件最早發生時間。 688 if (veArray[nIndex] + e->nNodeWeight > veArray[temp]) 689 { 690 veArray[temp] = veArray[nIndex] + e->nNodeWeight; 691 } 692 } 693 } 694 if (ncount < pList->numVertess) 695 { 696 printf(" 圖有內環"); 697 } 698 } 699 //關鍵路徑算法 700 void GraphData::CriticalPath(GraphListAdv *pList) 701 { 702 EdgeNode *node; 703 int i,j,top,k; 704 int ete,lte; 705 TopoLogicalSortAdv(pList); 706 for (i = 0; i< pList->numVertess;i++) 707 { 708 vlArray[i] = veArray[i]; //初始化vl 709 } 710 711 while(!staEtv.empty()) 712 { 713 top = staEtv.top(); 714 staEtv.pop(); 715 for (node = pList->vertexList[top].pFirstNode;node;node = node->next) 716 { 717 k = node->nNodevex; 718 if (vlArray[k] - node->nNodeWeight < vlArray[top]) //求取頂點事件的最晚發生時間 719 { 720 vlArray[top] = vlArray[k] + node->nNodeWeight; 721 } 722 723 } 724 725 } 726 //求取關鍵路徑 727 for (i = 0;i < pList->numVertess;i++) 728 { 729 for (node = pList->vertexList[i].pFirstNode; node;node = node->next) 730 { 731 k = node->nNodevex; 732 ete = veArray[k]; 733 lte = vlArray[k]; 734 if (ete == lte) 735 { 736 printf("<v%c,v%c> length: %d, ",pList->vertexList[i].nNodeData,pList->vertexList[k].nNodeData,node->nNodeWeight); 737 } 738 } 739 } 740 741 } 742 #pragma endregion 743 #pragma region 私有方法 744 745 //查找連線頂點尾部 746 int GraphData::FindLastLine(int *parent,int f) 747 { 748 while(parent[f] >0) 749 { 750 f = parent[f]; 751 } 752 return f; 753 } 754 //直接插入排序 755 void GraphData::InsertSort(Edge *pEdge,int k) 756 { 757 Edge *itemEdge = pEdge; 758 Edge item; 759 int i,j; 760 for (i = 1;i<k;i++) 761 { 762 if (itemEdge[i].weight < itemEdge[i-1].weight) 763 { 764 item = itemEdge[i]; 765 for (j = i -1; itemEdge[j].weight > item.weight ;j--) 766 { 767 itemEdge[j+1] = itemEdge[j]; 768 } 769 itemEdge[j+1] = item; 770 } 771 } 772 } 773 //將鄰接矩陣轉化為邊集數組 774 void GraphData::GraphToEdges(GraphArray *pArray,Edge *pEdge) 775 { 776 int i; 777 int j; 778 int k; 779 780 k = 0; 781 for(i = 0; i < pArray->numVertexes; i++) 782 { 783 for(j = i; j < pArray->numEdges; j++) 784 { 785 if(pArray->arg[i][j] < 65535) 786 { 787 pEdge[k].begin = i; 788 pEdge[k].end = j; 789 pEdge[k].weight = pArray->arg[i][j]; 790 k++; 791 } 792 } 793 } 794 795 printf("k = %d\n", k); 796 printf("邊集數組排序前,如下所示.\n"); 797 printf("edges[] beign end weight\n"); 798 for(i = 0; i < k; i++) 799 { 800 printf("%d", i); 801 printf(" %d", pEdge[i].begin); 802 printf(" %d", pEdge[i].end); 803 printf(" %d", pEdge[i].weight); 804 printf("\n"); 805 } 806 807 808 //下面進行排序 809 InsertSort(pEdge, k); 810 811 printf("邊集數組排序后,如下所示.\n"); 812 printf("edges[] beign end weight\n"); 813 for(i = 0; i < k; i++) 814 { 815 printf("%d", i); 816 printf(" %d", pEdge[i].begin); 817 printf(" %d", pEdge[i].end); 818 printf(" %d", pEdge[i].weight); 819 printf("\n"); 820 } 821 822 } 823 #pragma endregion
1 #include <iostream> 2 #include "GraphData.h" 3 using namespace std; 4 // 5 6 void PrintGrgph(GraphList *pGraphList) 7 { 8 int i =0; 9 while(pGraphList->vertexList[i].pFirstNode != NULL && i<MAXVEX) 10 { 11 printf("頂點:%c ",pGraphList->vertexList[i].nNodeData); 12 EdgeNode *e = NULL; 13 e = pGraphList->vertexList[i].pFirstNode; 14 while(e != NULL) 15 { 16 printf("%d ", e->nNodevex); 17 e = e->next; 18 } 19 i++; 20 printf("\n"); 21 } 22 23 } 24 int main() 25 { 26 int numVexs,numEdges; 27 GraphData* pTestGraph = new GraphData(); 28 GraphArray graphArray; 29 GraphArray* pGraphArray = &graphArray; 30 GraphList* pGgraphList = new GraphList(); 31 GraphListAdv *pGraphListAdv = new GraphListAdv(); 32 33 cout<<"輸入頂點數和邊數"<<endl; 34 cin>>numVexs>>numEdges; 35 cout<<"頂點數和邊數為:"<<numVexs<<numEdges<<endl; 36 pTestGraph->CreateGraphListAdv(pGraphListAdv,numVexs,numEdges); 37 //pTestGraph->CreateGraphArray(pGraphArray,numVexs,numEdges); 38 //pTestGraph->MiniSpanTree_Prime(pGraphArray); 39 printf("構建的鄰接矩陣如下所示.\n"); 40 /*for(int i = 0; i< numEdges;i++) 41 { 42 for (int j = 0;j< numEdges;j++) 43 { 44 printf("%5d ", pGraphArray->arg[i][j]); 45 } 46 cout<<endl; 47 }*/ 48 //pTestGraph->MiniSpanTree_Kruskal(pGraphArray); 49 /*pTestGraph->CreateGraphList(pGgraphList,numVexs,numEdges); 50 PrintGrgph(pGgraphList);*/ 51 //pTestGraph->ShortPath_Dijkstra(pGraphArray); 52 //pTestGraph->ShortPath_Floyd(pGraphArray); 53 pTestGraph->CriticalPath(pGraphListAdv); 54 system("pause"); 55 }

