图的一些总结

1.图的定义
    1)线性表我们把数据元素叫做元素,树中将数据元素叫结点,在图中数据元素,我们则称之为顶点。
    2)线性表中可以没有数据元素,称为空表。树中可以没有结点,叫做空树。再图结构中,不允许没有顶点。
    3)线性表中,相邻的数据元素之间具有线性关系,树结构中,相邻两层的结点具有层次关系,而图中两个顶点之间都可能有关系,顶点
      之间的逻辑关系来表示,边集可以是空的。
2.各种图定义
    无向边:若顶点v1到v1之间的边没有方向,则称这条边为无向边,用无序偶对来表示。如果图中任意两个顶点之间的边都是无向边,则称为
    无向图。
    有向边:若从顶点vi到vj的边有方向,则称这条边为有向边,也称为弧。
    如果图中任意两个顶点之间的边都是有向边,则这个图为有向图。
    *看清楚,有向边用小括号"()"表示,而有向边则是用尖括号"<>"表示。
    入度,出度
    连通图,强连通
    图按照有无方向分为无向图和有向图。无向图由顶点和边构成,有向图由顶点和弧构成,弧有弧头和弧尾之分。
    图按照边或弧的多少分为稀疏图和稠密图。如果任意两个顶点之间都存在边叫完全图。有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。
    图中顶点之间有邻接点。无向图顶点的边数叫做度,有向图顶点分为出度和入度。
    图上的边或弧上带权则称为网。
    图中顶点间存在路径,两顶点存在路径则说明是连通,如果路径最终回到起始点则称为环,当中不重复叫简单路径。若任意两顶点都是连通的,则图就是
    连通图,有向则称为强连通。
    无向图中连通且n个顶点n-1条边叫生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向图。一个有向图由若干棵有向树构成生成森林。
