PTA 关键活动 思路分析及代码解析

一、前导

1. 需要掌握的知识

  1. 本题是 How Long Does It Take的加强版,不仅要判定图中是否存在回路、求解最早完成时间,还要求解最晚完成时间并得到关键路径。关键路径就是:由最早完成时间和最晚完成时间相等的结点所构成的路径
  2. 关键路径、最早完成时间、最晚完成时间知识点可以参考 关键路径讲解

2. 题目信息

  1. 题目来源:PTA / 拼题A
  2. 题目地址:关键活动

二、解题思路分析

1. 题意理解

  1. 可以在 How Long Does It Take的思路基础上求解
  2. 本题可以通过邻接矩阵存储图,这样可以降低编码量

1. 1 输入数据

7 8  //图的顶点数和边数,顶点数最大值100,顶点从1开始编号
1 2 4  //边的两个顶点及其权重, 有向图
1 3 3
2 4 5
3 4 3
4 5 1
4 6 6
5 7 5
6 7 2

1.2 输出数据

  1. 图如果存在回路,打印 ‘0’ ,否则打印最早完成时间并输出关键路径

2. 思路分析

  1. 通过拓扑排序判断图中是否存在回路 并 计算出各结点的最早完成时间和最晚完成时间
  2. 按题目要求 关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反 打印关键路径

三、具体实现

1. 弯路和bug

  1. 本题使用邻接矩阵存储图会更方便

2. 代码框架(重点)

2.1 采用的数据结构

  1. 使用邻接矩阵存储图
#define max 101
int Graph[max][max]; //方便起见,二维数组、结点数和边数都设定为全局变量
int Nodes,Edges;

int Indegree[max]; //结点的入度值
int Outdegree[max]; //结点的出度值
int EarlistFinishTime; 
int Earlist[max]; //统计最早完成时间 
int Latest[max]; //统计最晚完成时间

2.2 程序主体框架

               程序伪码描述
int main()
{
	0.初始化数据	
	1.根据输入数据构建图
	2.进行拓扑排序
	3.求解关键路径并按要求打印
}

2.3 各分支函数

  1. BuildGraph() : 构建图,并获取各结点的出度值和入度值
#define impossible -1 //边的权值可能为0,因此无边对应-1
void BuildGraph() //创建图 并 初始化各个顶点的出度和入度 
{
	int value;
	vertex a,b;
	
	cin>>Nodes>>Edges; // Nodes Edges是全局变量
	
	for(int i=1;i<=Nodes;i++)
	{
		for(int j=1;j<=Nodes;j++)
		{
			Graph[i][j]= impossible;
		} 
	}
	
	for(int k=1;k<=Edges;k++)
	{
		cin>>a>>b>>value;
		Graph[a][b]=value;
		
		Indegree[b]++;
		Outdegree[a]++;
	}
	
	return;
}
  1. TopSort( ):核心函数,拓扑排序的基本流程如下(使用队列)
    (1)遍历图得到每个顶点的入度值,将入度值为0的顶点加入对列
    (2)执行出列,出列顶点的邻接点入度值 -1 ,若入度值变为0,相关邻接点入列
    (3)重复执行(2),直到队列为空。当顶点出列时,通过count变量计数,以便判定图中是否存在回路
bool TopSort()
{
	int count=0,CurrentNode;
	vertex V,W;
	queue<vertex> q;
	
	for(V=1;V<=Nodes;V++) 
	{
		if(Indegree[V]==0)
		{
			q.push(V);
			Earlist[V]=0; //初始入度为0的顶点,其最早完成时间必定为0 
		}
	}
	
	/*开始进行Top Sort*/
	while(!q.empty()) 
	{
		CurrentNode=q.front();
		q.pop();
		count++; //判定图中是否存在回环,若count数等于图顶点数,一定不存在回环 
		
		for(V=1;V<=Nodes;V++)
		{
			if(Graph[CurrentNode][V]!=impossible)
			{
				if(Earlist[V]<Earlist[CurrentNode]+Graph[CurrentNode][V])
					Earlist[V]=Earlist[CurrentNode]+Graph[CurrentNode][V];
					
				if(EarlistFinishTime<Earlist[V])
					EarlistFinishTime=Earlist[V];
				
				if(--Indegree[V]==0)
					q.push(V);
			}
		}		
	}
	
	if(count!=Nodes)
		return false;
	else 
		return true;
	
}
  1. KeyActive( ) :AC的核心函数。通过顶点的出度值再反向进行一次拓扑排序,得到最晚完成时间。 最晚完成时间、最早完成时间相等的结点构成的路径就是关键路径。
  2. 输出规则 任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反 是一个难点。根据输出规则,可以分析出如下隐含信息:任务开始的交接点编号小者优先 意味着 打印时顶点编号从小到大排列;起点编号相同时,与输入时任务的顺序相反 输入时是按从小到大的顺序录入边的,相反意味着从大到小输出。最后,满足Latest[W]-Earlist[V]==Graph[V][W] 的 V W 就是关键路径上的边
for(V=1;V<=Nodes;V++) //从小到大:任务开始的交接点编号小者优先 
		for(W=Nodes;W>=1;W--) //从大到小:起点编号相同时,与输入时任务的顺序相反
			if(Graph[V][W]!=impossible && Latest[W]-Earlist[V]==Graph[V][W]) // Latest[W]-Earlist[V]==Graph[V][W] 满足条件的V W 就是关键路径上的边 
				cout<<V<<"->"<<W<<endl;

