读书笔记-----数据结构七(图的知识点)

在这一节中,我们记录图这种结构,学习其中的相关的知识点。开始之旅,上车噶·~

2018年6月5日思考
图的存储结构有五种方式,分别是邻接矩阵,邻接表,十字链表(有向图),邻接多重表(无向图),边集数组。

图的深度优先遍历类似于树的前序遍历情况;图的广度优先遍历类似于树的层序遍历的情况。

用邻接矩阵实现深度优先遍历:
首先,需要一个list来存储各个节点的访问情况,然后对每一个点判断其是否被访问过,如果没有被访问过,则访问,接下来判断访问节点邻接的点,如果没有被访问过,则访问,最终没有访问的情况,则返回上一层进行判断,一直到主循环中所有的结点均被访问,即可。
用邻接表实现深度优先遍历:
首先,也是需要一个list来存储各个结点的访问情况,然后对每一个点判断,如果没有被访问过,则访问其第一条边,然后判断这个点是否被访问,如果没有被访问,则访问,然后从这个点开始访问其他点,如果最后没有要访问的点了,则返回上一级判断下一条边,最后知道所有的点都被访问过,则结束。
用邻接矩阵实现广度优先遍历:
广度优先遍历最主要的特点,是采用队列来实现,对于未访问过的结点,访问入队列,否则,不入队列,然后对队列元素出队列,然后判断这个元素一行的其他邻接点的访问情况,然后判断是否还有要入队列的,如果没有则本结点所有相关的结点均被访问完,考虑下一个结点的情况,然后一次判断。。只要这个结点被访问,则说明这个结点的邻接的所有的顶点都会被访问。所以只要寻找没有被访问的结点即可。
用邻接表实现广度优先遍历:
也是需要借助队列来实现,与深度优先遍历的区别在于,不需要回退,一个单链表走到底,然后走下一个单链表即可。

最小生成树
从图论学习中,我们可以知道最小生成树的方法有三种:prim算法,Kruskal算法,破圈法。三种算法其实对应于二种思路,第一种是避圈法,prim算法和Kruskal算法均是这一类算法,而对于破圈法,是另一种思路。对于prim算法而言,是从点出发,然后寻找从这个点出发的最小的边元素,然后从边的另一条边出发,然后寻找从这个边出发最小的元素,最终完成所有结点的迭代问题;类似于贪心算法的求解。对于Kruskal算法而言,是从边集数组出发,我们对边的权值进行排序,然后从小权值的边出发,然后判断是否形成环,如果没有形成环,则选择,否则,不选择。对于破圈法, 是管梅谷老先生发现的,是对于图中出现的圈,不断的进行破圈,去除圈中权值最大的边。直到图中没有圈,则算法结束。

prim算法具体代码实现时的思路:
利用邻接矩阵来实现最小生成树的问题。我们需要二个list,一个list1用于存储现在可选的路径长度是什么,另一个list2用于存储当前最小的路径中相关的边。我们从点出发,然后选择这个点的最小的边,然后从这个边的另一个点的邻接情况出发,更新list1,list2,然后从二个点中选择最短的哪个点,贪心算法,然后从当前的list1中再选择最短的一条路径,最终完成整个树的构建。
证明prim算法得到的生成树是最小生成树,我个人觉得网上的反证法有点说不通啊,是不是我没有学懂,我个人认为,反证假设的应该是生成树不是最小的生成树,则存在这样一条边,树中存在圈,破圈,可以得到更小的生成树,但这与贪婪算法,寻找当前最短边矛盾,因为不可能出现选到大边,而没有选到小边的情况。
时间复杂度O(N^2)

Kruskal算法具体代码实现时的思路:
利用边集数组来实现最小生成树的问题,并对边集数组来进行从小到大进行排序。此时最精华的应该是判断回路是否存在的思路。真的可以说巧妙了。利用一个一维数组来判断是否有环,利用一维数组得下标来表示起点,然后一维数组得值来表示可达的情况。到达值为0的情况下,然后结束,判断从起点开始到终点是否到达一样的终点,终点一样,说明形成环,不能要这条边,否则,要这条边。
时间复杂度O(eloge)

