一.什么是拓扑排序

拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。 且该序列必须满足下面两个条件: 每个顶点出现且只出现一次。 若存在一条从顶点A 到顶点B 的路径,那么在序列中顶点A 出现在顶点B 的前面。

二.图片描述

该算法解决的问题类似于办事的流程一样
在做一件事情之前必须完成之前的一件或多件相关的事情
拓扑排序_#include

三.代码的实现图示

拓扑排序_#include_02
1.先在所有任务中找到没有需要其他任务做铺垫的任务,图片中体现为点1。将1先入队,寻找其他点,在第一次寻找结束后,开始出队,将1出队,寻找与他相关的后面任务,解锁后面任务的一个条件。
拓扑排序_任务调度_03
再次寻找当前完全解锁条件的任务再次入队,依次循环,直到队空为止

四.相关例题

1. 6-5-拓扑排序 任务调度的合理性 (25 分)

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

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

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

输入格式:
输入说明:输入第一行给出子任务数N(≤100),子任务按1~N编号。随后N行,每行给出一个子任务的依赖集合:首先给出依赖集合中的子任务数K,随后给出K个子任务编号,整数之间都用空格分隔。

输出格式:
如果方案可行,则输出1,否则输出0。

输入样例1:

12
0
0
2 1 2
0
1 4
1 5
2 3 6
1 3
2 7 8
1 7
1 10
1 7

输出样例1:

1

输入样例2:

5
1 4
2 1 4
2 2 5
1 3
0

输出样例2:

0

#include<iostream>
#include<queue>
using namespace std;
#define N 120
int main()
{
	int a[N][N] = { 0 };
	int size[N] = { 0 };
	queue<int>q;//建队用来存解锁的任务
	int n;
	cin >> n;
	int i = 0;
	int m;
	int aim;
	for (i = 1; i <= n; i++) {
		cin >> m;
		for (int j = 0; j < m; j++) {
			cin >> aim;
			a[i][aim] = 1;//将任务与解锁条件挂钩
			size[i]++;//记录解锁的条件个数,当个数为0时解锁
		}
	}
	for (i = 1; i <= n; i++) {
		if (!size[i]) {
			q.push(i);//第一遍寻找无需条件的任务
		}
	}
	while (!q.empty()) {
		aim = q.front();
		q.pop();
		for (i = 1; i <= n; i++) {//寻找需要该任务作为解锁条件的任务
			if (a[i][aim]) {
				size[i]--;//解除该任务的一个条件个数
				if(!size[i]) q.push(i);//如果该任务达到解锁,就入队
			}
		}
	}
	for (i = 1; i <= n; i++) {
		if (size[i] != 0) {
			cout << "0";
			return 0;
		}
	}
	cout << "1" << endl;
}
2. 7-99 最短工期 (25 分)

一个项目由若干个任务组成,任务之间有先后依赖顺序。项目经理需要设置一系列里程碑,在每个里程碑节点处检查任务的完成情况,并启动后续的任务。现给定一个项目中各个任务之间的关系,请你计算出这个项目的最早完工时间。

输入格式:
首先第一行给出两个正整数:项目里程碑的数量 N(≤100)和任务总数 M。这里的里程碑从 0 到 N−1 编号。随后 M 行,每行给出一项任务的描述,格式为“任务起始里程碑 任务结束里程碑 工作时长”,三个数字均为非负整数,以空格分隔。

输出格式:
如果整个项目的安排是合理可行的,在一行中输出最早完工时间;否则输出"Impossible"。

输入样例 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

输出样例 1:

18

输入样例 2:

4 5
0 1 1
0 2 2
2 1 3
1 3 4
3 2 5

输出样例 2:

Impossible

#include<iostream>
#include<queue>
using namespace std;
#define N 120
int main()
{
	int map[N][N];
	int cost[N] = { 0 };
	int size[N] = { 0 };
	queue<int> q;
	int n, m;
	cin >> n >> m;
	int i = 0;
	int j = 0;
	for (i = 0; i < n; i++) {
		for (j = 0; j < n; j++) {
			map[i][j] = -1;//因为存在工期为0的情况,所以初始话为-1
		}
	}
	int a1, a2, w;
	for (i = 0; i < m; i++) {
		cin >> a1 >> a2 >> w;
		map[a1][a2] = w;
		size[a2]++;
	}
	int aim;
	int sum = 0;
	for (i = 0; i < n; i++) {
		if (!size[i]) q.push(i);
	}
	while (!q.empty()) {
		aim = q.front();
		q.pop();
		if (cost[aim] > sum)sum = cost[aim];
		for (i = 0; i < n; i++) {
			if (map[aim][i] != -1) {
				size[i]--;
				if (!size[i])q.push(i);
				if (cost[i] < cost[aim] + map[aim][i])
					cost[i] = cost[aim] + map[aim][i];//刷新当前节点的工期
			}
		}
	}
	for (i = 0; i < n; i++) {
		if (size[i]) {
			cout << "Impossible" << endl;
			return 0;
		}
	}
	cout << sum << endl;
}

五.关键代码

	for (i = 1; i <= n; i++) {
		if (!size[i]) {
			q.push(i);//第一遍寻找无需条件的任务
		}
	}
	while (!q.empty()) {
		aim = q.front();
		q.pop();
		for (i = 1; i <= n; i++) {//寻找需要该任务作为解锁条件的任务
			if (a[i][aim]) {
				size[i]--;//解除该任务的一个条件个数
				if(!size[i]) q.push(i);//如果该任务达到解锁,就入队
			}
		}
	}

该代码可以达到拓扑排序的效果,而有些题目就需要该代码的拓展