一、前导
1. 需要掌握的知识
本题是 How Long Does It Take 的加强版,不仅要判定图中是否存在回路 、求解最早完成时间 ,还要求解最晚完成时间 并得到关键路径 。关键路径就是:由最早完成时间和最晚完成时间相等 的结点 所构成的路径 关键路径、最早完成时间、最晚完成时间知识点可以参考 关键路径讲解
2. 题目信息
题目来源:PTA / 拼题A 题目地址:关键活动
二、解题思路分析
1. 题意理解
可以在 How Long Does It Take 的思路基础上求解 本题可以通过邻接矩阵存储图,这样可以降低编码量
1. 1 输入数据
7 8
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 输出数据
图如果存在回路,打印 ‘0’ ,否则打印最早完成时间并输出关键路径
2. 思路分析
通过拓扑排序判断图中是否存在回路 并 计算出各结点的最早完成时间和最晚完成时间 按题目要求 关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反 打印关键路径
三、具体实现
1. 弯路和bug
本题使用邻接矩阵存储图会更方便
2. 代码框架(重点)
2.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 各分支函数
BuildGraph() : 构建图,并获取各结点的出度值和入度值
#define impossible -1
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 ;
}
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 ;
}
}
while ( ! q. empty ( ) )
{
CurrentNode= q. front ( ) ;
q. pop ( ) ;
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 ;
}
KeyActive( ) :AC的核心函数。通过顶点的出度值 再反向进行一次拓扑排序,得到最晚完成时间。 最晚完成时间、最早完成时间相等的结点构成的路径就是关键路径。 输出规则 任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反 是一个难点。根据输出规则,可以分析出如下隐含信息:任务开始的交接点编号小者优先 意味着 打印时顶点编号从小到大排列;起点编号相同时,与输入时任务的顺序相反 输入时是按从小到大的顺序录入边的,相反意味着从大到小输出。最后,满足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] )
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] )
cout<< V<< "->" << W<< endl;
}
}
return ;
}
3. 完整AC编码
本文如果对你有帮助,请点赞鼓励 ,谢谢 😊 如有建议或意见,欢迎留言
#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] )
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 ;
}
}
while ( ! q. empty ( ) )
{
CurrentNode= q. front ( ) ;
q. pop ( ) ;
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;
Latest[ i] = maxValue;
Indegree[ i] = 0 ;
Outdegree[ i] = 0 ;
}
return ;
}
四、参考
浙江大学 陈越、何钦铭老师主讲的数据结构