2.图的存储结构
    1)邻接矩阵
        考虑到图是由顶点和边或弧两部分组成,合在一起比较困难,那就很自然考虑到分两个结构分别存储。顶点不分大小、主次,需要一个二维数组来存储。
    
        图的邻接矩阵存储方式是两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
        无向图是的边数组是一个对称矩阵。
        对称矩阵
            即从矩阵的左上角到右下角的主对角轴,右上角的元与左下角相对应的元全部都是相等的。
    代码实现:
        typedef char VertexType;/* 顶点类型由用户自定义 */
        typedef int EdgeType;  /* 边上的权值类型应由用户定义 */
        #define MAXVEX  100 /* 最大顶点数,应由用户定义*/
        #define INFINITY 65535 /* 用65535代表无穷*/
        typedef struct
        {
            VertexType vexs[MAXVEX]; /*顶点表*/
            EdgeType  arc[MAXVEX][MAXVEX];/*邻接矩阵,可看做边表 */
            int numVertexes, numEdges;/* 图中当前的 顶点数 和 边数 */
        }MGraph;
    有了这个结构定义,我们构造一个图,其实就是给顶点表和边表输入数据的过程。我们看看无向图的创建代码。
        void CreateMGraph(MGraph  *G)
        {
            int i,j,k,w;
            printf("输入顶点数和边数: ");
            scanf("%d,%d",&G->numVertexes,&G->numEdges);  /* 输入顶点数和边数 */
            for(i=0;i<G->numVertexes;i++)/* 读入顶点信息,建立顶点表*/
                scanf("%c",&G->vexs[i]);
            for(i=0;i<G->numVertexes;i++)
                for(j=0;j<G->numVertexes;j++)
                    G->arc[i][j] = INFINITY;/*邻接矩阵初始化*/
            for(k=0;k<G->numEdges;k++)  /**  读入numEdges条边,建立邻接矩阵**/
            {
                printf("输入边(vi,vj)上的下标i,下标j和权w: ");
                scanf("%d,%d,%d",&i,&j,&w);/*  输入边(vi,vj)上的权w */
                G->arc[i][j] = w;
                G->arc[j][i] = G->arc[i][j];/* 因为是无向图,矩阵对称 */
            }
            
        }
    从代码也可以得到,n个顶点和e条边的无向网图的创建,时间复杂度为O(n+n*n+e),其中对邻接矩阵的初始化耗费了O(n*n)的时间。
    2)邻接表
        邻接矩阵是不错的一种图存储结构,但是我们发现,对于边数相对顶点较少的图,这种结构是存在对存储空间的极大的浪费
    数组与链表相结合的存储方法称为链接表。
    邻接表的处理方法:
        1.图中顶点用一个一维数组存储。
        2.图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储。
    定义结构体:
        typedef char VertexType;  /* 顶点类型应由用户定义*/
        typedef int EdgeType;  /* 边上的权值类型应该由用户定义*/
        
        typedef struct EdageNode   /* 边表结点 */
        {
            int adjvex; /* 邻边点域,存储该结点对应的下标*/
            EdgeType weight;/* 用于存储权值,对于非网图可以不需要*/
            struct EdgeNode *next;/* 链域,指向下一个邻接点 */
        }EdgeNode;
        
        typedef struct VertexNode  /* 顶点表结点 */
        {
            VertexType data;/*  顶点域,存储顶点信息 */
            EdgeNode *firstedge; /* 边表头指针 */
        }VertexNode,AdjList[MAXVEX];

        typedef struct  
        {
            AdjList adjList;
            int numVertexes,numEdges;   /* 图中当前顶点数和边数  */
        }GraphAdjList;
    无向图的邻接表创建代码如下:(这里没有搞明白,研究一下头插法)
        void CreateALGraph(GraphAdjList *G)
        {
            int i,j,k;
            EdgeNode *e;
            printf("输入顶点数和边数: ");
            scanf("%d,%d",&G->numVertexes,&G->numEdges);/* 输入顶点数和边数 */
            for(i=0;i<G->numVertexes;i++)  /* 读入顶点信息,建立顶点表 */
            {
                scanf(&G->adjList[i].data);  /* 输入顶点信息  */
                G->adjList[i].firstedge = NULL;   /* 将边表置为空表 */
            }
            for(k=0;k<G->numEdges;k++)   /* 建立边表*/
            {
                printf("输入边(vi,vj)上的顶点序号: ");
                scanf("%d,%d",&i,&j);   /* 输入边(vi,vj)上的顶点序号 */
                e = (EdgeNode *)malloc(sizeof(EdgeNode)); /* 向内存申请空间 */
                                    /* 生成边表结点 */
                e->adjvex = j; /* 邻接序号为j */
                e->next = G->adjList[i].firstedge; /* 将e指针指向当前顶点指向的结点 */
                G->adjList[j].firstedge = e;  /* 将当前顶点的指针指向 e */
                
                e = (EdgeNode *)malloc(sizeof(EdgeNode)); /* 向内存申请空间 */
                                    /* 生成边表结点 */
                e->adjvex = i; /* 邻接序号为j */
                e->next = G->adjList[j].firstedge; /* 将e指针指向当前顶点指向的结点 */
                G->adjList[j].firstedge = e;  /* 将当前顶点的指针指向 e */
            }
        }
    3)十字链表
        十字链表的好处就是因为把邻接表和逆邻接表整合在了一起,这样既容易找到以vi为尾的弧,也容易找到以vi为头的弧,因
          而容易求得顶点的出度和入度。而且它除了结构复杂
一点外,其实创建图算法的时间复杂度和邻接表相同,因此,再有向图中的
          应用中,十字链表是非常好的数据结构模型。
    4)邻接多重表
        邻接多重表与邻接表区别,仅仅是在于同一条表在邻接表中用两个结点表示,而在邻接多重表中只有一个结点,这样对边的操作
         就方便的多了,修改指向就可以了。
    5)边集数组
        边集数组是由两个一维数组构成,一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、
        终点下标(end)和权(weight)组成。
            ------------------------
            |begin  |  end | weight|
            ------------------------
                  其中begin是存储起点下标,end是存储终点下标,weight是存储权值
