數據結構(七)圖

第七章 圖

7.1 概念

  1. 連通圖:若是圖中任意兩點都有路徑,則該圖是連通圖
  2. 若一個有向圖恰有一個頂點的入度爲0,其與定點入度爲1,則是一顆有向樹

7.2 圖的物理存儲結構

由於圖的節點度數相差很大,按照度數最大的頂點設計節點結構會形成存儲單元浪費;若是按照每一個頂點本身的度數設計不一樣結構,又會帶來操做的不便算法

1、鄰接矩陣數組

  1. 鄰接矩陣存儲使用2個數組存儲圖的信息:1個覺得數組存儲頂點,一個二維數組存儲邊的信息
    (1)二維數組中的對角線爲0,覺得不存在頂點到自身的邊
    (2)要知道某個點的出度,就是頂點vi在第i行的元素之和,入度就是該頂點所在列的元素之和
    (3)頂點vi的全部鄰接點就是吧矩陣中第i行元素掃描一遍
              鄰接矩陣.PNG-38.8kB
    (4)對於有權值的網,二維數組中的元素再也不是0,1表示是否存在邊,而是把元素值表示爲權值。不存在的邊,權值記錄爲\(\infty\);對角線上的權值爲0.
    2.PNG-64.8kB網絡

  2. 鄰接矩陣定義圖數據結構

    #include <stdio.h>
    typedef char VertexType;
    typedef int EdgeType;
    #define MAXVEX 100
    #define IUNFINITY 65535
    typedef struct {
        VertexType vexs[MAXVEX];        /* 頂點表*/
        EdgeType arc[MAXVEX][MAXVEX];   /* 鄰接矩陣 */
        int vnum,edgenum;               /*定點的個數和邊的個數*/
    }MGraphy;
    
    void createGraphy(MGraphy *g){
        printf("input vetex num and edge num\n");
        scanf("%d,%d",&g->vnum,&g->edgenum);
    
        for (int i = 0; i < g->vnum ; i++) {                      // 輸入頂點字符
            printf("input %d vetex:",(i+1));
            setbuf(stdin, NULL);
            scanf("%c",&g->vexs[i]);
        }
    
        for(int i=0;i<g->vnum;i++){                               // 初始化數組元素 Infonity
            for(int j=0;j<g->vnum;j++){
                g->arc[i][j] = IUNFINITY;
            }
        }
    
        printf("input a,b,c represent corner mark and weight\n");
        for(int i=0;i<g->edgenum;i++){
            int a,b,c=0;
            printf("%d edge:",(i+1));
            setbuf(stdin,NULL);
            scanf("%d,%d,%d",&a,&b,&c);
            g->arc[a][b] = c;
            g->arc[b][a] = c;    // 無向圖增長這個
        }
    }
    int main() {
        MGraphy g ;
        createGraphy(&g);
    }

二. 鄰接表spa

  1. 鄰接矩陣對於頂點多而邊數少的稀疏圖形成存儲空間的大量浪費。正如線性表的預先分配可能形成存儲空間浪費,所以引入鏈式存儲結構。一樣能夠考慮用鏈表存儲邊或弧。設計

  2. 鄰接表:數組 + 鏈表
    (1)用的數組存儲每一個節點
    (2)數組中的每一個節點的全部鄰接點組成一個鏈表(由於鄰接點的個數不肯定)。這個鄰接表就是頂點的出度表
    (3)鄰接表的圖形表示
    adjarr.PNG-57.8kB
    (4)鄰接表關心了出度,可是查找入度就須要遍歷整個圖3d

  3. 建立鄰接表指針

    #include <stdio.h>
    #include <malloc.h>
    
    typedef char VertexType;
    typedef int EdgeType;
    #define MAXVEX 100
    #define IUNFINITY 65535
    
    typedef struct EdgeNode{     
        int adjvex;                         /*  鄰接點域,該頂點對應的下標  */
        EdgeType weight;          
        EdgeNode *next;                     /*  鏈,指向下一個鄰接點  */
    }EdgeNode;
    
    typedef struct VertexNode{                  /*  頂點表結點  */
        VertexType data;                        /*  節點名字  */
        EdgeNode *firstedge;                    /*  邊表頭節點  */
    }VertexNode;
    
    typedef struct{
        VertexNode adjList[MAXVEX];                            /*  頂點表是一個結構體數組,數組中的元素是Vertex節點  */
        int vnum,enumber;                                      /*  圖中當前頂點和邊數  */
    }GraphyAdjList;
    
    /*  創建鄰接表結構  */
    void createGraphy(GraphyAdjList *g){
        EdgeNode *e;
        printf("input vertexNum and edgeNum:\n");
        setbuf(stdin,NULL);
        scanf("%d,%d",&g->vnum,&g->enumber);
        for (int i = 0; i < g->vnum; i++) {
            printf("int %d vertex",(i+1));
            setbuf(stdin,NULL);
            scanf("%c",&g->adjList[i].data);
            g->adjList[i].firstedge = NULL;                     /*  將邊表設爲空 */
        }
        /*   創建邊表  */
        for (int k = 0; k < g->enumber; k++) {
            printf("input edge serialize num (i,j):\n");
            int i,j;
            setbuf(stdin,NULL);
            scanf("%d,%d",&i,&j);
            e = (EdgeNode *) malloc (sizeof(EdgeNode));
        }
    }

