全部每周作业和视频思考题答案和解析 见 浙江大学 数据结构 思考题+每周练习答案
题目:Given the relations of all the activities of a project, you are supposed to find the earliest completion time of the project.
Input Specification:
Each input file contains one test case. Each case starts with a line containing two positive integers N (≤100), the number of activity check points (hence it is assumed that the check points are numbered from 0 to N−1), and M, the number of activities. Then M lines follow, each gives the description of an activity. For the i
-th activity, three non-negative numbers are given: S[i]
, E[i]
, and L[i]
, where S[i]
is the index of the starting check point, E[i]
of the ending check point, and L[i]
the lasting time of the activity. The numbers in a line are separated by a space.
每个输入文件包含一个测试用例。每种情况都以一行开始,该行包含两个正整数N(≤100)、活动检查点的数量(因此,假设检查点的编号范围为0到N-1)和M,即活动数量。接下来是M行,每行给出一个活动的描述。对于第i个活动,给出了三个非负数:S[i],E[i]和L[i],其中S[i]是开始检查点的索引,E[i]是结束检查点,L[i]是活动的持续时间。一行中的数字用空格隔开。
Output Specification:
For each test case, if the scheduling is possible, print in a line its earliest completion time; or simply output "Impossible".
Sample Input 1:
9 12
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
5 4 0
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4
Sample Output 1:
18
Sample Input 2:
4 5
0 1 1
0 2 2
2 1 3
1 3 4
3 2 5
Sample Output 2:
Impossible
解答:
思路很简单,构成一个拓扑排序,把每层最多需要的时间记录,逐层记录。
这里需要注意,我们的拓扑结构是最简单的任务,不需要保证某些步骤同步进行,视频里说了一种复杂的情况,与该题无关,我之前就混淆了。
我们先整理一个使用邻接表存储的拓扑排序程序:
注意因为是有向图,所以插入边的操作需要去掉后半部分。题目给的顶点序号是0到N-1,所以不需要读入数据再减一(参考上个题,顶点序号1-N,所以需要进一步处理)。
#include <iostream>
#include <queue>
using namespace std;
#define MaxVertexNum 1000
typedef int Vertex;
// 邻接表存储 - Kruskal最小生成树算法
//-------------------- 顶点并查集定义 --------------------
typedef Vertex ElementType; // 默认元素可以用非负整数表示
typedef Vertex SetName; // 默认用根结点的下标作为集合名称
typedef ElementType SetType[MaxVertexNum]; // 假设集合元素下标从0开始
typedef int WeightType; // 边的权值设为整型
typedef char DataType; // 顶点存储的数据类型设为字符型
queue<Vertex> myQueue;
// 边的定义
typedef struct ENode *PtrToENode;
struct ENode {
Vertex V1, V2; // 有向边<V1, V2>
WeightType Weight; // 权重
};
typedef PtrToENode Edge;
//邻接点的定义
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode {
Vertex AdjV; // 邻接点下标
WeightType Weight; // 边权重
PtrToAdjVNode Next; // 指向下一个邻接点的指针
};
//顶点表头结点的定义
typedef struct Vnode {
PtrToAdjVNode FirstEdge; // 边表头指针
DataType Data; // 存顶点的数据
// 注意:很多情况下,顶点无数据,此时Data可以不用出现
} AdjList[MaxVertexNum]; // AdjList是邻接表类型
//图结点的定义
typedef struct GNode *PtrToGNode;
struct GNode {
int Nv; // 顶点数
int Ne; // 边数
AdjList G; // 邻接表
};
typedef PtrToGNode LGraph; // 以邻接表方式存储的图类型
LGraph CreateGraph(int VertexNum)
{ //初始化一个有VertexNum个顶点但没有边的图
Vertex V;
LGraph Graph;
Graph = (LGraph)malloc(sizeof(struct GNode)); // 建立图
Graph->Nv = VertexNum;
Graph->Ne = 0;
//初始化邻接表头指针
//注意:这里默认顶点编号从0开始,到(Graph->Nv - 1)
for (V = 0; V<Graph->Nv; V++)
Graph->G[V].FirstEdge = NULL;
return Graph;
}
void InsertEdge(LGraph Graph, Edge E)
{
PtrToAdjVNode NewNode;
//插入边 <V1, V2>
//为V2建立新的邻接点
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V2;
NewNode->Weight = E->Weight;
//将V2插入V1的表头
NewNode->Next = Graph->G[E->V1].FirstEdge;
Graph->G[E->V1].FirstEdge = NewNode;
//注意拓扑排序是用的有向图
//若是无向图,还要插入边 <V2, V1>
//为V1建立新的邻接点
//NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
//NewNode->AdjV = E->V1;
//NewNode->Weight = E->Weight;
//将V1插入V2的表头
//NewNode->Next = Graph->G[E->V2].FirstEdge;
//Graph->G[E->V2].FirstEdge = NewNode;
}
LGraph BuildGraph()
{
LGraph Graph;
Edge E;
Vertex V;
int Nv, i;
cin >> Nv; //读入顶点个数
Graph = CreateGraph(Nv); //初始化有Nv个顶点但没有边的图
cin >> Graph->Ne; //读入边数
if (Graph->Ne != 0) { //如果有边
E = (Edge)malloc(sizeof(struct ENode)); //建立边结点
//读入边,格式为"起点 终点 权重",插入邻接矩阵
for (i = 0; i<Graph->Ne; i++) {
cin >> E->V1 >> E->V2 >> E->Weight;
//E->V1--;
//E->V2--;
//注意:如果权重不是整型,Weight的读入格式要改
InsertEdge(Graph, E);
}
}
//如果顶点有数据的话,读入数据
//for (V = 0; V<Graph->Nv; V++)
//cin >> Graph->G[V].Data;
return Graph;
}
//邻接表存储 - 拓扑排序算法
bool TopSort(LGraph Graph, Vertex TopOrder[])
{ //对Graph进行拓扑排序, TopOrder[]顺序存储排序后的顶点下标
int Indegree[MaxVertexNum], cnt;
Vertex V;
PtrToAdjVNode W;
//初始化Indegree[]
for (V = 0; V<Graph->Nv; V++)
Indegree[V] = 0;
//遍历图,得到Indegree[]
for (V = 0; V<Graph->Nv; V++)
for (W = Graph->G[V].FirstEdge; W; W = W->Next)
Indegree[W->AdjV]++; //对有向边<V, W->AdjV>累计终点的入度
//将所有入度为0的顶点入列
for (V = 0; V < Graph->Nv; V++)
if (Indegree[V] == 0)
myQueue.push(V);
//下面进入拓扑排序
cnt = 0;
while (!myQueue.empty()) {
V = myQueue.front(); //弹出一个入度为0的顶点
myQueue.pop();
TopOrder[cnt++] = V; //将之存为结果序列的下一个元素
//对V的每个邻接点W->AdjV
for (W = Graph->G[V].FirstEdge; W; W = W->Next)
if (--Indegree[W->AdjV] == 0)//若删除V使得W->AdjV入度为0
myQueue.push(W->AdjV); //则该顶点入列
} //while结束
if (cnt != Graph->Nv)
return false; //说明图中有回路, 返回不成功标志
else
return true;
}
int main(void) {
LGraph myGraph = BuildGraph();
Vertex TopOrder[MaxVertexNum];
bool flag = TopSort(myGraph, TopOrder);
if (true == flag) {
for (int i = 0;i < myGraph->Nv;i++) {
cout << TopOrder[i] << " ";
}
cout << endl;
}
else {
cout << " 有回路 " << endl;
}
cout << endl;
system("pause");
return 0;
}
打印输出结果为:0 3 2 1 5 4 7 6 8
说明我们的拓扑排序程序是正确的。
然后我们只需要在每个点到其连接点的判断中加入一句话:
if ((Earliest[V] + W->Weight)>Earliest[W->AdjV]) {
Earliest[W->AdjV] = Earliest[V] + W->Weight;
}
也就是说找到每一组到后面最大的数。
然后最后别忘了要找到Earliest数组里面最大的数,因为最大的数才是总项目最短完成时间。
全部代码如下:
#include <iostream>
#include <queue>
using namespace std;
#define MaxVertexNum 1000
typedef int Vertex;
// 邻接表存储 - Kruskal最小生成树算法
//-------------------- 顶点并查集定义 --------------------
typedef Vertex ElementType; // 默认元素可以用非负整数表示
typedef Vertex SetName; // 默认用根结点的下标作为集合名称
typedef ElementType SetType[MaxVertexNum]; // 假设集合元素下标从0开始
typedef int WeightType; // 边的权值设为整型
typedef char DataType; // 顶点存储的数据类型设为字符型
Vertex Earliest[MaxVertexNum];
queue<Vertex> myQueue;
// 边的定义
typedef struct ENode *PtrToENode;
struct ENode {
Vertex V1, V2; // 有向边<V1, V2>
WeightType Weight; // 权重
};
typedef PtrToENode Edge;
//邻接点的定义
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode {
Vertex AdjV; // 邻接点下标
WeightType Weight; // 边权重
PtrToAdjVNode Next; // 指向下一个邻接点的指针
};
//顶点表头结点的定义
typedef struct Vnode {
PtrToAdjVNode FirstEdge; // 边表头指针
DataType Data; // 存顶点的数据
// 注意:很多情况下,顶点无数据,此时Data可以不用出现
} AdjList[MaxVertexNum]; // AdjList是邻接表类型
//图结点的定义
typedef struct GNode *PtrToGNode;
struct GNode {
int Nv; // 顶点数
int Ne; // 边数
AdjList G; // 邻接表
};
typedef PtrToGNode LGraph; // 以邻接表方式存储的图类型
LGraph CreateGraph(int VertexNum)
{ //初始化一个有VertexNum个顶点但没有边的图
Vertex V;
LGraph Graph;
Graph = (LGraph)malloc(sizeof(struct GNode)); // 建立图
Graph->Nv = VertexNum;
Graph->Ne = 0;
//初始化邻接表头指针
//注意:这里默认顶点编号从0开始,到(Graph->Nv - 1)
for (V = 0; V<Graph->Nv; V++)
Graph->G[V].FirstEdge = NULL;
return Graph;
}
void InsertEdge(LGraph Graph, Edge E)
{
PtrToAdjVNode NewNode;
//插入边 <V1, V2>
//为V2建立新的邻接点
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V2;
NewNode->Weight = E->Weight;
//将V2插入V1的表头
NewNode->Next = Graph->G[E->V1].FirstEdge;
Graph->G[E->V1].FirstEdge = NewNode;
//注意拓扑排序是用的有向图
//若是无向图,还要插入边 <V2, V1>
//为V1建立新的邻接点
//NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
//NewNode->AdjV = E->V1;
//NewNode->Weight = E->Weight;
//将V1插入V2的表头
//NewNode->Next = Graph->G[E->V2].FirstEdge;
//Graph->G[E->V2].FirstEdge = NewNode;
}
LGraph BuildGraph()
{
LGraph Graph;
Edge E;
Vertex V;
int Nv, i;
cin >> Nv; //读入顶点个数
Graph = CreateGraph(Nv); //初始化有Nv个顶点但没有边的图
cin >> Graph->Ne; //读入边数
if (Graph->Ne != 0) { //如果有边
E = (Edge)malloc(sizeof(struct ENode)); //建立边结点
//读入边,格式为"起点 终点 权重",插入邻接矩阵
for (i = 0; i<Graph->Ne; i++) {
cin >> E->V1 >> E->V2 >> E->Weight;
//E->V1--;
//E->V2--;
//注意:如果权重不是整型,Weight的读入格式要改
InsertEdge(Graph, E);
}
}
//如果顶点有数据的话,读入数据
//for (V = 0; V<Graph->Nv; V++)
//cin >> Graph->G[V].Data;
return Graph;
}
//返回最大的元素
Vertex getMaxElement(Vertex arr[],int N) {
Vertex maxElement = 0;
for (int i = 0; i < N; i++)
if (maxElement < arr[i])
maxElement = arr[i];
return maxElement;
}
//邻接表存储 - 拓扑排序算法
bool TopSort(LGraph Graph, Vertex TopOrder[])
{ //对Graph进行拓扑排序, TopOrder[]顺序存储排序后的顶点下标
int Indegree[MaxVertexNum], cnt;
Vertex V;
PtrToAdjVNode W;
//初始化Indegree[]
for (V = 0; V<Graph->Nv; V++)
Indegree[V] = 0;
//遍历图,得到Indegree[]
for (V = 0; V<Graph->Nv; V++)
for (W = Graph->G[V].FirstEdge; W; W = W->Next)
Indegree[W->AdjV]++; //对有向边<V, W->AdjV>累计终点的入度
//将所有入度为0的顶点入列
for (V = 0; V < Graph->Nv; V++)
if (Indegree[V] == 0)
myQueue.push(V);
//下面进入拓扑排序
cnt = 0;
while (!myQueue.empty()) {
V = myQueue.front(); //弹出一个入度为0的顶点
myQueue.pop();
TopOrder[cnt++] = V; //将之存为结果序列的下一个元素
//对V的每个邻接点W->AdjV
for (W = Graph->G[V].FirstEdge; W; W = W->Next) {
if (--Indegree[W->AdjV] == 0)//若删除V使得W->AdjV入度为0
myQueue.push(W->AdjV); //则该顶点入列
if ((Earliest[V] + W->Weight)>Earliest[W->AdjV]) {
Earliest[W->AdjV] = Earliest[V] + W->Weight;
}
}
} //while结束
if (cnt != Graph->Nv)
return false; //说明图中有回路, 返回不成功标志
else
return true;
}
int main(void) {
LGraph myGraph = BuildGraph();
Vertex TopOrder[MaxVertexNum];
bool flag = TopSort(myGraph, TopOrder);
Vertex showestTime = getMaxElement(Earliest, myGraph->Nv);
if (true == flag) {
cout << showestTime;
}
else {
cout << "Impossible";
}
system("pause");
return 0;
}
测试结果: