数据结构应用实例(五)——关键路径

Content:

一、问题描述

设计实现 AOE 网的关键活动与关键路径问题;

二、算法思想

  1. 获取拓扑序列;
  2. 计算节点的最早开始时间 v e [ i ] ve[i] ve[i];
  3. 计算节点的最晚开始时间 v l [ j ] vl[j] vl[j];
  4. 查找关键路径

三、代码实现

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxx 999999
#pragma warning(disable:4996)

typedef struct arc//弧
{	
	int index;//指向节点编号
	int weight;//边的权值
	struct arc *next;
}AR;

typedef struct MyGraph
{
	int type;//0表示无向图,1表示有向图
	int arcnum,vexnum;//边的个数、顶点个数
	char **vexname;//存放顶点名称的二维数组	
	AR *N;//表头数组
	int **A;//邻接矩阵
}GH;

int findvex(char *s,GH *G);//找到图G中名称为s的节点编号,并将其返回
void createGraph(GH *G);//创建图G
void showGraph(GH *G);//以邻接表的形式显示图G

int Topological_sort(GH *G,int *q);//对G进行拓扑排序,q用于存放拓扑序列;如果G中有回路,返回1,否则返回0
void CriticalPath(GH *G);//寻找G中的关键路径					
void findCP(int *t,int top,int end,int *visit,GH *G,int *ve,int *vl,int *count);//递归函数,用于在图G中寻找关键路径;
//t为栈,存储关键路径上节点编号;end表示汇点编号;visit表示访问标记数组;ve,vl表示节点的最早开始时间和最晚开始时间;count表示关键路径条数;

int main(void)
{
	GH *G;
	G=(GH *)malloc(sizeof(GH));
	createGraph(G);
	printf("图的邻接表形式:\n");
	showGraph(G);
	CriticalPath(G);

	free(G);
	return 0;
}


int findvex(char *s,GH *G)//找到图G中名称为s的节点编号,并将其返回
{
	int i;
	for(i=0;i<G->vexnum;i++)
	{
		if(strcmp(s,G->vexname[i])==0)
			return i;
	}
	printf("读取文件错误.\n");
	exit(-1);
}

void createGraph(GH *G)//创建图G
{
	int i,j,n,edge;
	char filename[]="graph.txt";//存放图的数据文件
	char str[10],str1[10];
	FILE *fp;
	AR *p;

	fp=fopen(filename,"r");
	if(!fp)
	{
		printf("打开文件失败!\n");
		exit(-1);
	}

	fscanf(fp,"%d",&G->type);//读取图的类型
	G->arcnum=0;
	fscanf(fp,"%d",&n);//读取结点数量
	G->vexnum=n;

	//为动态数组分配空间
	G->vexname=(char **)malloc(n*sizeof(char *));
	G->N=(AR *)malloc(n*sizeof(AR));
	G->A=(int **)malloc(n*sizeof(int *));

	//对头结点数组和邻接矩阵初始化
	for (i = 0; i < n; i++)
	{
		G->N[i].next = NULL;
		G->A[i] = (int *)malloc(n*sizeof(int));
		for (j = 0; j < n; j++)
			G->A[i][j]=maxx;
	}

	//读取顶点名称
	for(i=0;i<n;i++)
	{
		fscanf(fp,"%s",str);
		G->vexname[i]=(char *)malloc(strlen(str)*sizeof(char));
		strcpy(G->vexname[i],str);
	}

	//读取边
	while(!feof(fp))
	{
		fscanf(fp,"%s",str);
		fscanf(fp,"%s",str1);
		fscanf(fp,"%d",&edge);

		i=findvex(str,G);
		j=findvex(str1,G);
		//邻接表
		p=(AR *)malloc(sizeof(AR));
		p->index=j;
		p->weight=edge;
		p->next=G->N[i].next;
		G->N[i].next=p;
		//邻接矩阵
		G->A[i][j]=edge;
		G->arcnum++;//边的个数增加
		if(G->type==0)//如果是无向图
		{
			//邻接表
			p=(AR *)malloc(sizeof(AR));
			p->index=i;
			p->weight=edge;
			p->next=G->N[j].next;
			G->N[j].next=p;
			//邻接矩阵
			G->A[j][i]=edge;
		}
	}
	fclose(fp);
}