7.3 圖的遍歷

一. 基本思路code

  1. 圖的遍歷:從圖中某一個頂點出發遍歷途中其他頂點,每個頂點僅被訪問一次blog

  2. 基本思路
    (1)樹有四種遍歷方式,由於根節點只有一個。而圖的複雜狀況是的順着一個點向下尋找,極有可能最後又找到本身,造成迴路致使死循環。
    (2)因此要設置一個數組voisited[n],n是圖中頂點個數,初值爲0,當該頂點被遍歷後,修改數組元素的值爲1
    (3)基於此,造成了2中遍歷方案:深度優先遍歷和廣度優先遍歷

二. 深度優先遍歷(DFS)

  1. 以下圖所示,咱們進行深度遍歷,一個原則就是,每當咱們發現有多個出度時,選擇右手邊的出度做爲下一個遍歷的頂點路徑。
    (1)從A出發,發現出度爲B,F。選擇右手邊的B。A->B
    (2)從B出發,出度爲C,I,G,選擇右手邊的C
    (3)從C出發,出度爲I,D,選擇右手邊的D
    (4)從D出發,出度爲I,G,H,E,選擇右手邊的E
    (5)從E出發,出度爲H,F,選擇右手邊的F
    (6)從F出發,出度爲A,G,選擇右手邊的A,但發現A已經被遍歷過,因此選擇G
    (7)從G出發,出度爲B,D,H,B,D訪問過了,選擇H
    (8)從H出發,出度爲D,F,均被訪問過了。但此時圖中的節點並無遍歷徹底,所以咱們要按原路返回,去找沒走過的路
    (9)回退到G,發現所鏈接的BDFH均被訪問;
    (10)回退到F,沒有通道;回退到E,沒有通道,回退到D,發現一個點I,進行標記(若此時與D相鄰的還有其餘頂點,則在此時一塊兒進行標記);而後繼續回退到A,走完整個路

  2. 鄰接矩陣下的深度遍歷

    int visited[MAXVEX] = {0};
    void DFS(MGraphy g,int i){
        visited[i] = 1;
        printf("%c,\t",g.vexs[i]);
        for (int j = 0; j < g.vnum; j++) {
            if(g.arc[i][j]!=0 && g.arc[i][j]!=IUNFINITY && !visited[j]){
                DFS(g,j);
            }
        }
    }
    void DFSTraverse(MGraphy g){
        printf("deep first search begin.\n");
        for (int i = 0; i < g.vnum; i++) {
            if(!visited[i]){
                DFS(g,i);
            }
        }
    }
    
    int main() {
        MGraphy g ;
        createGraphy(&g);
        printf("graphy create success ! ! !\n");
        DFSTraverse(g);
    }
  3. 鄰接表下的深度遍歷

    int visited[MAXVEX] = {0};
    void DFS(Graph g, int i){      
        printf("%c",g.vset[i].name);
        visited[i] = 1;
        EdgeNode *edgeNode = g.vset[i].firstedgeNode;
        while(edgeNode!=NULL){
            if(!visited[edgeNode->index])
                DFS(g,edgeNode->index);
            edgeNode = edgeNode->next;
        }
    }
    void DFStraverse(Graph g){
        for (int i = 0; i < g.vNum; i++) {    // 用於不一樣連通份量
            if(!visited[i])
                DFS(g,i);
        }
    }
    
    int main() {
        Graph g;
        createGraphy(&g);
        printf("create graphy success ! ! !\n");
        DFStraverse(g);
    }