void KeyActive()
{	
	vertex V,W;
	queue<int> q;
	
	for(V=1;V<=Nodes;V++)
	{
		if(Outdegree[V]==0)
		{
			q.push(V);
			Latest[V]=EarlistFinishTime;
		}	
	}
	
	while(!q.empty())
	{
		W=q.front();
		q.pop();
		
		for(V=1;V<=Nodes;V++)
		{
			if(Graph[V][W]!=impossible)
			{
				if(Latest[V]>Latest[W]-Graph[V][W])
					Latest[V]=Latest[W]-Graph[V][W];
				if(--Outdegree[V]==0)
					q.push(V);
			}
		}
	}
	
	//关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反
	
	for(V=1;V<=Nodes;V++) //从小到大:任务开始的交接点编号小者优先 
	{
		for(W=Nodes;W>=1;W--) //从大到小:起点编号相同时,与输入时任务的顺序相反
		{
			if(Graph[V][W]!=impossible && Latest[W]-Earlist[V]==Graph[V][W]) // Latest[W]-Earlist[V]==Graph[V][W] 满足条件的V W 就是关键路径上的边 
				cout<<V<<"->"<<W<<endl;
		}
	}
	
	return;
}

3. 完整AC编码

  1. 本文如果对你有帮助,请点赞鼓励 ,谢谢 😊
  2. 如有建议或意见,欢迎留言
#include <queue>
#include <cstdlib>
#include <iostream>
using namespace std;

typedef int vertex;

#define max 101
#define maxValue 999
#define impossible -1

int Graph[max][max];
int Nodes,Edges;

int Indegree[max];
int Outdegree[max];

int EarlistFinishTime; 
int Earlist[max]; //统计最早完成时间 
int Latest[max]; //统计最晚完成时间


void BuildGraph();
bool TopSort();
void init();
void KeyActive(); 

int main()
{	
	init(); 
	
	BuildGraph();
	
	if(!TopSort())
	{
		cout<<"0"<<endl;
		return 0;
	}
	else	
		cout<<EarlistFinishTime<<endl;
	
	KeyActive();
	
	return 0;
} 

void KeyActive()
{	
	vertex V,W;
	queue<int> q;
	
	for(V=1;V<=Nodes;V++)
	{
		if(Outdegree[V]==0)
		{
			q.push(V);
			Latest[V]=EarlistFinishTime;
		}	
	}
	
	while(!q.empty())
	{
		W=q.front();
		q.pop();
		
		for(V=1;V<=Nodes;V++)
		{
			if(Graph[V][W]!=impossible)
			{
				if(Latest[V]>Latest[W]-Graph[V][W])
					Latest[V]=Latest[W]-Graph[V][W];
				if(--Outdegree[V]==0)
					q.push(V);
			}
		}
	}
	
	//关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反
	
	for(V=1;V<=Nodes;V++) //从小到大:任务开始的交接点编号小者优先 
	{
		for(W=Nodes;W>=1;W--) //从大到小:起点编号相同时,与输入时任务的顺序相反
		{
			if(Graph[V][W]!=impossible && Latest[W]-Earlist[V]==Graph[V][W]) //隐藏条件:Latest[W]-Earlist[V]==Graph[V][W] 
				cout<<V<<"->"<<W<<endl;
		}
	}
	
	return;
}

bool TopSort()
{
	int count=0,CurrentNode;
	vertex V,W;
	queue<vertex> q;
	
	for(V=1;V<=Nodes;V++) 
	{
		if(Indegree[V]==0)
		{
			q.push(V);
			Earlist[V]=0; //初始入度为0的顶点,其最早完成时间必定为0 
		}
	}
	
	/*开始进行Top Sort*/
	while(!q.empty()) 
	{
		CurrentNode=q.front();
		q.pop();
		count++; //判定图中是否存在回环,若count数等于图顶点数,一定不存在回环 
		
		for(V=1;V<=Nodes;V++)
		{
			if(Graph[CurrentNode][V]!=impossible)
			{
				if(Earlist[V]<Earlist[CurrentNode]+Graph[CurrentNode][V])
					Earlist[V]=Earlist[CurrentNode]+Graph[CurrentNode][V];
					
				if(EarlistFinishTime<Earlist[V])
					EarlistFinishTime=Earlist[V];
				
				if(--Indegree[V]==0)
				{
					q.push(V);
				}
			}
		}		
	}
	
	if(count!=Nodes)
		return false;
	else 
		return true;
	
}

void BuildGraph() //创建图 并 初始化各个顶点的出度和入度 
{
	int value;
	vertex a,b;
	
	cin>>Nodes>>Edges;
	
	for(int i=1;i<=Nodes;i++)
	{
		for(int j=1;j<=Nodes;j++)
		{
			Graph[i][j]= impossible;
		} 
	}
	
	for(int k=1;k<=Edges;k++)
	{
		cin>>a>>b>>value;
		Graph[a][b]=value;
		
		Indegree[b]++;
		Outdegree[a]++;
	}
	
	return;
}

void init()
{	
	EarlistFinishTime=impossible;
	
	for(int i=0;i<max;i++)
	{
		Earlist[i]=impossible; //结点V的最早完成时间=max( W + Edge<W->V> ) 
		Latest[i]=maxValue; //结点W的最迟完成时间=min( V - Edge<W->V> ) 
		
		Indegree[i]=0;
		Outdegree[i]=0;
	}
	return;
}

四、参考

  1. 浙江大学 陈越、何钦铭老师主讲的数据结构
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值