迪杰斯特拉(Dijkstra)算法具体代码思路:
我们需要三个list结构来维护所需要的东西,list1用来存储当前已经求了最短路的点,已经求了最短路的标记为1,其余标记为0;list2用来存储到当前结点的最短路的长度情况;list3用来存储前一跳的结点,是索引,以便之后我们来恢复最短路走法;
算法思路在于维护结点结合,以及维护当前处理情况下,到其他点的最短路的长度的情况,然后对其更新。第一步,初始化三个数组;第二步,先找最近的一条边,然后找到这个点的最短路长度,更新数组,然后再这个点的基础上,再维护更新长度数组;然后在找更新之后的最短路长度,重复第二步。。与Prim算法类似,Prim算法是找最短的边,然后也是需要维护当前结点中的最短的边的情况。
(寻找+更新 –>再寻找+再更新的过程)
迪杰斯特拉只能寻找到某一个点到其他点的最短路的情况,时间复杂度为O(n^2)

弗洛伊德(Floyd)算法具体代码思路:
需要二个二维数组(与邻接矩阵同维度),我们对其进行初始化,然后三层循环,最外层是中间的变量,然后根据条件来进行判断是否需要进行更新。最终得到的是所有结点到所有结点的最短路径,时间复杂度为O(n^3).

二种避圈的算法,在边比较多的情况下,也就是稠密图的情况下,用Prim算法比较较好,当边数比较少的情况,采用krustal算法比较好。

在需要计算不同点到不同点的最短路径时采用floyd算法,当需要计算同一点到不同点的最短路径时,采用dijkstra算法。

拓扑排序
DAG图(Directed Acyclic Graph),无环图(有向,无环)
AOV网(Active On Vertex Network),有向图为顶点表示活动的网,弧表示活动之间的制约关系,不能存在环路。
拓扑排序是将有向图构造成拓扑序列(顶点之间若存在一条路径,则先后顺序确定)的过程。
我们用邻接表来实现拓扑排序的问题,我们在顶点结构中,加一维入度的情况。我们将入度为0的顶点入栈,然后对所有度为0的点出栈,并对其邻接的点的度数减1,如果度为0则入栈,然后出栈,最后栈空的时候,判断输出的结点个数 ,如果少于总数,则说明有环,不能进行拓扑排序,否则可以进行拓扑排序。
AOE网(Activity On Edge Network),带权有向图中,顶点表示事件,有向边表示活动,边的权值表示活动持续的时间,有向图表示活动的网。(源点,汇点)
AOV网和AOE网都是对工程建模的,但他们还是有很多的不同,AOV网是用顶点代表活动的网,弧描述的是活动的制约关系,是表示了先后情况;AOE网是用边表示活动的网,边上的权值代表活动持续的时间。AOE网是需要建立在活动之间制约关系没有矛盾的基础上,然后再分析至少需要多长时间来完成的情况。
etv(early time of vertex)
ltv(latest time of vertex)
ete(earliest time of edge)
lte(lastest time of edge)

图的定义:
图(graph)是由顶点的有穷非空集合和顶点之间边的集合来组成,通常表示为:G(V,E),其中G表示一个图,V表示图G中顶点的集合,E表示图G中边的集合。
在图的定义中,需要重要的一点在于V必须是有穷非空的集合,即不准是空集,但对于E则没有这样的限制。
特殊的图定义:
无向图(边没有方向的图)
有向图(边存在方向的图)
简单图(无环,无重边的图)
无向完全图(无向,任意二点之间都存在边的图)
有向完全图(有向,任意二点之间都存在不同方向的弧的图)
稀疏图(稠密图)(根据边或者弧的多少来划分,主观看法)
网(图中边或者弧具有权)
子图(子图的V,E均是母图V,E的子集)

在图中顶点以及边之间的关系:
顶点与顶点之间是是否邻接;顶点与边的关系是是否相关联,引出度的概念。
在无向图中,一条边有二度的存在,而对于有向图而言,一条弧有入度和出度的概念。
路径是连接二个点之间的中间点的集合。路径的长度时途经边或者弧的个数。路径为简单路径说明路径途径的点都不重复。
回路或者环是指路径的第一个点和最后一个点是同一个点,若回路也是简单路径,此时称为了简单回路或者简单环。