三. 廣度優先遍歷

  1. 廣度優先遍歷相似輸的層次遍歷
    (1)先入隊列一個元素
    (2)彈出隊列頂端的1個元素打印,並把它鏈接的頂點入隊
    (3)重複以上過程,直到隊列爲空

  2. BFS的過程
    bfs.PNG-182.5kB

  3. BFS的實現
    (1)鄰接矩陣的BFS

    typedef char VertexType;
    typedef int EdgeType;
    #define MAXVEX 100
    #define IUNFINITY 65535
    typedef struct {
        VertexType vexs[MAXVEX];        /* 頂點表*/
        EdgeType arc[MAXVEX][MAXVEX];   /* 鄰接矩陣 */
        int vnum,edgenum;               /*定點的個數和邊的個數*/
    }MGraphy;
        
        
    /**
     * 鄰接矩陣遍歷圖
     * @param g
     */
    void BFSTraverse(MGraphy g){
        SeqQueue *queue;
        initQueue(queue);   // 順序表實現的隊列,先初始化
        int visited[] = {0};    // 初始化每一個結點對應爲未訪問
        int a;
        for(int i=0;i<g.vnum;i++){   // 對每一個結點進行深度遍歷
            if(visited[i] == 0){
                visited[i] = 1;
                printf("%c",g.vexs[i]);  // 深度遍歷後對結點進行打印操做
                enQueue(queue,i);        // 將節點放到隊列中
                while (queueLength(queue)){
                    deQueue(queue,&a); // 取出對頭元素,進行廣度遍歷
                    for (int j = 0; j < g.vnum; ++j) {
                        if(g.arc[a][j] == 1 && visited[j]==0){   // 存在邊,且對應的店沒有方問過
                            visited[j] = 1;
                            printf("%c",g.vexs[j]);
                            enQueue(queue,j);                           // 遍歷後再入隊
                        }
                    }
                }
            }
        }
    }

(2)鄰接表的BFS
```c
typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100
#define IUNFINITY 65535

typedef struct EdgeNode{
    int adjvex;                         /*  鄰接點域,該頂點對應的下標  */
    EdgeType weight;
    EdgeNode *next;                     /*  鏈,指向下一個鄰接點  */
}EdgeNode;


typedef struct VertexNode{                  /*  頂點表結點  */
    VertexType data;                        /*  節點名字  */
    EdgeNode *firstedge;                    /*  邊表頭節點  */
}VertexNode;


typedef struct{
    VertexNode adjList[MAXVEX];             /*  頂點表是一個結構體數組,數組中的元素是Vertex節點  */
    int vnum,enumber;                       /*  圖中當前頂點和邊數  */
}GraphyAdjList;


/**
 * 廣度優先遍歷鄰接表
 * @param g 
 */
void BFSTraverse2(GraphyAdjList *g){
    SeqQueue *queue;
    initQueue(queue);
    int a;
    int visited[g->vnum] = {0};
    for (int i = 0; i < g->vnum; ++i) {
        if(visited[i] == 0){
            visited[i] = 1;
            printf("%c",g->adjList[i].data);  // 打印定點
            enQueue(queue,i);
            while(queueLength(queue)!=0){
                deQueue(queue,&a);
                EdgeNode *p = g->adjList[i].firstedge;  // 進入結點的鄰接表
                while (p!=NULL){
                    if(visited[p->adjvex] != 0){
                        visited[p->adjvex] == 1;
                        printf("%c\n",g->adjList[p->adjvex].data);
                        enQueue(queue,p->adjvex);
                    }
                    p = p->next;
                }
            }
        }
    }
}
```