3.图的遍历
    从图中某一顶点出发访遍图中其余顶点,且每一个顶点仅被访问一次,这个过程就要做图的遍历。
    对于图的遍历来说,如何避免因回路陷入死循环,就需要科学地设计遍历方案,通常由两种遍历次序方案:深度优先和广度优先遍历。
    1)深度优先遍历
        深度优先遍历(Depth_First_Search),也有称为深度优先搜索,简称DFS。
        深度优先遍历其实就是一个递归的过程。
        如果我们用的是邻接矩阵的方式,则代码如下:
        typedef int Boolean;  /* Boolean是布尔类型,其值是true或者false */
        Boolean  visitad[MAX];    /* 访问标志的数组 */
        /* 邻接矩阵的深度优先递归算法 */
        void DFS(MGraph G,int i)
        {
            int j;
            visitad[i] = TRUE;
            printf("%c", G.vexs[i]); /* 打印顶点,也可以其他操作 */
            for(j=0; j<G.numVertexes; j++)
                if(G.arc[i][j] == 1 && !visited[j])
                        DFS(G,j);    /* 对为访问的邻接顶点递归调用 */
        }
        /* 邻接矩阵的深度优先遍历操作  */
        void DFSTraverse(MGraph G)
        {
            int i;
            for(i=0; i<G.numVertexes; i++)
                visitad[i] = FALSE;   /* 初始化所有顶点状态都是未访问过的状态 */
            for(i = 0;i<G.numVertexes; i++)
                if(!visitad[i]) /* 对未访问的顶点调用DFS,若是连通图,只会执行一次 */
                    DFS(G,i);
        }
        链表实现方法:
        /************ 邻接表的深度优先递归算法 *****************/
        void DFS(GraphAdjList GL, int i)
        {
            EdgeNode *p;
            visited[i] = TRUE;
            printf("%c",GL->adjList[i].data); /* 打印顶点,也可以其他操作 */
            p = GL->adjList[i].firstedge;
            while(p)
            {
                if(!visited[p->adjvex])
                    DFS(GL,p->adjvex);/* 对未访问的邻接顶点递归调用 */
                p= p->next;
            }
        }
        /**    邻接表的深度遍历操作           */
        void DFSTraverse(GraphAdjList GL)
        {
            int i;
            for(i =0;i< GL->numVertexes; i++)
                visited[i] = FALSE; /** 初始化所欲偶顶点状态都是未访问过的状态 **/
            for(i =0;i<GL->numVertexes; i++)
                if(!visited[i]) /* 对未访问过的顶点调用DFS,若是连通图,只会执行一次 */
                    DFS(GL,i);
        }
    2)广度优先遍历
        广度优先遍历(Breadth_First_Search),又称为广度优先搜索,简称BFS.
        邻接矩阵结构的代码:
        /* 邻接矩阵的广度遍历算法 */
        void BFSTraverse(MGraph G)
        {
            int i,j;
            Queue Q;
            for(i=0;i<G.numVertexes;i++)
                visited[i] = FALSE;
            InitQueue(&Q);   /* 初始化一辅助用的队列 */
            for(i=0;i<G.numVertexes;i++)/* 对每一个顶点做循环 */
            {
                if(!visited[i])   /* 若是未访问过就处理 */
                {
                    visited[i] = TRUE;/*设置当前顶点访问过*/
                    printf("%c",G.vexs[i]); /* 打印顶点,也可以其他操作*/
                    EnQueue(&Q,i);/*将此顶点入队列 */
                    while(!QueueEmpty(Q)) /*若当前队列不为空*/
                    {
                        DeQueue(&Q,&i); /* 将队中元素出队列,赋值给i */
                        for(j=0;j<G.numVertexes;j++)
                        {
                            /*判断其他顶点若与当前顶点存在边且未访问过 */
                            if(G.arc[i][j] == 1 && !visited[j])
                            {
                                visited[j] = TRUE; /* 将找到的此顶点标记为已访问 */
                                printf("%c ",G.vexs[j]); /* 打印顶点 */
                                EnQueue(&Q,j);    /* 将找到的此顶点入队列 */
                            }
                        }
                    }
                }
            }
        }
        邻接表的广度优先遍历,代码与邻接矩阵差异不大,代码如下:
        /* 邻接表的广度遍历算法 */
        void BFSTraverse(GraphAdjList GL)
        {
            int i;
            EdgeNode *p;
            Queue Q;
            for(i=0; i< GL->numVertexes; i++)
            {
                visited[i] = FALSE;
            }
            InitQueue(&Q);
            for(i = 0;i<GL->numVertexes;i++)
            {
                if(!visited[i])
                {
                    visited[i] = TRUE;
                    printf("%c",GL->adjList[i].data); /*打印顶点,也可以其他操作*/
                    EnQueue(&Q,i);
                    while(!QueueEmpty(Q))
                    {
                        DeQueue(&Q,&i);
                        p = GL->adjList[i].firstedge;/*找到当前顶点边表链表头指针*/
                        while(p)
                        {
                            if(!visited[p->adjvex]) /*若此顶点未被访问*/
                            {
                                visited[p->adjvex] = TRUE;
                                printf("%c ",GL->adjList[p->adjvex].data);
                                EnQueue(&Q,p->adjvex); /* 将此顶点入队列*/
                            }
                            p = p->next; /* 指针指向下一个邻接点*/
                        }
                    }
                }
            }
        }