在无向图中,若二点之间存在路径,则说明二点之间是连通的,若任意二点都是连通的,则图称为连通图。在无向图的连通图中有一个概念是连通分量的概念,也就是极大的连通子图,首先是连通的子图,其次是极大的,什么是极大呢?也就是说在加任意一个点就不连通了,这就是极大,最后,对于连通分量而言,一般包含了与连通分量中存在的点相关的所有的边。
在有向图中,如果对于每一对顶点,都有相互到达的路径,则图为强连通图,在有向图中,极大的强连通子图称为有向图的强连通分量。

连通图的生成树
连通图的生成树,也就是顶点集合不变,生成树的边集合是原图的边集合的子集,但又满足连通图的特性,并且边数为n-1,是一个极小连通图,极小的概念说明去掉图中的任意一条边则不连通。
有向树是指只有一个顶点的入度为0,其他顶点的入度为1,则是一棵有向树。
有向图的生成森林是由若干棵有向树来组成,含有原有向图中的所有的顶点,但只有足以构成若干棵不相交的有向树的弧。

图的基本的操作:

1,CreateGraph(*G, V, VR)根据顶点集与边弧集来构造图G
2,DestroyGraph(*G)销毁存在的图G
3,LocateVex(G, u)如果图G中存在点u,则返回图中的位置
4,GetVex(G, u)返回G图中的点u的值
5,PutVex(G, v, value)给图G中的点v赋值value
6,FirstAdjVex(G, *v)返回图G中点v的一个邻接顶点
7,NextAdjVex(G, v, *w)返回图G中点v相对于点w的下一个邻接的顶点
8,InsertVex(*G, v)在图G中添加一个新的点
9,DeleteVex(*G, v)在图G中删除点v以及其相关的弧
10,InsertArc(*G, v, w)在图G中添加<v,w>的弧,若为无向图则也需要增加<w,v>的弧
11,DeleteArc(*G, v, w)在图G中删除<v, w>的弧,如果是无向图,则还需要删去<w,v>的对称弧
12,DFSTraverse(G)在图G中深度优先遍历,在遍历过程中对每个结点来进行调用
13,HFSTraverse(G)在图G中广度优先遍历,在遍历过程中对每个结点来进行调用

图的存储结构
1,邻接矩阵(Adjacency Matrix)
无向图是个对称矩阵。通过邻接矩阵,我们可以很方便的判断任意二点之间是否存在边;可以很容易的知道某个顶点的度数。
有向图则不是一个对称矩阵,通过邻接矩阵,我们可以知道任意二点之间是否存在弧,行列分别代表结点的出度和入度。
2,邻接表(Adjacency List)
把数组和链表相结合的存储方式成为邻接表.对于邻接表而言,我们将顶点利用一维数组来实现,在顶点表中,我们存储数据结点信息以及与此结点相关的第一个的邻接结点(边或者弧)的位置关系;在边表中,是单链表的结构,存储着每个结点的邻接节点在顶点表中下标的信息以及下一个该点的邻接结点的下标信息。有向图稍微复杂一下,此时的边表可以被称为是弧表,也就是按照弧的尾部来进行存储,邻接表更多的可以体现的是结点出度的关系,可以通过建立逆邻接表,我们得到结点入度的关系。
3,十字链表
对于有向图而言,我们采用邻接表的实现是有一定的缺陷的,邻接表在某种程度上可以体现出度的关系,但入度的关系则很难体现出来,逆邻接表而言,可以很方便的体现出入度的关系,但却很难体现出度的关系。所以将二者结合起来,创造出十字链表的结构。(一般而言,看文字是很难进行理解的,最好还是通过例子来解释繁杂的文字)
在十字链表的顶点表结构中,我们分为三个域,数据域来存储结点的数据信息,第一个指针域来存储指向该结点的边弧的信息,第二个指针域来存储该结点的出弧的位置信息。
在边表中,我们分为四个域,第一个数据域存储该边弧尾结点在顶点表中的下标,第二个数据域存储该边弧头结点在顶点表中的下标,第三个指针域存储同样的弧头的另一条弧的位置信息,第四个指针域存储同样的弧尾的另一条弧的位置信息。
十字链表的好处在于将邻接表和逆邻接表结合起来,可以很方便的得到结点的入度和出度,所以在图结构中,十字链表是一个很好的结构。
这里写图片描述
4,邻接多重表
十字链表是对于有向图的存储结构的优化,对于无向图的存储结构的优化,是仿造十字链表来进行构造的,形成了一种优化后的无向图的存储方式,称为邻接多重表。
在邻接多重表中,顶点表的结构没有发生变化。
我们重新定义了边表结构,变为了四个域,二个域分别为这条边所依附的二个结点在顶点表中的下标,二个指针域分别存储与这二个顶点相关的另一条边的位置信息。
对于邻接表和邻接多重表的区别在于,邻接多重表的边只用一个结点表示,而对于邻接表而言,则需要用二个结点来实现。对于删除某条边的操作,只需要将指向该结点的二条线的指向变为空即可。
在邻接多重表中,我们对于每一条边有一个指向,有一个指出,并且在指出关系中,结点的jvex值与指向节点的ivex的值相同。
这里写图片描述
5,边集数组
顶点表由一个以为数组组成,对于边表而言,仍然由一个二维数组来组成,在这边数组中,我们主要存储的是边的关系,关注的重点也在于边,所以,如果要求结点的度数肯定是低效的,但对于边的一些操作而言,却是效率比较高的。