7.4 最小生成樹

  1. 應用場景
    設想有9個村莊,這些村莊構成以下圖所示的地理位置,每一個村莊的直線距離都不同。若要在每一個村莊間架設網絡線纜,若要保證成本最小,則須要選擇一條可以聯通9個村莊,且長度最小的路線
    city.PNG-44.9kB

二. 最小生成樹

  1. 最小生成樹的概念
    (1)一個帶權值的圖:網。所謂最小成本,就是用n-1條邊把n個頂點鏈接起來,且鏈接起來的權值最小。
    (2)咱們把構造聯通網的最小代價生成樹稱爲最小生成樹
    (3)普里姆算法和克魯斯卡爾算法

  2. 普里姆算法
    以下圖,普利姆的最小生成樹過程爲:用Vs存儲已經遍歷的點,用Es存儲已經遍歷的邊
    普利姆.PNG-11.6kB
    (1)選擇D爲起點,加入Vs,與D鏈接的邊中,權值最小的邊爲5,鏈接的點爲A,所以將A加入到Vs,路徑DA加入到Es。
    (2)此時Vs中存在D和A。與DA鏈接的邊中,權值最小的爲6,鏈接的點爲F,所以F加入到Vs,邊DF加入到Es。
    (3)此時Vs中存在DAF,與DAF鏈接的邊中最小權值爲7,鏈接的點爲B,所以B加入Vs,路徑AB加入Es
    (4)重複以上過程,知道Vs中加入了全部的點

    #include <stdio.h>
    
    typedef char VertexType;
    typedef int EdgeType;
    #define MAXVEX 100
    #define IUNFINITY 65535
    typedef struct {
        VertexType vexs[MAXVEX];        /* 頂點表*/
        EdgeType arc[MAXVEX][MAXVEX];   /* 鄰接矩陣 */
        int vnum,edgenum;               /*定點的個數和邊的個數*/
    }MGraphy;
    
    /**
     * 普里母最小生成樹:鄰接表表示,時間複雜度爲O(n方)
     * @param g 
     */
    void miniSpanTree_Prim(MGraphy *g){
        int adjVetex[MAXVEX] = {0};   // 保存相關定點下標
        int lowcost[MAXVEX];      // 保存相關頂點間的權值
        lowcost[0] = 0;
        for (int i = 1; i < g->vnum; ++i)     // 循環除下標爲0外的所有結點
            lowcost[i] = g->arc[0][i];        // 初始化lowcost數組,每個元素的值爲0點和給點邊的權值
        
        for (int i = 1; i < g->vnum; ++i) {  // 循環除下標爲0外的所有結點
            int min = IUNFINITY;          // 初始化最小權值爲無窮
            int j=1,k=0;
            while(j<g->vnum){
                if(lowcost[j] != 0 && lowcost[j]<min){   // lowcost[j]爲0表示當前點與其餘點的權值數組
                    min = lowcost[j];
                    k = j;  // k爲遍歷到的最小權值邊鏈接的點
                }
                j++;
            }
            printf("(%d,%d)",adjVetex[k],k); // 打印當前頂點邊中權值最小的邊
            lowcost[k] = 0;
            for (int j = 1; j < g->vnum; ++j) {
                if(lowcost[j] != 0 && g->arc[k][j] < lowcost[j]){
                    lowcost[j] = g->arc[k][j];   // 將較小邊的權值併入lowcast
                    adjVetex[j] = k;
                }
            }
        }
    }
  3. 克魯斯卡爾算法
    克魯斯卡爾算法從邊的集合中挑選權值最小的,加入到選擇的邊集合中。若是這條邊,予以選擇的邊構成了迴路,則捨棄這條邊。
    以下圖所示,克魯斯卡爾的方法爲:
    克魯斯卡爾.PNG-59.5kB
    (1)選擇權值最小爲7的邊V7-V4
    (2)選擇權值最小爲8的邊V2-V8
    (3)選擇權值最小爲10的邊V1-V0
    (4)選擇權值最小爲11的邊V0-V5
    (5)選擇全職最小爲12的邊V1-V8,可是發現V1和V8所有是已經訪問的點,因此會構成迴路,捨棄
    (6)選擇權值最小爲16的邊V1-V6
    (7)選擇權值最小爲16的邊V3-V7
    (8)。。。。

    /* 科魯斯卡爾最小生成樹的邊的結構體  */
    typedef struct{
        int begin;
        int end;
        int weight;
    }Edge;
    
    typedef char VertexType;
    typedef int EdgeType;
    #define MAXVEX 100
    #define IUNFINITY 65535
    typedef struct {
        VertexType vexs[MAXVEX];        /* 頂點表*/
        EdgeType arc[MAXVEX][MAXVEX];   /* 鄰接矩陣 */
        int vnum,edgenum;               /*定點的個數和邊的個數*/
    }MGraphy;
    
    /**
     * 查找連線頂點的尾部下標
     */
    int find(int *parement,int f){  
        while (parement[f] > 0)
            f= parement[f];
        return f;
    }
    
    void miniSpan_Kruskal(MGraphy *g){
        Edge edges[g->edgenum];  // 定義邊集數組
        int parement[g->vnum] = {0};     // 定義一個數組,用來判斷是否造成迴路
        /**
         * 此處省略將鄰接矩陣g轉化爲邊集數組edges,並按照權值由大到小排序的過程
         */
        for(int i=0;i<g->edgenum;i++){
            int n = find(parement,edges[i].begin);
            int m = find(parement,edges[i].end);
            if(n!=m){    // n != m說明沒有造成環路
                parement[n] = m;  // 將此邊的爲節點放入到下標爲起點的parement數組中
                printf("(%d,%d)  %d",edges[i].begin,edges[i].end,edges[i].weight);
            }
        }
    }