4.最小生成树
    构造连通网的最小代价生成树称为最小生成树。
    找连通图的最小生成树,经典算法有两种,普里姆算法和克鲁斯卡尔算法。
    1)普利姆(Prim)算法
        /* Prim 算法最小生成树 */
        void MiniSpanTree_Prim(MGraph G)
        {
            int min,i,j,k;
            int adjvex[MAXVEX];/* 保存相关顶点下标 */
            int lowcost[MAXVEX]; /* 保存相关顶点间边的权值 */
            lowcost[0] = 0; /*初始化第一个权值为0 ,即v0加入生成树 */
                            /*lowcost 的值为0,在这里就是此下标的顶点已经加入生成树*/
            adjvex[0] = 0; /*初始化第一个顶点下标为0*/
            for(i=1;i<G.numVertexes; i++)  /*循环除下标为0外的全部结点*/
            {
                lowcost[i] = G.arc[0][i]; /*将v0顶点与之有边的权值存入数组*/
                adjvex[i] = 0; /* 初始化都为v0的下标*/
                
            }
            for(i = 0;i<G.numVertexes; i++)
            {
                if(lowcost[j] != 0 && lowcost[j] < min)
                {/*如果权值不为0,且权值小于min */
                    min = lowcost[j]; /* 则让当前权值成为最小值 */
                    k = j;/*将当前权值成为最小值*/
                }
                j++;
            }
            printf("(%d,%d)", adjvex[k],k); /*打印当前顶点边中权值最小边 */
            lowcost[k] = 0; /* 将当前权值设置为 0 ,表示此顶点已经完成任务 */
            for(j = 1;j< G.numVertexes; j++)
            {
                if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j])
                {
                    /* 若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值*/
                    lowcost[j] = G.arc[k][j]; /*将较小的权值存入lowcost */
                    adjvex[j] = k;   /* 将下标为k的顶点存入adjvex*/
                }
            }
        }
    2)克鲁斯卡尔(Kruskal)算法
        普利姆(Prim)算法是以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的。我们可以直接就以边为目标去构建,因为权值是在边上,直接去找
    最小权值的边来构建生成最小树。
    edge边集数组结构的定义代码:
    /*对边集数组Edge结构的定义*/
    typedef struct
    {
        int begin;
        int end;
        int weight;
    }Edge;
    克鲁斯卡尔算法代码如下,左侧数字为行号。其中MAXEDGE为边数量的极大值,此处大于等于15即可,MAXVEX为顶点个数最大值,此处大于等于9即可。
    实现代码:
    /* Kruskal 算法生成最小生成树 */
    void MiniSpanTree_Kruskal(MGraph G)  /* 生成最小生成树 */
    {
        int i,n,m;
        Edge edges[MAXEDGE]; /*定义边集数组 */
        int parent[MAXVEX]; /*定义一数组用来判断边与边是否形成环路 */
        /* 此处省略将邻接矩阵G转化为边集数组edges并按由小到大排序的代码*/
        for(i = 0; i< G.numVertexes; i++)
            parent[i] = 0;  /*初始化数组值为0 */
        for(i=0;i<G.numEdges; i++) /*循环每一条边 */
        {
            n = Find(parent,edges[i].begin);
            m = Find(parent,edges[i].begin);
            if(n != m) /*假如n与m不等,说明此边没有与现有生成树形成环路 */
            {
                parent[n] = m;  /* 将此边的结尾顶点放入下标为顶点的parent中*/
                                /* 表示此顶点已经在生成树集合中*/
                printf(" (%d, %d) %d ",edges[i].begin,edges[i].end,edges[i].weight);
            }
        }
    }
    
    int Find(int *parent, int f)/* 查找连线顶点的尾部下标 */
    {
        while(parent[f] > 0)
            f = parent[f];
        return  f;
    }