图的遍历
从图中的某一个顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历。
图的深度优先遍历(Depth_First_Search),称为深度优先搜索,简称为DFS。
具体的实现类似于以一个点来作为突破口,然后将这个点的能够达到的遍历最多结点的那条路选出(深度),类似于树的前序遍历。
图的广度优先遍历(Breadth_First_Search),又称为广度优先搜索,简称BFS。
这里写图片描述
用的邻接矩阵和邻接表的实现相差无几,只是邻接矩阵需要遍历每一个值,也就是需要判断n的平方个数,但邻接表只需要判断n+e个数,如果边比较稀疏的时候,用邻接表的优势可以体现出来。
不管是用图的深度优先遍历还是用图的广度优先遍历,全图的遍历是没有优劣之分的,只是视不同的情况选择不同的算法。但一般我们遍历的目的在于寻找某一个目标的点,选择哪种遍历的方法则非常重要了,对于深度优先遍历而言,比较适合目标比较明确,以便找到目标为主要目的的情况,而对于广度优先比遍历而言,则需要不断扩大遍历范围时找到相对最优的情况。

最小生成树
网结构是图中的边具有权重的结构。对于连通网而言,我们关注的重点在于得到其最小生成树,也就是具有n-1条边的极小连通子图,生成树。连通网的最小代价生成树称为最小生成树。
1,普里姆算法(Prim)
寻找此点集合中的连接非点集合中的最小边,然后不断扩充点集合,最终点集合为全集,则结束。

void MiniSpanTree_Prim(MGraph G)
{
   int min, i, j, k;
   int adjvex[MAXVEX];
   int lowcost[MAXVEX];
   lowcost[0] = 0;
   adjvex[0] = 0;
   for(i = 1; i < G.numVertexes; i++)
   {
      lowcost[i] = G.arc[0][i];
      adjvex[i] = 0;
   }
   for(i = 1; i < G.numVertexes; i++)
   {
       min = INFINITY;
       j = 1; k = 0;
       while(j < G.numVertexes)
       {
           if(lowcost[j] != 0 && lowcost[j] < min)
           {
               min = lowcost[j];
               k = j;
           }
           j++;
       }
       printf("(%d,%d)",adjvex[k],k);
       lowcost[k] = 0;
       for(j = 1; j < G.numVertexes; j++)
       {
           if(lowcost[j]!=0 && G.arc[k][j] < lowcost[j])
           {
              lowcost[j] = G.arc[k][j];
              adjvex[j] = k;
           }
       } 
   }
}

在Prim算法中,lowcost存储的是当前所处理的结点集合中,依附着的最小的边。adjvex则用来存储与最小边相关联的顶点的编号。
【算法的精华】:依次扩充点集合,寻找点集合中的所有点的最小的连接边(与不在点集合中的其他点的连接边)

Kruskal算法来实现最小生成树
①寻找没有选中的边中最短的不构成环的边,然后将边选中。
②选中的边有n-1条边之后结束。

