圖的關鍵路徑

關鍵路徑:在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;
}
運行結果
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容