10.关键路径

实验10 关键路径

实验目的

  • 通过上机掌握图的存储定义、生成、显示
  • 掌握图的拓扑排序与关键路径算法

实验内容

  • 从文件中读入有向图(网)并以邻接表存储
  • 利用 AOE 网络求取其关键路径

代码

tu.txt (教程P179 图6.28)

9 11
a b c d e f g h i
a b 6
a c 4
a d 5
b e 1
c e 1
d f 2
e g 9
e h 7
f h 4
g i 2
h i 4

critical_path.cpp

#include <algorithm>
#include <iostream>
#include <fstream>
using namespace std;

#define MVNum 20			  //最大顶点数
#define GRAPH_FILE "./tu.txt" // 输入的文件路径

struct ArcNode
{
	int adjvex;		  //该边所指向的顶点的位置
	int weight;		  //权值
	ArcNode *nextarc; //指向下一条边的指针
};

using VerTexType = char;

struct VNode
{
	VerTexType data;   //顶点信息
	ArcNode *firstarc; //指向第一条依附该顶点的边的指针
};

using AdjList = VNode[MVNum]; // 邻接表类型

struct ALGraph
{
	AdjList vertices;		  //邻接表
	AdjList inverse_vertices; //逆邻接表
	int vexnum, arcnum;		  //图的当前顶点数和边数
};

// 顺序栈的定义
struct spStack
{
	int *base;
	int *top;
	int stacksize;
};

// 栈的初始化
void InitStack(spStack &S)
{
	S.base = new int[MVNum];
	if (!S.base)
		exit(-1);
	S.top = S.base;
	S.stacksize = MVNum;
}

// 销毁栈
void DestroyStack(spStack &S)
{
	delete[] S.base;
}

// 判断栈是否为空
bool StackEmpty(spStack &S)
{
	return S.top == S.base;
}

// 元素入栈
void Push(spStack &S, int i)
{
	if (S.top - S.base == S.stacksize)
		return; // 栈满
	*S.top = i;
	S.top++; // top 指向下一个空位置
}

// 栈顶元素出栈
void Pop(spStack &S, int &i)
{
	if (StackEmpty(S))
		return; // 栈空
	S.top--;
	i = *S.top;
}

//---------------------------------------

// 确定结点 v 在图 G 邻接表的下标
int LocateVex(ALGraph &G, VerTexType v)
{
	for (int i = 0; i < G.vexnum; ++i)
		if (G.vertices[i].data == v)
			return i;
	return -1;
}

// 创建有向图G的邻接表、逆邻接表
void CreateUDG(ALGraph &G)
{
	fstream file(GRAPH_FILE);
	if (!file)
	{
		cout << "未找到相关文件,无法打开!" << endl;
		exit(-1);
	}
	file >> G.vexnum >> G.arcnum;	   // 输入结点数, 边数
	for (int i = 0; i < G.vexnum; ++i) //输入各点,构造表头结点表
	{
		file >> G.vertices[i].data; // 输入顶点值
		G.inverse_vertices[i].data = G.vertices[i].data;
		// 初始化表头结点的指针域为空
		G.vertices[i].firstarc = nullptr;
		G.inverse_vertices[i].firstarc = nullptr;
	}
	//输入各边,构造邻接表(头插法)
	for (int k = 0; k < G.arcnum; ++k)
	{
		VerTexType v1, v2;
		int weight;
		file >> v1 >> v2 >> weight;	 //输入一条边依附的两个顶点, 和边的权值
		int src = LocateVex(G, v1);	 //确定 v1 在 G 中位置, src 是边的来源结点
		int dest = LocateVex(G, v2); //确定 v2 在 G 中位置, dest 是边指向的结点

		// 在邻接表插入新的边结点
		ArcNode *p1 = new ArcNode;
		p1->weight = weight;
		p1->adjvex = dest;
		p1->nextarc = G.vertices[src].firstarc; // 头插法
		G.vertices[src].firstarc = p1;

		// 在逆邻接表插入新的边结点
		ArcNode *p2 = new ArcNode;
		p2->weight = weight;
		p2->adjvex = src;
		p2->nextarc = G.inverse_vertices[dest].firstarc;
		G.inverse_vertices[dest].firstarc = p2;
	}
	file.close();
}

// 通过逆邻接表求出各顶点的入度存入数组indegree中
void FindInDegree(ALGraph &G, int indegree[])
{
	for (int i = 0; i < G.vexnum; i++)
	{
		int count = 0;
		for (ArcNode *p = G.inverse_vertices[i].firstarc; p; p = p->nextarc)
			count++;
		indegree[i] = count;
	}
}