7.5 最短路徑

一. 迪傑斯特拉

  1. 迪傑斯特拉算法
    (1)迪傑斯特拉,計算的是一個點到其他全部點的最短路徑。
    (2)它的基本思想:
            若是點 i 到點 j 的最短路徑通過k,則ij路徑中,i到k的那一段必定是i到k的最短路徑。

  2. 查找方法:
    (1)聲明2個一維數組:一個用來標識當前頂點是否已經找到最短路徑。另外一個數組用來記錄v0到該點的最短路徑中,該點的前一個頂點是什麼。
    (2)比較:計算\(v_0\)\(v_i\)的最短路徑時,比較\(v_0\)\(v_i\)\(v_0\)\(v_k\)+\(v_k\)\(v_i\)的大小,而\(v_0v_k\)\(v_kv_i\)的值是暫時得出的記錄在數組中的最短路徑。

  3. 算法實現:基於鄰接矩陣

    #include "graphy/graphy.c"   // 鄰接矩陣
    #define MAXVEX 9
    #define INFINITY 65535
    typedef int Pathmatrix[MAXVEX];             //存儲最短路徑下標的數組
    typedef int ShortPathTable[MAXVEX];     //存儲到各點最短路徑的權值和
    
    /**
     * 迪傑斯特拉:求有向圖G的V[0]到其他各點的最短路徑及帶權長度
     * @return
     */
    void shortestPath_Dijkstra(MGraphy *g,int v0,Pathmatrix *p,ShortPathTable *sptable){
        int final[MAXVEX] = {0};
        *p = {0};   // 初始化最短路徑數組爲0
        for (int i = 0; i < g->vnum; ++i)
            (*sptable)[i] = g->arc[v0][i];   //初始化sptable:讓最短路徑爲圖中v0和其他各頂點的權值
    
        (*sptable)[v0] = 0;     // sptable記錄v0到v0的權值爲0
        final[v0] = 1;               // final數組,記錄以求得v0到v0的最短路徑
    
        /* 每次循環求得v0到頂點v的最短路徑 */
        for (int i = 0; i< g->vnum ; ++i) {
            int min = INFINITY;
            int k;
            for (int j = 0; j < g->vnum; ++j) {   // 循環每一個頂點
                if(! final[j] && (*sptable)[j] < min){
                    k = j;                                     // 這個k只是把j的做用域擴大出去,供後面計算a
                    min = (*sptable)[j];              // 讓min=當前被遍歷頂點與v0點的邊的權值
                }
                final[k]  = 1;
                for (int w = 0; w < g->vnum ; ++w) {
                    int a = min+g->arc[k][w];     // 上面讓k=j,因此a=(*sptable)[j] + g->arc[j][w]。也就是:好比計算a0到a2,就比較a0a1+a1a2 與鄰接矩陣中的a0a2邊的權值
                    if(! final[w] && a < (*sptable)[w]) {
                        (*sptable)[w] = a;
                        (*p)[w] = k;                      // 這個k就是:假設該等式角標與程序無關,計算 a[i][j] > a[i][k]+a[k][j],記錄i到j的最短路徑中,j前面的節點
                    }
                }
            }
        }
    }

