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;
}