void showGraph(GH *G)//以邻接表的形式显示图G
{
	int i;
	AR *p;//用于遍历
	for (i = 0; i < G->vexnum; i++)
	{
		printf("%s",G->vexname[i]);
		p=G->N[i].next;
		while (p)
		{
			if (G->type == 1)
				printf("-->");
			else//无向图没有箭头
				printf("--");
			printf("%s(%d)",G->vexname[p->index],p->weight);
			p=p->next;
		}
		printf("\n");
	}
	printf("\n");
}


int Topological_sort(GH *G,int *q)//对G进行拓扑排序,q用于存放拓扑序列;如果G中有回路,返回1,否则返回0
{
	int i,n;
	int *d;
	int *t,top;
	int index,count;
	AR *p;

	n=G->vexnum;
	d=(int *)malloc(n*sizeof(int));//统计各个节点的入度
	t=(int *)malloc(n*sizeof(int));//建立栈,用于存储节点编号
	top=-1;
	
	//初始化,将各个节点的入度设为0
	for (i = 0; i < n; i++)
		d[i] = 0;

	//遍历表头数组,统计各个节点的入度
	for (i = 0; i < n; i++)
	{
		p=G->N[i].next;
		while (p)
		{
			d[p->index]++;
			p=p->next;
		}
	}

	//挑选入度为0的点进栈
	for (i = 0; i < n; i++)
	{
		if (d[i] == 0)
		{
			top++;
			t[top]=i;
		}
	}

	count=0;//统计弹出的节点个数
	while (top >= 0)//若栈非空,弹栈
	{
		index=t[top];//栈顶元素编号
		//栈顶元素不输出
		//printf("%s ",G->vexname[index]);
		top--;
		//记录弹出序列,即拓扑序列
		q[count]=index;
		count++;
		//遍历弹出节点的邻接表,其相邻点的入度减一
		p=G->N[index].next;
		while (p)
		{
			d[p->index]--;
			if (d[p->index] == 0)//若入度变为0,进栈
			{
				top++;
				t[top]=p->index;
			}
			p=p->next;
		}
	}
	//printf("\n");//输出

	free(t);
	free(d);
	if (count == n)//拓扑序列中含有G中全部点,表示没有回路
		return 0;
	else
		return 1;
}

void CriticalPath(GH *G)//寻找G中的关键路径
{
	int i,n;
	int x,y;//计数器
	int length;//关键路径长度
	int num,count;//关键节点个数和关键路径条数
	AR *p;
	int *q,*ve,*vl;
	int *t;//用于在递归寻找关键路径时存储路径
	int *visit;//访问标记数组

	n=G->vexnum;
	q=(int *)malloc(n*sizeof(int));//拓扑序列,存储节点编号
	ve=(int *)malloc(n*sizeof(int));//最早开始时间
	vl=(int *)malloc(n*sizeof(int));//最晚开始时间

	if (Topological_sort(G,q))//获取拓扑序列
	{
		printf("该有向图中存在回路,故不存在关键路径.\n");
		return;
	}

    //1.计算最早开始时间
	//初始化,全设为0
	for (i = 0; i < n; i++)
		ve[i] = 0;
	for (i = 0; i < n; i++)//利用正向拓扑序列计算最早开始时间
	{
		x = q[i];
		p = G->N[x].next;//利用邻接表寻找x的直接后继
		while (p)//更新x的直接后继的最早开始时间
		{
			if (ve[p->index] < ve[x] + (p->weight))
				ve[p->index] = ve[x] + (p->weight);
			p = p->next;
		}
	}

	//2.计算最晚开始时间
	length = ve[q[n - 1]];//关键路径长度
    //初始化
	for (i = 0; i < n; i++)
		vl[i] = length;
	for (i = n - 1; i >= 0; i--)//利用逆向拓扑序列计算最晚开始时间
	{
		y = q[i];
		//利用邻接矩阵寻找y的直接前驱
		for (x = 0; x < n; x++)//更新y的直接前驱的最晚开始时间
		{
			if (G->A[x][y] < maxx)//找到之后
			{
				if (vl[x] > vl[y] - G->A[x][y])
					vl[x] = vl[y] - G->A[x][y];
			}
		}
	}

	//3.输出最早开始时间和最晚开始时间
	num=0;
	visit=(int *)malloc(n*sizeof(int));
	printf("节点名称 最早开始时间 最晚开始时间\n");
	for (i = 0; i < n; i++)
	{
		x = q[i];//节点编号
		printf("%4s %10d %12d\n",G->vexname[x],ve[x],vl[x]);
		if (ve[x] == vl[x])
		{
			visit[x] = 0;//关键节点设置为未访问
			num++;
		}
		else
			visit[x]=1;//事先标记非关键节点,避免后续访问
	}
		
	//4.利用递归在关键节点中探寻关键路径
    //初始化
	t = (int *)malloc(num*sizeof(int));//存储关键路径中节点编号
	visit[q[0]]=1;
	t[0] = q[0];
	count = 0;//关键路径条数
	//调用递归函数
	findCP(t,0,q[n-1],visit,G,ve,vl,&count);
	printf("\n");

	free(ve);
	free(vl);
	free(q);	
	free(t);
	free(visit);
}