二. 弗洛伊德算法

  1. 弗洛伊德與迪傑斯特拉的區別
    (1)它們都是基於比較\(v_0\)\(v_i\)\(v_0\)\(v_k\)+\(v_k\)\(v_i\)的大小的基本算法。
    (2)弗洛伊德三次循環計算出了每一個點個其餘點的最短路徑,迪傑斯特拉算法用2次循環計算出了一個點到其餘各點的最短路徑 。
    (3)若是要計算出所有的點到其餘點的最短路徑,他們都是\(O(n^2)\)

    typedef int Pathmatrix_Floyd[MAXVEX][MAXVEX];             //存儲最短路徑下標的數組
    typedef int ShortPathTable_Floyd[MAXVEX][MAXVEX];     //存儲到各點最短路徑的權值和
    void shortPath_Floyd(MGraphy *g,Pathmatrix_Floyd *p,ShortPathTable_Floyd *D){
        for (int i = 0; i < ; ++i) {
            for (int j = 0; j < g->vnum; ++j) {
                (*D)[i][j] = g -> arc[i][j];
                (*p)[i][j] = j;
            }
        }
        for (int i = 0; i < g->vnum; ++i) {
            for (int j = 0; j < g->vnum; ++j) {
                for (int k = 0; k < g->vnum; ++k) {
                    if((*D)[j][k] > (*D)[j][i]+(*D)[i][k]){
                        (*D)[j][k] = (*D)[j][i]+(*D)[i][k];
                        (*p)[j][k] = (*p)[j][i];
                    }
                }
            }
        }
    }

7.6 拓撲排序

一. 拓撲排序的概念

  1. 拓撲排序是對AOV網輸出的一個序列
  2. AOV網(Active on Vertex Network):在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關係。這樣的圖稱爲活動的網。

二. 拓撲排序的算法

  1. 步驟:
    從AOV網中選擇一個入度爲0的頂點而後刪除此頂點,並刪除以此頂點爲。重複此步驟,直到輸出所有頂點或AOV網中不存在入度爲0的頂點爲止。

  2. 拓撲排序中頂點的數據結構:
    (1)前面求最小生成樹和最短路徑時,都是使用鄰接矩陣,但因爲拓撲排序中,須要刪除頂點,因此使用鄰接表方便。
    (2)由於拓撲排序中,須要刪除入度爲0的頂點,因此在原先的頂點數據結構中,加入入度域in。使頂點接都變爲
    ![image_1auud954h1rbs1cm014lb16l83ip.png-3.1kB][23]

    #include <malloc.h>
    #define MAXVEX 100
    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 numVertexes,numEdges;
    }graphAdjList,* GraphAdjList;
    
    /**
     * 拓撲排序
     * @param gl :鏈表
     * @return :若GL無迴路,則輸出排序序列並返回1;如有迴路則返回-1
     */
    int topologicalSort(GraphAdjList gl){
        int *stack = (int *)malloc(gl->numVertexes * sizeof(int));   // stack用於存儲入度爲0的節點
        int top = 0;   // stack棧頂指針
        int count;      // 加入到棧中節點個數
        for (int i = 0; i < gl->numVertexes; ++i)
            if(gl->adjList[i].in == 0)
                stack[++top] = i;
    
        while(top!=0){
            int gettop = stack[top--];
            printf("%d -> ",gl->adjList[gettop].data);
            count ++;
            for(EdgeNode *e=gl->adjList[gettop].firstEdge; e ; e=e->next){
                int k = e->adjvex;                   // 頂點的下標
                if( ! (-- gl->adjList[k].in))      // 將k號頂點入度減1
                    stack[++top] = k;                // 若是發現入度爲0,則把該頂點加入到棧中
            }
        }
    
       int res =  (count < gl->numVertexes) ? -1 : 1;      //  若是最後遍歷到的個數小於圖的總定點數,則說明有環存在,返回-1
        return res;
    }