typedef struct
{
    int begin;
    int end;
    int weight;
}
void MiniSpanTree_Kruskal(MGraph G)
{
    int i, n, m;
    Edge edges[MAXEDGE];
    int parent[MAXVEX];
    for(i = 0; i < G.numVertexes; i++)
       parent[i] = 0;
    for(i = 0; i < G.numEdges; i++)
    {
       n = Find(parent, edges[i].begin);
       m = Find(parent, edges[i].end);
       if(n!=m)
       {
          parent[n] = m;
          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;
}

【算法精华】构建了新的边的结构,按照边权重的大小来对边进行处理,先处理权重小的边,然后再处理权重大的边。其中最关键的一点在于对于是否存在环的记录,也就是parent数组,parent数组记录了以i为起点(在数组中的下标值)以j为重点(在数组中的位置的具体值)的边的情况。在寻找是否存在环的情况,parent数组在记录在之前处理过得边中最终下标的结点能够达到的最新的结点。类似于顶真的一种特殊的结构。
对比二个算法,克鲁斯卡尔算法主要针对的边展开的,所以边比较少的时候效率比较高,对于稀疏图而言,效率比较高;普里姆算法对于稠密图,边数比较多的情况会比较好。

最短路算法
1,迪杰斯特拉(Dijkstra)算法
基于已知的最短路径的基础上,然后求更远顶点的最短路径,最终得到需要的结果。

#define MAXVEX 9
#define INFINITY 65535
typedef int Patharc[MAXVEX];
typedef int ShortPathTable[MAXVEX];
void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D)
{
    int v,w,k,min;
    int final[MAXVEX];
    for(v = 0; v < G.numVertexes; v++)
    {
        final[v] = 0;
        (*D)[v] = G.arc[v0][v];
        (*P)[v] = 0;
    }
    (*D)[v0] = 0;
    final[v0] = 1;
    for(v = 1; v < G.numVertexes; v++)
    {
         min = INFINITY;
         for(w = 0; w < G.numVertexes; v++)
         {
             if(!final[w] && (*D)[w] < min)
             {
                k = w;
                min = (*D)[w];
             }
         }
         final[k] = 1;
         for(w = 0; w < G.numVertexes; w++)
         {
             if(!final[w] && (min + G.arc[k][w] < (*D)[w]))
             {
                (*D)[w] = min + G.arc[k][w];
                (*P)[w] = k;
             }
         }
    }
}

【算法精华】在迪杰特斯拉算法中,利用final数组来记录是否已经处理过的结点,利用ShortPathTable来作为目前处理的结点中到vo的最短路径,利用Patharc来作为到此结点的前一跳结点的情况。
在迪杰特斯拉算法中,比较方便的是求某一结点到其他结点的最短的路径,如果要求全局的情况,则需要对每一个结点都进行算法操作。

2,弗洛伊德(Floyd)算法

typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];
void ShortestPath_Floyd(MGraph G, Pathmatirx *P, ShortPathTable *D)
{
    int v, w, k;
    for(v = 0; v < G.numVertexes; ++v)
    {
        for(w = 0; w < G.numVertexes; ++w)
        {
            (*D)[v][w] = G.matirx[v][w];
            (*P)[v][w] = w;
        }
    }
    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])
               {
                   (*D)[v][w] = (*D)[v][k] + (*D)[k][w];
                   (*P)[v][w] = (*P)[v][k];
               }
            }
        }
    }
}

【算法精华】在二重循环初始化的基础上,然后加上一个三重循环权值修正,就完成了所有顶点到所有顶点的最短路径计算。

拓扑排序(判断有向图中是否存在环)
AOV网:在表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,在AOV网中,一定不存在回路,弧代表着活动之间的相互制约的关系。
拓扑序列:在一个有向图中,我们的顶点之间存在一条路径,则顶点序列为一个拓扑序列,起点一定在终点之前。
拓扑排序:其实就是对有向图构造拓扑序列的过程。在构造过程中,如果网中全部顶点都输出,则说明是不存在回路的AOV网,否则说明不是AOV网。
拓扑排序算法基本思路:
从AOV网中,选择一个入度为0的顶点,然后删去此顶点,然后顺便删除此点为尾的弧,重复这个步骤,直到输出全部顶点或者没有入度为0的顶点为止。

