關鍵路徑:在AOV網中,路徑上各個活動所持續的時間之和稱為路徑長度,從源點到匯點具有最大長度的路徑叫做關鍵路徑。
關鍵概念定義
AOV網:在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關系,這樣的有向圖為頂點表示活動的網,就是AOV網。
關鍵路徑算法原理:先求所有頂點的事件最早發生時間(從起點開始計算),如有頂點1和頂點2都到達頂點3,那么頂點3的最早開始時間為max(頂點1 + 路徑長度,頂點2 + 路徑長度);之后求出所有頂點的時間最遲發生時間(從終點開始計算),如頂點3會到達頂點4和頂點5,那么頂點3的最遲發生時間為min(頂點4 - 路徑長度,頂點5 - 路徑長度)。比較事件最早發生時間數組和事件最遲發生時間數據,若是對應位置的值相等(即沒有可延遲的時間),那么該頂點就是關鍵路徑上的頂點。比較完所有頂點即可輸出關鍵路徑。
// 圖的關鍵路徑算法
#include <stdio.h>
#include <malloc.h>
#define OK 1 // 執行成功
#define ERROR 0 // 執行失敗
#define MAXVEX 14 // 最大頂點數
#define INFINITY 65535 // 無窮
typedef int Status; // 執行狀態
int *etv, *ltv; // 事件最早發生時間和事件最遲發生時間數組,全局變量
int *stack2; // 用于存儲拓撲序列的棧
int top2; // 用于stack2的指針
// 鄰接矩陣結構
typedef struct {
int vexs[MAXVEX]; // 頂點表
int arc[MAXVEX][MAXVEX]; // 邊表
int numNodes, numEdges; // 圖中當前的頂點數,邊數
} MGraph;
/**************** 用到的鄰接表結構 ****************/
// 邊表節點
typedef struct EdgeNode {
int adjvex; // 鄰接點域,存儲該頂點對應的下標
int weight; // 用來存儲權值(非網圖可沒有)
struct EdgeNode *next; // 指向下一個鄰接點
} EdgeNode;
// 頂點表節點
typedef struct VertexNode {
int in; // 頂點入度
int data; // 頂點域,存儲頂點信息
EdgeNode *firstEdge; // 邊表頭指針
} VertexNode, AdjList[MAXVEX];
// 鄰接表結構
typedef struct {
AdjList adjList;// 頂點表數組
int numNodes, numEdges; // 圖中當前頂點數,邊數
} graphAdjList, *GraphAdjList;
/*************************************************/
/**
* 生成鄰接矩陣(圖)
* @param G 鄰接矩陣(圖)
*/
void CreateMGraph(MGraph *G) {
int i, j; // 用于遍歷元素
G->numEdges = 13; // 設置邊數
G->numNodes = 10; // 設置頂點數
// 初始化圖的頂點表
for (i = 0; i < G->numNodes; i++) {
G->vexs[i] = i;
}
// 初始化圖的邊表
for (i = 0; i < G->numNodes; i++) {
for (j = 0; j < G->numNodes; j++) {
if (i == j) { // 如果是對稱邊
G->arc[i][j] = 0; // 設置邊的權值為0
} else { // 其他邊
G->arc[i][j] = INFINITY; // 設置權值為無窮
}
}
}
// 設置特定邊的權值
G->arc[0][1]=3;
G->arc[0][2]=4;
G->arc[1][3]=5;
G->arc[1][4]=6;
G->arc[2][3]=8;
G->arc[2][5]=7;
G->arc[3][4]=3;
G->arc[4][6]=9;
G->arc[4][7]=4;
G->arc[5][7]=6;
G->arc[6][9]=2;
G->arc[7][8]=5;
G->arc[8][9]=3;
}
/**
* 使用鄰接矩陣生成鄰接表
* @param G 鄰接矩陣
* @param GL 鄰接表
*/
void CreateALGraph(MGraph G, GraphAdjList *GL) {
int i, j; // 用于遍歷元素
EdgeNode *e; // 邊表節點
*GL = (GraphAdjList) malloc(sizeof(graphAdjList)); // 為鄰接表表分配存儲空間
(*GL)->numNodes = G.numNodes; // 設置鄰接表的頂點數
(*GL)->numEdges = G.numEdges; // 設置鄰接表的邊數
// 建立頂點表
for (i = 0; i < G.numNodes; i++) {
(*GL)->adjList[i].in = 0; // 設置下標為i位置的頂點入讀為0
(*GL)->adjList[i].data = G.vexs[i]; // 存入該頂點的權值
(*GL)->adjList[i].firstEdge = NULL; // 設置頂點對應的邊表為空
}
// 建立邊表
for (i = 0; i < G.numNodes; i++) {
for (j = 0; j < G.numNodes; j++) {
// 如果該邊有路徑可通
if (G.arc[i][j] != 0 && G.arc[i][j] < INFINITY) {
e = (EdgeNode *) malloc(sizeof(EdgeNode)); // 為邊表節點分配存儲空間
e->adjvex = j; // 設置鄰接序號為j
e->weight = G.arc[i][j]; // 設置節點的權值
e->next = (*GL)->adjList[i].firstEdge; // 新節點指針指向邊表的頭節點
(*GL)->adjList[i].firstEdge = e; // 讓新節點成為邊表的頭節點
(*GL)->adjList[j].in++; // 邊終點所指向的節點入度加1
}
}
}
}
/**
* 拓撲排序
* 若鄰接表無回路,則輸出拓撲排序序列并返回OK,若有回路則返回ERROR
* @param GL 鄰接表
* @return 執行狀態
*/
Status TopologicalSort(GraphAdjList GL) {
EdgeNode *e; // 邊表節點
int i, k, gettop; // i用于遍歷元素,k用來獲取頂點下標,gettop用來獲取棧頂元素下標
int top = 0; // 用于棧指針下標
int count = 0; // 用于統計輸出頂點的個數
int *stack; // 存儲入度為0的頂點的棧
stack = (int *) malloc(GL->numNodes * sizeof(int)); // 為棧分配存儲空間
// 將所有入度為0的頂點入棧
for (i = 0; i < GL->numNodes; i++) {
if (GL->adjList[i].in == 0) { // 頂點入度為0
stack[++top] = i; // 將該節點入棧
}
}
top2 = 0; // 設置stack2的棧頂指針為0
etv = (int *) malloc(GL->numNodes * sizeof(int)); // 事件最早發生時間數組
for (i = 0; i < GL->numNodes; i++) {
etv[i] = 0; // 初始化事件最早發生時間都為0
}
stack2 = (int *) malloc(GL->numNodes * sizeof(int)); // 初始化拓撲序列棧
printf("拓撲排序:\n");
// 未把所有入度為0的頂點出棧
while (top != 0) {
gettop = stack[top--]; // 獲取入度為0的棧的棧頂元素
printf("%d -> ", GL->adjList[gettop].data); // 打印該元素的頂點信息
count++; // 輸入頂點個數加1
stack2[++top2] = gettop; // 將彈出的頂點序號壓入拓撲序列的棧
// 遍歷該頂點的邊表
for (e = GL->adjList[gettop].firstEdge; e; e = e->next) {
k = e->adjvex; // 獲取下個頂點(邊終點所指向的節點)的下標
// 將邊終點所指向的節點的入度減1,如果該頂點入讀為0,將頂點入棧
if (!(--GL->adjList[k].in)) {
stack[++top] = k; // 將頂點入棧
}
// 求各頂點事件的最早發生時間etv的值
// 如果邊起點的值加上邊上的權值大于邊終點的值
if (etv[gettop] + e->weight > etv[k]) {
// 設置邊終點的值為邊起點的值加上邊上的權值
etv[k] = etv[gettop] + e->weight;
// 如頂點1和頂點2都到達頂點3,
// 那么頂點3的最早開始時間為max(頂點1 + 邊上的權值,頂點2 + 邊上的權值)
}
}
}
printf("\n");
// 輸出的頂點數小于鄰接表的頂點數,鄰接表中有回路,拓撲排序失敗
if (count < GL->numNodes) {
return ERROR;
} else { // 鄰接表中無回路,拓撲排序成功
return OK;
}
}
/**
* 關鍵路徑算法
* @param GL 鄰接表
*/
void CriticalPath(GraphAdjList GL) {
EdgeNode *e; // 邊表節點
int i, gettop, k, j;
int ete, lte; // 記錄活動最早發生事件,活動最遲發生時間
TopologicalSort(GL); // 求拓撲序列,計算數組etv和stack2的值
ltv = (int *) malloc(GL->numNodes * sizeof(int)); // 為事件最遲發生時間數組分配存儲空間
for (i = 0; i < GL->numNodes; i++) { // 初始化事件最遲發生時間數組
ltv[i] = etv[GL->numNodes - 1]; // 設置值為事件最早發生時間數組的結束時間
}
// 打印事件最早發生時間數組
printf("etv:\n");
for (i = 0; i < GL->numNodes; i++) {
printf("%d -> ", etv[i]);
}
printf("\n");
// 拓撲序列的棧未出棧完
while (top2 != 0) {
gettop = stack2[top2--]; // 獲取拓撲序列棧的棧頂元素
// 遍歷與該頂點相連的邊
// 求各頂點事件的最遲發生時間ltv的值
for (e = GL->adjList[gettop].firstEdge; e; e = e->next) {
k = e->adjvex; // 獲取節點下標
// 如果邊終點的值減去邊的權值小于邊起點的值
// ltv[9] - 3 < ltv[8] = 24,ltv[8] = 27,min(27, 24) = 24
if (ltv[k] - e->weight < ltv[gettop]) {
// 設置邊起點的值為邊終點的值減去邊的權值
ltv[gettop] = ltv[k] - e->weight;
}
}
}
// 打印事件最遲發生時間數組
printf("ltv:\n");
for (i = 0; i < GL->numNodes; i++) {
printf("%d -> ", ltv[i]);
}
printf("\n");
printf("各頂點間路徑長度:\n");
for (j = 0; j < GL->numNodes; j++) {
for (e = GL->adjList[j].firstEdge; e; e = e->next) {
k = e->adjvex; // 獲取當前頂點的下標
ete = etv[j]; // 活動最早發生時間
lte = ltv[k] - e->weight; // 活動最遲發生時間
// 活動最早發生時間等于最遲發生時間,兩者相等表示在關鍵路徑上
if (ete == lte) {
printf("<v%d - v%d> length: %d \n", GL->adjList[j].data, GL->adjList[k].data, e->weight);
}
}
}
}
int main() {
MGraph G; // 鄰接矩陣
GraphAdjList GL; // 鄰接表
CreateMGraph(&G); // 創建鄰接矩陣
CreateALGraph(G, &GL); // 使用鄰接矩陣創建鄰接表
CriticalPath(GL); // 求圖的關鍵路徑
return 0;
}
運行結果