题目信息
AOE 网络是有向无环加权图,其中顶点表示事件,弧表示活动,权表示活动持续的时间,通常可以用来估算工程完成的时间,即图中从开始点到结束点之间最长的路径对应的时间。请完成一个程序,完成下列任务:
1 、计算 AOE 网络对应的拓扑排序。如果排序结果不唯一,请输出按照从小到大的顺序排列的结果。从小到大的顺序就是输入的节点序列顺序(参见下面关于输入格式的说明)。如图1中满足要求的拓扑排序是: a-b-c-d-e-f-g-h-k ,图2中满足要求的拓扑排序是:v1-v3-v5-v2-v6-v4-v7-v8-v9
2 、计算 AOE 网络的关键路径。注意关键路径可能不唯一,要求输出所有的关键路径。同样,按照是按照从小到大的顺序输出。例,如果得到两条关键路径,分别是0-1-3-6-8-9和0-1-3-4-5-8-9,那么先输出后一条路径,因为两条路径中前三个节点相同,而后一条路径第四个节点的编号小。
输入
节点的个数,边的条数;
各个节点的名称序列
边: < 起点 , 终点 , 权值 > 。说明起点和终点是在各个点在输入序列中的位置,如图1中边 <a,b> 表示为 <0,1,6> 。
输出
拓扑排序
关键路径
测试样例
测试样例1
9,11
a,b,c,d,e,f,g,h,k
<0,1,6>,<0,2,4>,<0,3,5>,<1,4,1>,<2,4,1>,<4,6,8>,<4,7,7>,<3,5,2>,<5,7,4>,<6,8,2>,<7,8,4>
a-b-c-d-e-f-g-h-k
a-b-e-h-k
测试样例2
9,11
v1,v2,v3,v4,v5,v6,v7,v8,v9
<0,4,6>,<0,2,4>,<0,5,5>,<4,1,1>,<2,1,1>,<1,6,8>,<1,7,7>,<5,3,2>,<3,7,4>,<6,8,2>,<7,8,4>
v1-v3-v5-v2-v6-v4-v7-v8-v9
v1-v5-v2-v8-v9
测试样例3
4,4
A,B,C,D
<0,1,2>,<1,3,3>,<3,2,5>,<2,1,1>
NO TOPOLOGICAL PATH
解答
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;
char ch;
int NodeNum, EdgeNum;
int topo[55], path[100];
struct Link
{
int dir;//目标位置
int val;//权值
};
struct Node
{
char name[5];
int InNum = 0; //入度
int OutNum = 0; //出度
int Etv, Ltv;//存储事件的最早和最晚发生时间
Link links[300];
int LinkNum = 0;
} nodes[55];
int cmp1(const void *p1, const void *p2)
{
Link *c = (Link *) p1;
Link *d = (Link *) p2;
return (c->dir) > (d->dir);
}
struct Rule2
{
bool operator()(const int &a, const int &b) const
{
return a > b;
}
};
void CreateGraph()
{
char str[500] = {
'\0'};
scanf("%s", str);
int len = strlen(str), q = 0, k = 0;
for (int i = 0; i < len; i++)
{
if (str[i] != ',')
nodes[k].name[q++] = str[i];
else
{
nodes[k].name[q] = '\0';
k++;
q = 0;
}
}
int p = 0, k_flag = 0;
int ch_data[5], cd_p = 0;
char chs[10] = {
'\0'}, ch;
getchar();
while ((ch = getchar()))
{
if (ch == '\n' || ch == -1)
break;
if (ch == '<')
{
k_flag = 1;
continue;
}
if (ch == ',' && k_flag == 1)
{
ch_data[cd_p] = atoi(chs);
memset(chs, '\0', sizeof(chs));
p = 0;
cd_p++;
continue;
}
if (ch != '<' && ch != ',' && k_flag == 1 && ch != '>')
{
chs[p++] = ch;
continue;
}
if (ch == '>' && k_flag == 1)
{
ch_data[cd_p] = atoi(chs);
int u = ch_data[0];
nodes[u].links[nodes[u].LinkNum].dir = ch_data[1];
nodes[u].links[nodes[u].LinkNum].val = ch_data[2];
nodes[u].OutNum++;
nodes[ch_data[1]].InNum++;
nodes[u].LinkNum++;
k_flag = 0;
p = 0;
cd_p = 0;
memset(chs, '\0', sizeof(chs));
continue;
}
}
for (int i = 0; i < NodeNum; i++) //先对邻节点排序,输出即是按顺序的
qsort(nodes[i].links, nodes[i].LinkNum + 1, sizeof(nodes[i].links[0]), cmp1);
}
int TopoLogicalSort_DFS()
{
//广度优先搜索获取拓扑序列
int *Stack;
Stack = (int *) malloc(sizeof(int) * NodeNum); //分配栈空间
int top = 0;
for (int i = 0; i < NodeNum; i++)
{
nodes[i].Etv = 0; //初始化各事件最早发生事件为0
if (nodes[i].InNum == 0)
{
//将入度为0的顶点入栈,既找到了开头节点
Stack[top++] = i;
}
}
int count = 0;
while (top > 0)
{
//采用广度优先搜索获取拓扑序列
int u = Stack[--top];
topo[count++] = u;
for (int i = 0; i <= nodes[u].LinkNum; i++)
{
//将u的邻接点入度减1,并将入度为0的顶点入栈
int v = nodes[u].links[i].dir;
if (nodes[v].Etv < nodes[u].Etv + nodes[u].links[i].val)
{
//更新各顶点事件的最早发生时间
nodes[v].Etv = nodes[u].Etv + nodes[u].links[i].val;
}
if (--nodes[v].InNum == 0)
{
Stack[top++] = v;
}
}
sort(Stack, Stack + top, Rule2());
//使得输出的拓扑序列中无拓扑关系的节点要从小到大输出,但入栈是从大到小
}
return (count == NodeNum);//如果count小于顶点数,说明存在环
}
void PrintPath(int top, int end)
{
int u = path[top - 1];
if (u == end)
{
cout << nodes[path[0]].name; //输出关键路径
for (int i = 1; i < top; i++)
{
cout<<"-"<<nodes[path[i]].name;
}
cout << endl;
return;
}
for (int i = 0; i <= nodes[u].LinkNum; i++)
{
int v = nodes[u].links[i].dir;
if (nodes[u].Etv + nodes[u].links[i].val < nodes[v].Etv)
continue;
if (nodes[v].Etv == nodes[v].Ltv)//关键事件
{
path[top++] = nodes[u].links[i].dir;//入栈
PrintPath(top, end);
top--;//退栈
}
}
}
void CriticalPath()
{
//求关键路径
if (!TopoLogicalSort_DFS())
{
cout << "NO TOPOLOGICAL PATH" << endl;
return;
}
for (int i = 0; i < NodeNum; i++)
{
nodes[i].Ltv = nodes[NodeNum - 1].Etv; //初始化各事件最晚发生事件为最后一个事件发生的时间
if (i)
{
cout << "-";
}
cout << nodes[topo[i]].name; //这里方便题目要求的打印拓扑序列
}
cout << endl;
for (int i = NodeNum - 2; i >= 0; i--)
{
//倒着遍历拓扑排序的点,从倒数第二个开始
int u = topo[i];
for (int j = 0; j <= nodes[u].LinkNum; j++)
{
int v = nodes[u].links[j].dir;
if (nodes[u].Ltv > nodes[v].Ltv - nodes[u].links[j].val)
{
//更新各顶点事件的最晚发生时间
nodes[u].Ltv = nodes[v].Ltv - nodes[u].links[j].val;
}
}
}
path[0] = topo[0];
PrintPath(1, topo[NodeNum - 1]);//从第一个点到最后一个点
}
int main()
{
//freopen("/Users/zhj/Downloads/test.txt", "r", stdin);
cin >> NodeNum >> ch >> EdgeNum;
CreateGraph();//把顶点和边信息读入到表示图的邻接表中
CriticalPath();//求关键路径
return 0;
}
想法
本题的考察点就在于拓扑排序和关键路径上
之前用另外一种方法构建的子函数有一些问题,但尚未找到问题所在
struct Rule1
{
bool operator()(const Link &a, const Link &b) const
{
return a.dir < b.dir;
}
};
void CreateGraph()
{
//把顶点和边信息读入到表示图的邻接表中
string str;
cin >> str;
int len = str.length();
int q = 0, k = 0;
//用q来暂时记录name信息,因为节点名称不止一个字符,用k来记录第几个边
for (int i = 0; i < len; i++)
{
if (str[i] != ',')
{
nodes[k].name[q++] = str[i];
}
else
{
k++;
q = 0;
}
}
cin >> str;
len = str.length();
bool flag = false;
int Num_Num = 0;//第几个数字
int Num_Data[3];
int Char_Num;
string Char_Data;
for (int i = 0; i < len; i++)
{
if (str[i] == '<')
{
//表明是括号对开始
flag = true;
}
else if (str[i] == ',' && flag)
{
//一个数字已经凑齐了
Num_Data[Num_Num] = atoi(Char_Data.c_str());
Char_Data.clear();
Char_Num = 0;
Num_Num++;
}
else if (str[i] != '<' && str[i] != ',' && str[i] != '>' && flag)
{
//此时他应该是个数字
Char_Data[Char_Num++] = str[i];
}
else if (str[i] == '>' && flag)
{
//括号对结束。0起点,1终点,2权值
Num_Data[Num_Num] = atoi(Char_Data.c_str());
int tempNum = Num_Data[0];
nodes[tempNum].links[nodes[tempNum].LinkNum].dir = Num_Data[1];
nodes[tempNum].links[nodes[tempNum].LinkNum].val = Num_Data[2];
nodes[tempNum].OutNum++;//对应的出度+1
nodes[Num_Data[1]].InNum++;//相应的入度+1
nodes[tempNum].LinkNum++;
flag = false;
Num_Num = 0;
Char_Num = 0;
Char_Data.clear();
}
}
for (int i = 0; i < NodeNum; i++)
{
//先对邻节点排序,输出即是按顺序的
sort(nodes[i].links, nodes[i].links + nodes[i].LinkNum, Rule1());
}
}