// 求拓扑排序数组, 保存在 topo 中
bool TopologicalOrder(ALGraph &G, int topo[])
{
	// 求出各顶点的入度存入数组 indegree 中
	int *indegree = new int[G.vexnum];
	FindInDegree(G, indegree);

	spStack S;
	InitStack(S);

	// 入度为 0 的结点下标进栈
	for (int i = 0; i < G.vexnum; ++i)
		if (indegree[i] == 0)
			Push(S, i);

	int index = 0; // 记录 topo 数组的下标
	while (!StackEmpty(S))
	{
		int v;
		Pop(S, v);		 // 弹出一个入度为 0 的结点的下标
		topo[index] = v; // 将其保存在 topo 数组中
		index++;		 // 指向 topo 数组下一个位置

		// 将与之相邻的结点入度减一
		for (ArcNode *p = G.vertices[v].firstarc; p; p = p->nextarc)
		{
			int k = p->adjvex; // k 为 v 的邻接点的下标
			--indegree[k];
			if (indegree[k] == 0) // 若入度减为0,则入栈
			{
				Push(S, k);
			}
		}
	}

	// 释放内存
	delete[] indegree;
	DestroyStack(S);

	if (index < G.vexnum)
		return false; //该有向图有回路
	else
		return true;
}

// G为邻接表存储的有向网,输出G的各项关键活动
bool CriticalPath(ALGraph &G)
{
	int vexnum = G.vexnum; // 顶点个数
	int arcnum = G.arcnum; // 边的条数
	int *topo = new int[vexnum];
	int *event_earliest = new int[arcnum];
	int *event_latest = new int[arcnum];

	if (!TopologicalOrder(G, topo))
		return false;

	// 初始化给每个事件的最早发生时间
	// 源点的最早发生时间是 0, 其它结点就先来一个比较小的数占位, 就用 0 吧方便, 后面再更新
	for (int i = 0; i < vexnum; i++)
		event_earliest[i] = 0;

	// 按拓扑次序求每个事件的最早发生时间
	// 事件的最早发生时间起码要等到它的所有前驱事件结束
	// A--(2)-->C  B--(5)-->C , A最早4时刻发生, B最早2时刻发生, C最早发生得等AB中最迟完成的那个
	// A结束:4+2=6  B结束:5+2=7  所以C最找得7时刻发生
	for (int i = 0; i < vexnum; i++)
	{
		int k = topo[i];	 // 取得拓扑序列中的顶点序号k
		for (ArcNode *pre = G.inverse_vertices[k].firstarc; pre; pre = pre->nextarc) // 遍历 k 的所有前驱结点(事件)
		{
			int j = pre->adjvex; // j为前驱结点的序号
			event_earliest[k] = max(event_earliest[k], event_earliest[j] + pre->weight);
		}
	}

	// 初始化每个事件的最迟发生时间
	// 汇点的最迟发生时间就是它的最早发生时间, 其它结点先来一个比较大的值, 后面再更新, 也用这个值吧
	for (int i = 0; i < vexnum; i++)
		event_latest[i] = event_earliest[vexnum - 1];

	// 按逆拓扑次序求每个事件的最迟发生时间
	// 事件的最迟发生时间就是 deadline, 再拖可能导致后面的事情做不完, 比如交作业前的最后一晚是写作业的最迟发生时间
	// 要算一个事件的最迟发生时间, 得保证后续事件可以完成
	// A--(2)-->B   A--(1)-->C  假如 B 事件最迟6时刻必须开始, C在4时刻必须开始
	// 为了保证B能完成,A拖到6-2=4时刻必须开始,为了保证C完成,A拖到4-1=3时刻必须开始, 所以A事件最迟发生时间是其中最早的一个,也就是3
	for (int i = vexnum - 1; i >= 0; i--)
	{
		int k = topo[i]; // 取得逆拓扑序列中的顶点序号 k
		for (ArcNode *p = G.vertices[k].firstarc; p; p = p->nextarc) // p指向 k 的后继结点(事件)
		{
			int j = p->adjvex; // j 为后继结点的序号
			event_latest[k] = min(event_latest[k], event_latest[j] - p->weight);
		}
	}

	// 判断每一活动是否为关键活动
	cout << "关键活动路径为: ";
	for (int i = 0; i < vexnum; i++)
	{
		for (ArcNode *p = G.vertices[i].firstarc; p; p = p->nextarc) // 处理 i 的所以后继结点(事件)
		{
			int j = p->adjvex;		 // j为后继事件的序号
			int e = event_earliest[i];	 // 活动<vi, vj>的最早开始时间就是事件 i 的最早开始时间
			int l = event_latest[j] - p->weight; // 活动<vi, vj>的最迟开始时间是事件 j 的最迟开始时间 - 活动所需时间
			if (e == l)		 // 若为活动的时间余量为 0, 说明<vi, vj>是关键活动
				cout << G.vertices[i].data << "-->" << G.vertices[j].data << " ";
		}
	}

	// 释放内存
	delete [] topo;
	delete [] event_latest;
	delete [] event_earliest;

	return true;
}

int main()
{
	cout << "************算法6.13 关键路径算法**************" << endl;
	ALGraph G;
	CreateUDG(G);
	cout << "有向图创建完成!" << endl;
	if (!CriticalPath(G))
		cout << "网中存在环,无法进行拓扑排序!" << endl;
	cout << endl;
}

截图

posted @ 2020-12-01 16:40  zaxtyson  阅读(143)  评论(0编辑  收藏  举报