Status TopologicalSort(GraphAdjList GL)
{
   EdgeNode *e;
   int i, k, gettop;
   int top = 0;
   int count = 0;
   int *stack;
   stack = ;
   stack = (int *)malloc(GL->numVertexes * sizeof(int));
   for(i = 0; i < GL->numVertexes; i++)
   {
       if(GL->adjList[i].in == 0)
           stack[++top] = i;
   }
   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))
             stack[++top] = k;
      }
   }
   if(count < GL->numVertexes)
        return ERROR;
   else
        return OK;
}

【算法精华】利用栈来存储入度为0的那些顶点,对于出栈一次,记录一个顶点,然后删去这个顶点所连接的弧,然后将对应的连接点的入度减一度,如果存在新的入度为0的点,重新压入栈,处理完栈的结点之后,判断出栈顶点的个数,若出栈顶点的个数为顶点数,说明有向图无环,如果出栈顶点的个数小于顶点数,则说明有环的存在。

关键路径
需要在分析图的基础上,然后得到流程中的最重要的部分,也就是最短时间。
AOE网:表示工程的带权有向图中,我们用顶点表示事件,用边来表示活动,边的权值来表示活动持续时间,这种有向图称之为AOE网,也就是用边来表示活动,关注的重点在于边。在AOE网中,没有入边的顶点叫做始点或源点,没有出边的顶点叫做终点或者汇点。
AOV(Activity On Vertex Network)与AOE(Activity On Edge Network),二者都是面对工程来建模的,但二者还是有很大的不同,前者关注的是结点,边用来描述结点之间的制约关系,而后者则关注边,关注边的权值的活动的持续时间。AOV网主要用来描述工程的流程是否合理,而AOE网则用来关注流程中最主要的部分,也就是应当加快哪些活动的问题。
关键路径:在AOE网中,源点到汇点具有的最大长度的路径称为关键路径,在关键路径上的活动叫做关键活动。
关键路径算法:
算法的思路在于找到事件的最早开始时间和最晚开始时间,并且比较他们,如果二者相等,则为关键路径,否则,不是关键路径。
算法实现:

int *etv, *ltv;
int *stack2;
int top2;
Status ToplogicalSort(GraphAdjList GL)
{
    EdgeNode e;
    int i, k, gettop;
    int top = 0;
    int count = 0;
    int *stack;
    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;
    etv = (int *)malloc(GL->numVertexes * sizeof(int));
    for(i = 0; i < GL->numVertexes; i++)
         etv[i] = 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;
}

在上述的代码中,我们得到了etv数组,表明每一个结点相对于源点的最长长度,也就是最早发生的时间。上述的代码的目的是:得到拓扑排序以及事件最早发生的时间的情况。
下面的是关键路径的算法代码

void CriticalPath(GraphAdjList GL)
{
   EdgeNode *e;
   int i, gettop; k, j;
   int ete, lte;
   TopologicalSort(GL);
   ltv = (int *)malloc(GL->numVertexes*sizeof(int));
   for(i = 0; i < GL->numVertexes; i++)
       ltv[i] = etv[GL->numVertexes-1];
   while(top2!=0)
   {
      gettop = stack2[top2--];
      for(e = GL->adjList[gettop].firstedge; e; e = e->next)
      {
         k = e->adjvex;
         if(ltv[k]-e->wight < ltv[gettop])
             ltv[gettop] = ltv[k] - e->weight;
      }
   }
   for(j = 0; j < GL->numVertexes; 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 , ", GL->adjList[j].data, GL->adjList[k].data, e->weight);
      }
   }
}

【算法精华】利用二个数组来记录,etv数组来记录每个事件的最早发生的时间,是以源点来作为时间结点的标识的,ltv数组来记录每个事件的最晚发生的时间,是以汇点来作为时间结点来进行标识的。
对于ete是弧最早的发生时间,也就是弧尾的发生时间,对于lte是弧最晚的发生时间,也就是弧头的发生时间,这二个量表明了事件是否有空闲的事件,如果二者相等,说明没有任何空闲,也就是是关键活动,否则的话,不是关键活动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值