数据结构 图9 关键活动

全部每周作业和视频思考题答案和解析 见 浙江大学 数据结构 思考题+每周练习答案

题目:假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。

比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。

但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。

任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。

请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。

输入格式:

输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1~N编号,M是子任务的数量,依次编号为1~M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。

输出格式:

如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反

输入样例:

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

输出样例:

17
1->2
2->4
4->6
6->7

解答:

首先需要注意的是不知道为啥这里的顶点标号又变成了从1到N,而不是上个题的0到N-1,搞这种细节游戏真是麻烦。所以要注意把相应的读入都减1然后存储。

直接把上个题的程序(别忘了读入后减1)拿来,然后改改调度不可行输出0,就可以把第一部分正确输出了。

第二部分是找关键路径,就是再往回遍历了呗。

思路也不难,建立一个反向的流程网络就好了(无非就是把V1和V2点交换一下)。

我们需要修改一下读入,生成一个倒着的图。

然后返回的时候我们需要这么做:

	for (V = 0; V < Graph->Nv; V++)
		if (Indegree[V] == 0) {
			myQueue.push(V);
			Latest[V] = Earliest[V];
		}
		else {
			Latest[V] = MYINFINITY;
		}



。。。。。。。

	if ((Latest[V] - W->Weight)<Latest[W->AdjV]) {
		Latest[W->AdjV] = Latest[V] - W->Weight;
	}

上面是把一个新建的数组进行初始化,注意因为反向是找小的,所以除了0度的点,其他初始化都是无穷。然后在循环遍历的时候判断找出回向中最小的时间。

为了验证我们的计算是否正确,先把图画一画:

然后反向找最小时间:

打印输出和上图完全一致。(因为程序现在太长,所以不发中间过程版本了)

按道理现在应该很简单了,但是题目中有个要求:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。我写程序的时候产生了点疑问:程序输入没有告诉你输入是从小到大的,也就是说,你可能得进行排序?然后是,起始点一样的话,需要与输入方向相反?为什么呢?

转念一想,原来是这样!因为你输入的顺序与你插入到邻接表的顺序是相同的,但是邻接表是往头上插,所以顺序相反(换句话说,这个题的答案要求不就是为了使用邻接表来做嘛!)

所以需要注意的是当我去浏览别人的解法时,很多人根本没有注意到这个“与输入顺序相反”这个要求,但是因为检测的数据的原因不会看出错误,但是并不是说写的就完全正确,他们是这么写的:

        for(int i = 1; i <= n; i++){
            .........
            for(int j = n; j >= 1; j--){
........

但是,你不知道人家输入的顺序就是从小到大输入的呀?题目中没有这么说。反正我看了三四遍题目,也没有这么说。

但是这里牵扯到另外一个问题:如何从最小的起始顶点依次输出

难道要搞一个最小堆?太麻烦了。

这个题真的有些不好,如果是直接按照从i到j依次递增输出,就直接用个邻接矩阵做不就好了?

吐槽完了,来点实际的。既然题目已经这熊样了,我也没有办法,懒得再用邻接矩阵写一遍,直接搞排序。

还有一个需要注意的地方:从后向前遍历的时候,如果有多条路通向多个末尾,我们所有末尾的值要设为最短时间

程序代码:

#include <iostream>
#include <queue>
using namespace std;

#define MaxVertexNum 1000
typedef int Vertex;
#define MYINFINITY 60000000
// 邻接表存储 - Kruskal最小生成树算法 

//-------------------- 顶点并查集定义 --------------------
typedef Vertex ElementType; // 默认元素可以用非负整数表示 
typedef Vertex SetName;     // 默认用根结点的下标作为集合名称 
typedef ElementType SetType[MaxVertexNum]; // 假设集合元素下标从0开始 							    
typedef int WeightType;       // 边的权值设为整型 
typedef char DataType;        // 顶点存储的数据类型设为字符型 
Vertex Earliest[MaxVertexNum];
Vertex Latest[MaxVertexNum];
queue<Vertex> myQueue;
//下面三个数组是为了生成反向AOE图的
Vertex V1Array[MaxVertexNum];
Vertex V2Array[MaxVertexNum];
WeightType WeightArray[MaxVertexNum];
//存储关键活动的
int keyActivityNum = 0;
struct keyActivity {
	Vertex Vstart;
	Vertex Vend;
};
keyActivity myKeyActivity[MaxVertexNum];
// 边的定义
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--;
			V1Array[i] = E->V1;
			V2Array[i] = E->V2;
			WeightArray[i] = E->Weight;
			//注意:如果权重不是整型,Weight的读入格式要改 
			InsertEdge(Graph, E);
		}
	}

	//如果顶点有数据的话,读入数据 
	//for (V = 0; V<Graph->Nv; V++)
	//cin >> Graph->G[V].Data;

	return Graph;
}

LGraph BuildBackGraph(LGraph Graph1)
{
	LGraph Graph;
	Edge E;
	Vertex V;
	int Nv = Graph1->Nv, i;
	Graph = CreateGraph(Nv); //初始化有Nv个顶点但没有边的图
	if (Graph1->Ne != 0) { //如果有边 
		E = (Edge)malloc(sizeof(struct ENode)); //建立边结点 
												//读入边,格式为"起点 终点 权重",插入邻接矩阵 
		for (i = 0; i<Graph1->Ne; i++) {
			E->V1 = V2Array[i];
			E->V2 = V1Array[i];
			E->Weight = WeightArray[i];
			//注意:如果权重不是整型,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;
}

//邻接表存储 - 反向查找时间算法 
bool keyRoute(LGraph Graph, int showestTime)
{ //对Graph进行拓扑排序,  TopOrder[]顺序存储排序后的顶点下标 
	int Indegree[MaxVertexNum];
	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);
			Latest[V] = showestTime;
		}
		else {
			Latest[V] = MYINFINITY;
		}
			
	//下面进入拓扑排序 
	while (!myQueue.empty()) {
		V = myQueue.front(); //弹出一个入度为0的顶点 
		myQueue.pop();
		for (W = Graph->G[V].FirstEdge; W; W = W->Next) {
			if (--Indegree[W->AdjV] == 0)//若删除V使得W->AdjV入度为0 
				myQueue.push(W->AdjV); //则该顶点入列 
			if ((Latest[V] - W->Weight)<Latest[W->AdjV]) {
				Latest[W->AdjV] = Latest[V] - W->Weight;
			}
		}

	} //while结束
	return true;
}

//邻接表存储 - 再次正向去寻找的机动时间的算法 
void cal4KeyRoute(LGraph Graph)
{ //对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();
		//对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 ((Latest[W->AdjV] - W->Weight)<=Earliest[V]) { //注意不是输出有机动时间的,而是输出没有机动时间的
				//cout << (V) << "->" << (W->AdjV)<< " "<< W->Weight << endl;
				myKeyActivity[keyActivityNum].Vstart = (V + 1);
				myKeyActivity[keyActivityNum].Vend = (W->AdjV + 1);
				keyActivityNum++;
			}
		}
	} //while结束
	//cout << endl;
}

//只对起始点进行排序,不打乱输入反向的顺序(题目要求的)
void mySort(void) {
	for(int i = 0;i<keyActivityNum-1;i++)
		for (int j = 0;j<keyActivityNum-i-1;j++) {
			if (myKeyActivity[j].Vstart > myKeyActivity[j + 1].Vstart) {
				Vertex temp;
				temp = myKeyActivity[j].Vstart;
				myKeyActivity[j].Vstart = myKeyActivity[j + 1].Vstart;
				myKeyActivity[j + 1].Vstart = temp;
				temp = myKeyActivity[j].Vend;
				myKeyActivity[j].Vend = myKeyActivity[j + 1].Vend;
				myKeyActivity[j + 1].Vend = temp;
			}
		}
}

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 << endl;
		//继续处理
		LGraph backGraph = BuildBackGraph(myGraph);
		keyRoute(backGraph, showestTime);
		//for (int i = 0;i < myGraph->Nv;i++) {
		//	cout << Latest[i] <<" ";
		//}cout << endl;
		cal4KeyRoute(myGraph);
		mySort();
		for (int i = 0;i < keyActivityNum;i++) {
			cout << myKeyActivity[i].Vstart << "->" << myKeyActivity[i].Vend << endl;
		}
	}
	else {
		cout << "0";
	}

	system("pause");
	return 0;
}

测试结果:

发布了174 篇原创文章 · 获赞 394 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/tiao_god/article/details/105367246
今日推荐