【数据结构】——拓扑排序和关键路径

一、拓扑排序:

DAG图和AOV网:DAG图即有向无环图。AOV网就是用顶点表示活动,边表示活动的优先级,来描述一个项目或者工程的DAG图

拓扑序列:一个DAG图中,存在V1,V2,......,Vn个顶点,若存在Vi----》Vj的路径,必须保证Vi始终在Vj的前面,这条路径就是拓扑序列。

拓扑排序:对DAG图构造拓扑序列的过程。

操作:根据上面知识如何去选择点去加入拓扑序列呢?其实就是根据点的入度来选择。

1、建立一个统计顶点的入度数组ind[]和一个存储拓扑序列的数组topo[],再建立一个栈容器用于存储入度为0的顶点。

2、先排序前先遍历一遍ind[]数组,将入度为0的顶点全放进栈中;然后呢进行n次枚举,每次从栈中取出一个顶点加入序列中,再把它的邻接点的入度更新,如果有入度为0的顶点也加入栈中。

//拓扑排序:时间复杂度为O(n+m)

#include <iostream>
int ind[105];//入度数组
int topo[105], k;//topo数组和统计个数的指针
//采用邻接表存储DAG图
typedef struct ENode {
	int adj;
	ENode* next;
}ENode;

struct Graph {
	char data;
	ENode* first;
}e[105];

int n, m;

int find(char x)//查找顶点编号
{
	int i;
	for (i = 1; i <= n; ++i)
	{
		if (e[i].data == x)break;
	}
	return i;
}

//封装一个栈
typedef struct stack {
	int arr[105];
	int size = 0;

	void Push(int x)
	{
		arr[++size] = x;
	}

	int Empty()
	{
		return size == 0;
	}

	int top()
	{
		return arr[size];
	}

	void Pop()
	{
		if (Empty()) return;
		size--;
	}

}st;

int TopoSort()
{
	st st;
	for (int i = 1; i <= n; ++i)//全部遍历一次ind数组
	{
		if (ind[i] == 0)
			st.Push(i);
	}
	for (int i = 1; i <= n; ++i)//枚举n次选点
	{
		if (st.Empty()) //可用于判环,即当图中存在环的时候,栈中的顶点一定会被取消完,但因为存在环会导致没有入度为0的顶点加入栈中
		{
			return 1;
		}
		int j = st.top();
		st.Pop();
		topo[++k] = j;//使k作为下标保持和顶点数组一样

		ENode* p = e[j].first;//更新邻接点入度数组
		while (p != NULL)
		{
			int k = p->adj;
			ind[k]--;
			if (ind[k] == 0) st.Push(k);

			p = p->next;
		}
	}
	return 0;
}

int main()
{
	std::cin >> n >> m;
	getchar();
	for (int i = 1; i <= n; ++i)
		std::cin >> e[i].data, e[i].first = NULL;
	char x, y;
	int xi, yi;
	for (int i = 1; i <= m; ++i)
	{
		getchar();
		std::cin >> x >> y;
		xi = find(x);
		yi = find(y);
		//x-------->y
		ENode* p = new ENode;
		p->adj = yi;
		p->next = e[xi].first;
		e[xi].first = p;

		ind[yi]++;//入度数组
	}
	if (TopoSort())
	{
		std::cout << "存在环\n";
		return 0;
	}
	for (int i = 1; i <= k; ++i)
	{
		std::cout << e[topo[i]].data;
	}

	return 0;
}

二、关键路径:


AOE网:即边表示活动的网。或者理解为用带权的边表示活动,边权表示活动的某一个数据(如时间),用顶点表示事件,来描述一个活动或者工程的DAG图

活动和事件的区别:活动是持续性的过程,描述一般为:活动的开始和活动的结束;事件是瞬时性的,描述一般为:事件的发生。

事件的专属术语ETV事件的最早发生时间LTV事件的最晚发生时间

活动的专属术语ETE活动的最早发生时间LTE活动的最晚发生时间

活动的最早发生时间ETE = 起点事件的最早发生时间ETV

活动的最晚发生时间LTE = 终点事件的最晚发生时间LTV - 该活动的时长(即边权)

关键活动:活动的ETE = 活动的LTE。

非关键活动:活动的ETE != 活动的LTE。

关键路径:即图中路径长度最长的一条路径。同时关键路径也是全部由关键活动构成的路径。


给出一个AOE网,如何求关键路径?

求关键路径就是去找关键活动,而关键活动又是由活动的ETE和LTE判断,于是问题转化为求活动的ETE和LTE;ETE和LTE又是通过ETV和LTV求得,于是问题转化为去求事件的ETV和LTV:

(1)求每个事件的ETV, 就是按照拓扑序列去求得每个顶点事件的ETV:                                     

起点事件s的ETV[s] = 0 ; 然后执行拓扑排序算法:ETV[i] = max (ETV[i] , ETV[j] + w)

(2)求每个事件的LTV,可以按照逆拓扑序列去求得每个顶点事件的LTV:

终点事件e的LTV[e] = ETV[e] ;逆拓扑序列顺序就是拓扑序列的倒着遍历,即去倒着遍历topo数组得:LTV[i] = min (LTV[i] , LTV[j] - w)

这俩个公式也表明了一个事件可能得要某些不同的活动都完成才能发生。 

(3)最后根据每个事件的ETV和LTV去求对应活动的ETE和LTE:   

活动的最早开始时间ETE = 起点事件的最早开始时间ETV

活动的最晚开始时间LTE = 终点事件的最晚开始时间LTV - 该活动的时长(即边权)。


//关键路径:时间复杂度为O(n+m)
#include <iostream>

int ind[105];//入度数组
int topo[105], k;//拓扑排序数组
int ETV[105];//事件最早发生时间数组
int LTV[105];//事件最晚发生时间数组
//采用邻接表存储带权有向图
typedef struct ENode {
	int adj,w;
	ENode* next;
}ENode;

struct Graph {
	char data;
	ENode* first;
}e[105];

int n, m,w;

int find(char x)
{
	int i;
	for (i = 1; i <= n; ++i)
	{
		if (e[i].data == x)break;
	}
	return i;
}

typedef struct stack {
	int arr[105];
	int size = 0;

	void Push(int x)
	{
		arr[++size] = x;
	}

	int Empty()
	{
		return size == 0;
	}

	int top()
	{
		return arr[size];
	}

	void Pop()
	{
		if (Empty()) return;
		size--;
	}

}st;

void TopoSort()
{
	std::memset(ETV, 0, sizeof(ETV));//开始把ETV数组都赋值为0
	st st;
	for (int i = 1; i <= n; ++i)//全部遍历一次ind数组
	{
		if (ind[i] == 0)
			st.Push(i);
	}
	for (int i = 1; i <= n; ++i)//枚举n次选点
	{
		int j = st.top();//从栈中取出一个点
		st.Pop();
		topo[++k] = j;//使k作为下标保持和顶点数组一样

		ENode* p = e[j].first;//更新邻接点入度数组
		while (p != NULL)
		{
			int k = p->adj;
			ind[k]--;
			if (ind[k] == 0) st.Push(k);
			ETV[k] = std::max(ETV[k], ETV[j] + p->w);//在更新邻接点的时候顺便把每个邻接点的ETV进行更新
			p = p->next;
		}
	}
}

void CriticalPath()
{
	int ed = topo[k];
	//开始把LTV[]都赋值为终点的LTV
	std::fill(&LTV[0], &LTV[0] + 105, ETV[ed]);

	//倒着遍历topo数组,更新LTV数组
	for (int i = k - 1; i >= 1; i--)
	{
		ENode* p = e[i].first;
		while (p != NULL)
		{
			int j = p->adj;
			LTV[i] = std::min(LTV[i], LTV[j] - p->w);
			p = p->next;
		}
	}

	for (int i = 1; i <= n; ++i)//遍历点从而遍历每条边
	{
		ENode* p = NULL;
		int ete, lte;
		for (p = e[i].first; p != NULL; p = p->next)
		{
			int j = p->adj;//边的终点即终点事件
			//求i----->j这条边的ete和lte
			ete = ETV[i]; //ete为起点事件的最早发生时间
			lte = LTV[j] - p->w;//lte为终点事件的最晚发生时间-边权
			if (ete == lte)//满足关键活动条件
			{
				std::cout << e[i].data << " " << e[j].data << std::endl;
			}
		}
	}
}

int main()
{
	std::cin >> n >> m;
	getchar();
	for (int i = 1; i <= n; ++i)
		std::cin >> e[i].data, e[i].first = NULL;
	char x, y;
	int xi, yi;
	for (int i = 1; i <= m; ++i)
	{
		getchar();
		std::cin >> x >> y >> w;
		xi = find(x);
		yi = find(y);
		//x-------->y
		ENode* p = new ENode;
		p->adj = yi;
		p->w = w;
		p->next = e[xi].first;
		e[xi].first = p;

		ind[yi]++;//入度数组
	}
	//先求topo排序,求ETV[]和topo[]
	TopoSort();
	//再求关键路径:求LTV[],再求关键路径
	CriticalPath();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值