图
一、图的定义
图:是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
- 线性表中将数据元素称为元素,在树中将数据元素称为结点,在图中数据元素,称为顶点
- 在图结构中,不允许没有顶点。在定义中,若V是顶点的集合,则强调了顶点集合V有穷非空
- 在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边表示,边集可以是空的
(1)各种图定义
无向边:若顶点vi到vj之间的边没有方向,则称这条边为无向边,用无序偶对(vi,vj)表示。
无向图:图中任意两个顶点之间的边都是无向边
G1=(V1,{E1}),其中顶点集合V1={A,B,C,D},边集合E1={(A,B),(B,C),(C,D),(D,A),(A,C)}
有向边:若从顶点vi到vj的边有方向,则称这条边为有向边,也称为弧。用有序偶<vi,vj>表示,vi称为弧尾,vj称为弧头
有向图:图中任意两个顶点之间的边都是有向边
简单图:若不存在顶点到其自身的边,且同一条边不重复出现
无向完全图:在无向图中,如果任意两个顶点之间都存在边
含有n个顶点的无向完全图有n*(n-1)/2条边
有向完全图:在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧
含有n个顶点的有向完全图有n*(n-1)条边
稀疏图:有很少条边或弧的图称为稀疏图,反正为稠密图
权:与图的边或弧相关的数
网:带权的图
假设有两个图G=(V,{E})和G’=(V’{E’}),如果V’包含于V且E‘包含于E,则称G’为G的子图
(2)图的顶点与边间关系
- 对于无向图G=(V,{E}),如果边(V,V’)属于E,则称顶点V与V‘互为邻接点,即V与V‘相邻接。边(V,V’)依附于顶点V和V‘ ,或者说(V,V’)与顶点V和V‘相关联。顶点V的度是和V相关联的边的数目,记为TD(V)
- 对于有向图G=(V,{E}),如果弧<V,V’>属于E,则称顶点V邻接顶点V’,顶点V‘邻接顶点V。弧<V,V’>和顶点V,V’相关联。以顶点V为头的弧的数目称为V的入度,记为ID(V);以V为尾的弧的数目称为V的出度,记为OD(V);顶点V的度为TD(V)=ID(V)+OD(V)
- 无向图G=(V,{E})中从顶点V到顶点V‘的路径是一个顶点序列(V=Vi,0,Vi,1,…,Vi,m=V’)
- 如果G是有向图,则路径也是有向的
- 路径的长度是路径上的边或弧的数目
- 第一个顶点到最后一个顶点相同的路径称为回路或环。序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环
(3)连通图相关术语
连通图:在无向图中G中,如果从顶点V到顶点V’有路径,则称V和V‘是连通的。如果对于图中任意两个顶点Vi、Vj属于V,Vi和Vj都是连通的,则称G是连通图
连通分量:无向图中的极大连通子图
强连通图:在有向图G中,如果对于每一对Vi、Vj属于V,Vi不等于Vj,从Vi到Vj都存在路径,则称G为强连通图
强连通分量:在有向图中的极大强连通子图
连通图生成树:为一个小的连通子图,其含有图的全部n个顶点,但只有足以构成一棵树的n-1条边
- 如果一个图有n个顶点和小于n-1条边,则为非连通图,多于n-1条边必定构成一个环
- 如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一棵有向树
- 一个有向图生成的森林由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧
二、图的抽像数据类型
ADT 图
Data
顶点的有穷非空集合和边的集合
Operation
CreateGraph(*G,V,VR);//按照顶点集V和边弧集VR的定义构造图G
DestropGraph(*G);//图G存在则销毁
LocateVex(G,u);//若图G中存在顶点u,则返回图中位置
GetVex(G,V);//返回图G中顶点v的值
PutVex(G,v,value);//将图G中顶点v赋值value
FirstAdjVex(G,v,*w);//返回顶点v的一个邻接顶点,若顶点在图G中无邻接顶点则返回空
NextadjVex(G,v *w);//返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最后一个邻接顶点则返回空
InsertArc(*G,v,w);//在图G中增添弧<v,w>,若G是无向图,则还需要增添对称弧<w,v>
Deletearc(*G,v,w);//在图G中删除弧<v,w>,若G是无向图,还需要删除对称弧<w,v>
DFSTraverse(G);//对图G中进行深度优先遍历,在遍历过程中对每一个顶点调用
HFSTraverse(G);//对图G中进行广度优先遍历,在遍历过程中对每个顶点调用
endADT
三、图的存储结构
(1)邻接矩阵
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为
- arc[i] [j]=1,若(Vi,Vj)属于E或<Vi,Vj>属于E
- arc[i] [j]=0,反之
顶点数组: | V0 |V1|V2| V3|
边数组
无向图的边数组是一个对称矩阵,有向图的边数组不一定对称
设图G是一个网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
- arc[i] [j]=Wij,若(Vi,Vj)属于E或<Vi,Vj>属于E(Wij表示权值)
- arc[i] [j]=0,若i=j
- arc[i] [j]=无穷大,反之
邻接矩阵存储的结构代码
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("请输入顶点数和边数:\n");
scanf("%d,%d",&G->numVertexes,&G->numEdges);//输入顶点数和边数
for(i=0;i<G->numVertexes;i++)//读入顶点信息,建立顶点表
{
scanf("&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:\n");
scanf("%d,%d,%d",&i,*j,&w);
G->arc[i][j]=w;
G->arc[i][j]=G->arc[i][j];//因为为无向图,矩阵对称
}
}
(2)邻接表
邻接表:数组与链表相结合的存储方式
邻接表的处理方法
- 图中顶点用一个一维数组存储,顶点也可以用单链表存储。对于顶点数组中,每个数据元素还需要存储一个指向第一个邻接点的指针,以便于查找该顶点的边信息
- 图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不确定,所以用单链表存储,无向图称为顶点Vi的边表,有向图称为顶点Vi作为弧尾的出边表
顶点表: data | firstedge
- data是数据域,存储顶点信息
- firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点
边表结点: adjvex | next
- adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标
- next则存储指向边表中下一个结点的指针
对于有向图,可以建立一个有向图的逆邻接表,即对每个顶点Vi都建立一个链接为Vi为弧头的表
对于带权值的网图,可以在边表结点定义中在增加一个weight的数据域,存储权值信息
结点定义代码
typedef char VertexType;//顶点类型由用户定义
typedef int EdgeType;//边上的权值类型由用户定义
typedef struct EdgeNode//边表结点
{
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;//图中当前的顶点数和边数
}GraphAdList;
无向图的邻接表创建代码
//建立图的邻接表结构
void CreateALGraph(GraphAdjList *G)
{
int i.j,k;
EdgeNode *e;
printf("请输入顶点数和边数:\n");
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)上的顶点序号:\n");
scanf("%d.%d",&i,&j);
e=(EdgeNode*)malloc(sizeof(EdgeNode));//向内存中申请空间,生成边表结点
e->adjvex=j;//邻接序号为j
e->next=G->adjList[i].firstedge;//将e指针指向当前顶点指向的结点
G->adjList[i].firstedge=e;//将当前顶点的指针指向e
e=(EdgeNode*)malloc(sizeof(EdgeNode));//向内存中申请空间,生成边表结点
e->adjvex=i;//邻接序号为i
e->adjvex=i;//邻接序号为i
e->next=G->adjList[j].firstedge;//将e指针指向当前顶点指向的结点、
G->adjList[j].firstedge=e;//将当前顶点的指针指向e
}
}
(3)十字链表
十字链表:将邻接表与逆邻接表结合起来
顶点表结构: data|firstin|firstout|
- firstin表示入边表头指针,指向该顶点的入边表中第一个结点
- firstout表示出边表头指针,指向该顶点的出边表的第一个结点
边表结点结构:tailex|headvex|headlink|taillink|
- tailvex是指弧起点在顶点表的下标
- headvex是指弧终点在顶点表的下标
- headlink是指入边表指针域,指向终点相同的下一条边
- taillink是指边表指针域,指向起点相同的下一条边
- 如果是网,还需再增加一个weight域来存储权值
(4)邻接多重表
重新定义十字链表中的边表结点:ivex|ilink|jvex|jlink|
- ivex与jvex是与某条边依附的两个顶点在顶点表的下标
- ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边
(5)边集数组
边集数组:由两个一维数组构成。一个存储顶点信息,另一个存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标和权组成。
边数组结构:begin|end|weight|
四、图的遍历
图的遍历:从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次
(1)深度优先遍历(深度优先搜索)DFS
从图中某个顶点V出发,访问此顶点,然后从V的未被访问的邻接点出发深度优先遍历图,直到图中所有和V有路径相通的顶点都被访问到
采用邻接矩阵的方式
typedef int Boolean//Boolean是布尔类型,其值为TRUE或FALSE
BOolean visited[MAX];//访问标志的数组
//邻接矩阵的深度优先递归算法
void DFS(MGraph G,int i)
{
int j;
visited[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 DDFSTraverse(MGraph G)
{
int i;
for(i=0;i<G.numVertexes;i++)
{
visited[i]=FALSE;//初始所有顶点状态都是未访问过状态
}
for(i=0;i<G.numVertexes;i++)
{
if(!visited[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)广度优先遍历(广度优先搜索)BFS
邻接矩阵的广度优先遍历算法
void BFSTraverse(MGraph G)
{
int i.j;
Queue Q;
for(i0;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.numVertexses;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->adList[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;//指针指向下一个邻接点
}
}
}
}
}
五、最小生成树
最小生成树:构造连通网的最小代价生成树
(1)普里姆算法
- INFINITY为权值极大值,记为65535
- MAXVEX为顶点个数最大值
Prim算法生成最小生成树
void MiniSpanTree_Prim(MGraph G)
{
int min,i,j,k;
int adjvex[MAXVEX];//保存相关顶点下标
int lowcost[MAXVEX];//保存相关顶点间边的权值
lowcost[0]=0;//初始化第一个权值为-,即v0加入生成树,lowcost的值为0,在这里就是此下标的顶点已经加入生成树
adjvex[0]=0;//初始化第一个顶点下标为0
for(i=1;i<G.numVertexes;i++)//循环除下标为0外的全部顶点
{
loecost[i]=G.arc[0][i];//将v0顶点与之有边的权值存入数组
adjvex[i]=0;//初始化都为V0的下标
}
for(i=1;i<G.numVertexes;i++)
{
min=INFINITY;//初始化最小权值为无穷大。通常设置为不可能的大数字如32767、65335等
j=1;k=0;
while(j<G.numVertexes)//循环全部顶点
{
if(lowcost[j]!=0&&lowcost[j]<min)//如果权值不为0且权值小于min
{
min=lowcost[j];//则让当前权值成为最小值
k=j;//将当前最小值的小标存入k
}
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)克鲁斯卡尔算法
edge边集数组结构的定义代码
typedef struct
{
int begin;
int end;
int weight;
}Edge;
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;j<G.numEdges;i++)//循环每一条边
{
n=Find(parent,edges[i].begin);
m=Find(parent,edges[i].end);
if(n!=m)//假设n与m不等,说明此边没有与现有生成树形成环路
{
parent[m]=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;
}
六、最短路径
最短路径:对于网图,是指两顶点之间经过的边上权值之和最小的路径,并称路径上的第一个顶点为源点,最后一个顶点为终点。
(1)迪杰斯特拉算法
#define MAXVEX 9
#define INDINITY 65535
typedef int Patharc[MAXVEX];//用于存储最短路径下标的数值
typedef int ShortPathTable[MAXVEX];//用于存储到各点最短路径的权值和
//Dijkatra算法,求有向网G的V0顶点到其余顶点V的最短路径P[v]及带权长度D[v]
//P[v]的值为前驱顶点下标,D[v]表示V0到V的最短路径长度和
void shortestPath_Dijkatra(MGraph G,int v0,Patharc *p,shortPathTable *D)
{
int v,w,k,min;
int final[MAXVEX];//final[w]=1表示求得顶点V0到Vw的最短路径
for(V=0;V<G.numVertexes;v++)//舒适化数据
{
finial[v]=0;//全部顶点初始化为未知最短路径状态
(*D)[v]=G.arc[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.arc[k][w]<(*D)[w]))
{
//说明找到了更短的路径,修改D[w]和P[w]
(*D)[w]=min+G.arc[k][w;]//修改当前路径长度
(*p)[w]=k;
}
}
}
}
弗洛伊德算法
typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];
//Floyd算法,求网图G中各顶点V到其余顶点w最短路径P[v][w]及带权长度D[v][w]
void ShorttestPath_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][k]+(*D)[k][w];
(*p)[v][w]=(*p)[v][k];//路径设置经过下标为k的顶点
}
}
}
}
}
七、拓扑排序
AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系
拓扑序列:设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列V1,V2,…,Vn,满足若从顶点Vi到vj有一条路径,则在顶点序列中顶点Vi必在顶点Vj之前。
拓扑排序:对一个有向图构造拓扑序列的过程
//拓扑排序,若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*siezof(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;
}
}
八、关键路径
在路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大的长度的路径叫关键路径,在关键路径上的活动叫关键活动
(1)关键路径算法原理
- 事件的最早发生时间etv:顶点Vk最早发生时间
- 事件的最晚发生时间ltv:顶点Vk的最晚发生时间,即每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期
- 活动的最早开工时间ete:即弧ak的最早发生时间
- 活动的最晚开关时间lte:即弧ak的最晚发生时间,即不推迟工期的最晚开工时间
(2)关键路径算法
首先声明全局变量
int *etv,*ltv;//事件最早发生时间和最迟发生时间数组
int *stack2;//用于存储拓扑序列的栈
int top2;
改进的求拓扑序列算法
Status TopologicalSort(GraphAdjList GL)
{
EdgeNOde *e;
int i,k,gettop;
int top=0;//用于栈指针下标
int count=0;//用于统计输出顶点的个数
int *stack;//建栈存储入度为0的顶点
stack=(int*)malloc(GL->numVertexes*siezof(int));
for(i=0;i<GL->numVertexes;i++)
{
if(GL->adjList[i].in=0)
{
stack[++top]=i;//将入度为0的顶点入栈
}
}
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--];//出栈
printf("%d->",GL->adjList[gettop].data);//打印此顶点
count++;//统计输出顶点数
stack2[++top2]=gettop;//将弹出的顶点序号压入拓扑序列的栈
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((etv[gettop]+e->weight)>etv[k])//求各顶点事件最早发生时间
{
etv[k]=etv[gettop]+e->weight;
}
}
}
if(count<GL->numVertexes)//如果count小于顶点数,则说明存在环
{
return ERROR;
}
else
{
return OK;
}
}
关键路径的算法代码
//求关键路径,GL为有向网,输出GL的各项关键活动
void CriticalPath(GraphAdjList GL)
{
EdgeNode *e;
int i,gettop,k,j;
int ete,lte;//声明活动最早发生时间和最迟发生时间变量
TopologicalSort(GL);//求拓扑序列,计算数组etv和stack2的值
ltv=(int*)malloc(GL->numVertexes*sizeof(int));//事件最晚发生时间
for(i=0;i<GL->numVertexes;i++)
{
ltv[i]=etv[GL->numVertexes-1];//初始化ltv
}
while(top2!=0)//计算ltv
{
gettop=stack2[top2--];//将拓扑序列出栈,后进先出
for(e=GL->adjList[gettop].firstedge;e;e->next)
{
//求各顶点事件的最迟发生时间ltv值
k=e->adjvex;
if(ltv[k]-e->weight<ltv[gettop]);//求各顶点事件最晚发生时间ltv
ltv[gettop]=ltv[k]-e->weight;
}
}
for(j=0;j<GL->numVertexes;j++)//求ete,lte和关键活动
{
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);
}
}
}
}