7.7 關鍵路徑

一. 概念

  1. 拓撲排序是解決一個工程可否順序進行的問題,

  2. 當須要計算一個工程完成的最短期,就須要用關鍵路徑。

  3. 拓撲排序使用的是AOV網(定點表示活動)。關鍵路徑使用AOE網(邊表示活動)。AOV網只能表示活動之間的制約關係,而AOE網能夠用變得權值表示活動的持續時間。因此AOE網是創建在活動之間制約關係沒有矛盾的基礎上,再來分析整個工程須要多少時間。

  4. 路徑長度:路徑上各個活動持續的時間之和
    關鍵路徑:從源點到匯點具備的最大長度的路徑
    關鍵活動:關鍵路徑上的活動

二. 關鍵路徑算法

  1. 關鍵路徑算法中須要的變量:
    (1)事件最先開始時間etv(earlist time of vertex):頂點\(v_k\)的最先發生時間
    (2)事件最晚開始時間ltv(latest time of vertex) :頂點\(v_k\)的最晚發生時間,超過此時間,會延誤整個工期
    (3)活動最先開始時間(earlist time of edge):弧\(a_k\)的最先發生時間
    (4)活動最晚開始時間(latest time of edge) :弧\(a_k\)的最晚發生時間,就是不推遲工期的最晚開始時間

    int *etv,*ltv;      /* 事件最先,最晚發生時間 */
    int *stack2;        /* 用於存儲拓撲排序的棧 */
    int top2 = 0;        /* stack2的棧頂指針 */
    
    int topologicalSort2(GraphAdjList gl){
        int *stack = (int *)malloc(gl->numVertexes * sizeof(int));    /* 建棧將入度爲0的頂點入棧 */
        int top = 0;
        for (int i = 0; i < gl->numVertexes; ++i) {
            if(0 == gl->adjList[i].in)
                stack[++top] = i;
        }
    
        etv = (int *)malloc(gl->numVertexes * sizeof(int));     /* 時間最先開時間數組 */
        for (int i = 0; i < gl->numVertexes; ++i)                          /* 初始化最先開始時間數組全0 */
            etv[i] = 0;
    
        int count = 0;
        stack2 = (int *)malloc(gl->numVertexes * sizeof(int));
        while (top !=0 ){
            int gettop = stack[top--];
            count ++;
            stack2[++top2] = gettop;              /* 將彈出的頂點序號壓入拓撲排序的棧 */
    
            for (EdgeNode *e = gl->adjList[gettop].firstEdge; e ; e = e->next) {
                int k = e->adjvex;
                if( !(-- gl->adjList[k].in) )
                    stack[++top] = k;
                if( (etv[gettop] + e->weight) > etv[k] )    /* 求各點事件最先發生時間值 */
                    etv[k] = etv[gettop] + e->weight;
            }
        }
    
        if(count < gl->numVertexes)
            return -1;
        else
            return 1;
    }
    
    void criticalPath(GraphAdjList gl){
        topologicalSort2(gl);
        ltv = (int *) malloc (gl->numVertexes * sizeof(int));     /* 事件最晚發生時間 */
        for (int i = 0; i < gl->numVertexes; ++i)
            ltv[i] = etv[gl->numVertexes -1];                                /* 初始化ltv */
    
        int k;
        while(top2 != 0){
            int gettop = stack2[top2--];
            for(EdgeNode *e=gl->adjList[gettop].firstEdge ; e ; e=e->next){
                k = e->adjvex;
                if(ltv[k] - e->weight < ltv[gettop])
                    ltv[gettop] = ltv[k] - e->weight;
            }
        }
    
        for (int j = 0; j < gl->numVertexes; ++j) {
            for (EdgeNode *e = gl->adjList[j].firstEdge; e ; e = e->next) {
                k = e->adjvex;
                int ete = etv[j];                        /* 活動最先發生時間 */
                int lte = ltv[k] - e->weight;      /* 活動最遲發生時間 */
                if(ete == lte)
                    printf("<v_%d , v_%d> length: %d",gl->adjList[j].data,gl->adjList[k].data,e->weight);
            }
        }
    }
相關文章
相關標籤/搜索