//t为栈,存储关键路径上节点编号;end表示汇点编号;visit表示访问标记数组;ve,vl表示节点的最早开始时间和最晚开始时间;count表示关键路径条数;
void findCP(int *t,int top,int end,int *visit,GH *G,int *ve,int *vl,int *count)//递归函数,用于在图G中寻找关键路径;
{
	int i;
	int cur;
	AR *p;

	cur=t[top];
	if (cur == end)//基准情况:到达汇点,输出路径
	{
		(*count)++;	
		printf("\n第%d条关键路径:\n",(*count));
		for (i = 0; i < top; i++)
			printf("%s-->",G->vexname[t[i]]);		
		printf("%s\n",G->vexname[cur]);
	}
	else//非基准情况
	{
		p=G->N[cur].next;
		while (p)//遍历当前节点的直接后继
		{
			if (visit[p->index]==0 && ve[cur] + (p->weight) == vl[p->index])//关键工序(边)的判别条件,非关键节点的visit[i]==1
			{
				visit[p->index]=1;
				t[top+1]=p->index;//入栈
				findCP(t,top+1,end,visit,G,ve,vl,count);//调用递归函数
				visit[p->index]=0;//撤销标记
			}
			p=p->next;
		}
	}
}

四、小结

1、 为了利用拓扑序列计算最早和最迟开始时间,在进行拓扑排序时,对排序序列进行记录;

2、 对于关键路径不唯一的情况,采用 DFS 寻找关键路径,查找路径之前,标记非关键节点,避免后续访问,从而简化了节点添入路径的条件,提高算法的执行效率;

3、 t 用于存储关键路径中的节点编号,由于关键路径中的节点均是关键节点,所以在给 t 分配空间时,分配的空间单位数与关键节点个数相同即可;

4、 在实际计算最早开始时间时,做法并不与算法思想完全一致,具体做法为:先设初值, v e [ i ] ve[i] ve[i] 全部为0;然后遍历正向拓扑序列 q q q,对于编号为 q [ i ] q[i] q[i] 的节点,更新其直接后继 y y y 号节点的最早开始时间,即若 v e [ y ] < v e [ q [ i ] ] + A [ q [ i ] ] [ y ] ve[y]<ve[q[i]]+A[q[i]][y] ve[y]<ve[q[i]]+A[q[i]][y],则令 v e [ y ] = v e [ q [ i ] ] + A [ q [ i ] ] [ y ] ve[y]= ve[q[i]]+ A[q[i]][y] ve[y]=ve[q[i]]+A[q[i]][y];对于最迟开始时间,进行类似操作;

相关推荐
程序猿阿伟1 小时前
《C++移动语义:解锁复杂数据结构的高效之道》
数据结构·c++·html
594h22 小时前
PAT 甲级 1002题
数据结构·c++·算法
snowful world2 小时前
vs2022链表的创建和打印(c语言版)
c语言·数据结构·链表
shylyly_3 小时前
归并排序
数据结构·算法·归并排序·递归归并排序·非递归归并排序
itoshi rin4 小时前
简单题21 - 合并两个有序链表(Java)20240917
java·数据结构·链表
Antonio9155 小时前
【高级数据结构】树状数组
数据结构·c++·算法
大晴的上分之旅5 小时前
树和二叉树基本术语、性质
数据结构·算法·二叉树
Chase-Hart5 小时前
【每日一题】LeetCode 815.公交路线(广度优先搜索、数组、哈希表)
数据结构·算法·leetcode·散列表·宽度优先
Am心若依旧4095 小时前
[C++进阶[六]]list的相关接口模拟实现
开发语言·数据结构·c++·算法·list
人才程序员6 小时前
CSP-J 算法基础 广度优先搜索BFS
数据结构·c++·算法·深度优先·宽度优先·比赛·noi