5.最短路径
        1)迪杰斯特拉(Dijkstra)算法
            按路径长度递增的次序产生最短路径的算法。
            代码实现:
            #define MAXVEX 9
            #define INFINITY 65535
            typedef int Pathmatirx[MAXVEX];  /*用于存储最短路径下标的数组*/
            typedef int ShortPathTable[MAXVEX];  /*用于存储到各点最短路径的权值和*/
            /* Dijkstra算法,求有向网G的v0顶点到其余顶点v最短路径P[v]及带权长度D[v]*/
            /* P[v]的值为前驱顶点下标,D[v]表示v0到v的最短路径长度和 */
            void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatirx *P,ShortPathTable *D)
            {
                int v,w,k,min;
                int final[MAXVEX]; /* final[w] = 1 表示求得顶点v0至vw的最短路径 */
                for(v =0;v<G.numVertexes; v++)
                {
                    final[v] = 0; /*全部顶点初始化为未知最短路径状态 */
                    (*D)[v] = G.matirx[v0][v]; /* 将与v0点右连线的点加上权值 */
                    (*p)[v] = 0; /*初始化路径数组p为 0 */
                }
                (*D)[v0] = 0; /*v0至v0的路径为0 */
                final[v0] = 1; /* v0 至v0不需要路径 */
                /* 开始主循环,每次求得v0到某个v顶点的最短路径 */
                for(v = 1; v < G.numVertexes; v++)
                {
                    min = INFINITY;  /*当前所知离v0顶点的最近距离*/
                    for(w = 0; w<G.numVertexes; w++)  /*寻找离v0最近的顶点 */
                    {
                        if(!final[w] && (*D)[w]<min)
                        {
                            k = w;
                            min = (*D)[w]; /*w顶点离v0顶点最近*/
                        }
                    }
                    final[k] = 1; /* 将目前找到的最近的顶点置为1 */
                    for(w= 0; w<G.numVertexes; w++)  /*修正当前最短路径及距离*/
                    {
                        /* 如果经过v顶点的路径比现在这条路径的长度短的话*/
                        if(!final[w] && (min + G.matirx[k][w] < (*D)[w]))
                        {
                            /*说明了找到了更短的路径,修改D[w]和P[w] */
                            (*D)[w] = min + G.matirx[k][w]; /* 修改当前路径长度 */
                            (*p)[w] = k;
                        }
                    }
                }
            }
        2)弗洛伊德(Floyd)算法
            这个算法涉及到了矩阵的知识,上代码
            typedef int Pathmatirx[MAXVEX][MAXVEX];
            typedef int ShortPathTable[MAXVEX][MAXVEX];
            /*Floyd算法,求网图G中各顶点v到其余顶点w最短路径P[v][w]及带权长度D[v][w] */
            void ShortestPath_Floyd(MGraph G, Pathmatirx *p, ShortPathTable *D)
            {
                int v,w,k;
                for(v=0;v<G.numVertexes; ++v)/*初始化D与P*/
                {
                    for(w=0;w<G.numVertexes; ++w)
                    {
                        (*D)[v][w] = G.matirx[v][w]; /* D[v][w]值即为对应点间的权值 */
                        (*P) [v][w] = w; /*初始化P */
                    }
                }
                for(k = 0; k<G.numVertexes; ++k)
                {
                    for(v = 0;v < G.numVertexes; ++v)
                    {
                        for(w = 0;w<G.numVertexes; ++w)
                        {
                            if((*D)[v][w] > (*D)[v][k] + (*D)[k][w])
                            {
                                /* 如果净多下标为k顶点路径比原两点间路径更粗*/
                                /* 将当前两点间权值设为更小的一个 */
                                (*D)[v][w] = (*D)[v][w] + (*D)[k][w];
                                (*P)[v][w] = (*P)[v][k]; /*路径设置净多下标为k的顶点*/
                            }
                        }
                    }
                }
            }
6.拓扑排序
    在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称为AOV网。
    所谓拓扑排序,其实就是对一个有向图构造拓扑序的过程。
    1)拓扑排序算法
        基本思路:
            从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此为顶点为尾的弧,继续重复此步骤,直到输出全部顶点
        或者AOV网中不存在入度为0的顶点为止。
    拓扑排序中,设计到的结构代码如下:
    typedef struct EdgeNode    /*边表结点*/
    {
        int adjvex; /* 邻接点域,存储该顶点对应的下标 */
        int weight;   /*用于存储权值,对于非同图可以不需要 */
        struct RdgeNode *next;   /* 链域,指向下一个邻接点 */
    }EdgeNode;
    
    
    typedef struct VertexNode   /* 顶点表结点 */
    {
        int in;        /* 顶点入度 */
        int data;      /* 顶点域,存储顶点信息 */
        EdgeNode *firstedge; /* 边表头指针 */
    }VertexNode,AdjList[MAXVEX];
    
    typedef struct
    {
        AdjList adjList;
        int numVertexes,numEdges; /* 图中当前顶点数和边数 */
    }graphAdjList, *GraphAdjList;
    /* 拓扑排序,若GL无回路,则输出拓扑排序序列并返回OK,若有回路则返回ERROR */
    Status TopologicalSort(GraphAdjList GL)
    {
        EdgeNode *e;
        int i,k,gettop;
        int top = 0;  /*用于栈指针下标 */
        int count = 0; /* 用于统计输出顶点的个数 */
        int *stack;  /* 建栈存储入度为0 的顶点 */
        stack = (int *)malloc(GL->numVertexes *sizeof(int));
        for(i = 0;i<GL->numVertexes; i++)
            if(GL->adjList[i].in == 0)
                    stack[++top] = i; /* 将入度为0 */
        while(top != 0)
        {
            gettop = stack[top--]; /* 出栈 */
            printf("%d -> ",GL->adjList[gettop].data); /* 打印此顶点 */
            count++; /*统计输出顶点数 */
            for(e = GL->adjList[gettop].firstedge; e; e=e->next)
            {/* 对此顶点弧表遍历 */
                k = e->adjvex;
                if(!(--GL->adjList[k].in))/*将k号顶点邻接点的入度减为1*/
                    stack[++top] = k; /* 若为0则入栈,以便于下次循环输出 */
            }
        }
        
        if(count < GL->numVertexes) /* 如果count 小于顶点数 ,说明存在环 */
        {
            return ERROR;
        }
        else
            return OK;
                    
    }
7.关键路径
    拓扑排序主要是为解决一个工程能否顺序进行的问题,但有时我们还需要解决工程完成需要的最短时间问题。
    在AOV网的基础上,我们来介绍一个新的概念。在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,
    这种有向图的边表示活动的网,我们称之为AOE网。我们把AOE网中没有入边的顶点称为始点或源点,没有出边的顶点称为终点或汇点。
    需要几个参数:
        前两个事件是预计:
        1.事件的最早发生时间etv(earlist time of vertex):即顶点vk的最早发生时间。
        2.事件的最晚发生时间ltv(latest time of vertex):即顶点vk的最晚发生时间,也就是每个顶点对应的事件最晚需要开始的时间,超过此时间将会延误整个工期。
        这两个事件开始做的时间预计:
        3.活动的最早开工时间ete (earlist time of edge):即弧ak的最晚发生时间,也就是不推迟工期的最晚开工时间。
        4.活动的最晚开工时间lte(latest time of edge):即弧ak的最晚发生时间,也就是不推迟工期的最晚开工时间。
    关键路径算法:
        将AOE网转化为邻接表结构,注意与拓扑排序时邻接表结构不同的地方在于,这里弧链表增加了weight域,用来存储弧的权值。
        首先声明几个全局变量:
        int *etv,*ltv;  /* 事件最早发生时间和最迟发生时间数组 */
        int *stack2;   /* 用于存储拓扑序列的栈 */
        int top2;     /* 用于stack2的指针 */
        
        /* 拓扑排序,用于关键路径计算 */
        Status TopologicalSort(GraphAdjList  GL)
        {
            EdgeNode  *e;
            int i,k,gettop;
            int top = 0; /* 用于栈指针下标 */
            int count;  /* 用于统计输出顶点的个数 */
            int *stack;  /* 建栈将入度为0的顶点入栈 */
            stack = (int *)malloc(GL->numVertexes *sizeof(int));
            for(i=0; i<GL->numVertexes; i++)
                if(0 == GL->adjList[i].in)
                    stack[++top] = i;
            top2 = 0;   /*初始化为0 */
            etv = (int*)malloc(GL->numVertexes * sizeof(int)); /* 时间最早发生时间 */
            for(i=0; i<GL->numVertexes; i++)
                etv[i] = 0;  /* 初始化为0 */
            stack2 = (int*)malloc(GL->numVertexes * sizeof(int));  /* 初始化 */
            while(top != 0)
            {
                gettop = stack[top--];
                count++;
                stack2[++top2] = gettop; /* 将弹出的顶点序号压入拓扑序列的栈 */
                
                for(e = GL->adjList[gettop].firstedge;e;e = e->next)
                {
                    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 ERROR;
            else
                return OK;
        }
    
   

The future's not set,there is no fate but what we make for ourselves.
原文地址:https://www.cnblogs.com/wang1994/p/9115483.html