惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

不改一行插件代码,实现消息优先级与阻断 洛玖定时任务系统 在 butterfly 主题中添加首页点集动画(基于p2line项目) 你好,2026 stm32f4xx-ads1256驱动 stm32f4xx-ad9854并行驱动 主动式网站状态监测实现及其应用 右键菜单加入用Trae打开文件和文件夹 三角洲行动ID映射表 洛玖SDK说明 为网页文章开头添加原文连接 Hexo-Butterfly主题在主页添加GitHub贡献日历 Proteus中555定时器仿真问题 装饰器 洛玖开发日记 STS3032舵机获取力矩输出 kotlin网页前后端那些事 mspm0g3507-ad9850 奇怪的bug Paddle模型转PaddleLite 人工智能考核 构建一个yolov3网络 yolo和paddle模型常见输出参数 PaddleDetection 随便写的一些东西 实验室C语言第一次考核题目讲解及相关代码解读 C语言神经网络房价预测系统 C、C++数组,指针,指针数组,数组指针的区别 C、C++的大括号是必须的部分吗? C、C++预处理详解 C、C++其他关键字详解 C、C++存储类型关键字详解 C、C++控制语句关键字详解 C、C++数据类型关键字详解 C、C++关键字 C++药品管理系统 Bi-LSTM(Attention)的PyTorch实现 C语言实现波士顿房价预测 easy库的使用 edgeboardFZ3A相关问题 Predict.py的编写 Paddle环境搭建 百度Paddle模型训练 GMD09601-0.96OLED显示屏 【LeetCode 1617】统计子树中城市之间最大距离 C语言学习相关 STM32阵列按键 CH32(F10X F20X) 最短路
拓扑排序
洛屿 · 2023-03-03 · via

什么是拓扑排序?

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

每个顶点出现且只出现一次

若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面

有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说

拓扑排序有什么用?

它可以用来解决形如:

有n个任务需要完成,每个任务有一个耗时和一个前置任务列表,前置任务完成后才能开始当前任务。求完成所有任务的最短时间

这样具有依赖关系的问题

拓扑排序的实现

实现拓扑排序的关键就是维护一个入度为0的顶点的集合


Eg:P1113 杂务

题目描述

John的农场在给奶牛挤奶前有很多杂务要完成,每一项杂务都需要一定的时间来完成它。比如:他们要将奶牛集合起来,将他们赶进牛棚,为奶牛清洗乳房以及一些其它工作。尽早将所有杂务完成是必要的,因为这样才有更多时间挤出更多的牛奶。当然,有些杂务必须在另一些杂务完成的情况下才能进行。比如:只有将奶牛赶进牛棚才能开始为它清洗乳房,还有在未给奶牛清洗乳房之前不能挤奶。我们把这些工作称为完成本项工作的准备工作。至少有一项杂务不要求有准备工作,这个可以最早着手完成的工作,标记为杂务$1$。John有需要完成的$n$个杂务的清单,并且这份清单是有一定顺序的,杂务$k(k>1)$的准备工作只可能在杂务$1$至$k-1$中。

写一个程序从$1$到$n$读入每个杂务的工作说明。计算出所有杂务都被完成的最短时间。当然互相没有关系的杂务可以同时工作,并且,你可以假定John的农场有足够多的工人来同时完成任意多项任务。

输入格式

第1行:一个整数$n$,必须完成的杂务的数目($3 \le n \le 10,000$);

第$2$至$(n+1)$行: 共有$n$行,每行有一些用$1$个空格隔开的整数,分别表示:

* 工作序号($1$至$n$,在输入文件中是有序的);

* 完成工作所需要的时间$len(1 \le len \le 100)$;

* 一些必须完成的准备工作,总数不超过$100$个,由一个数字$0$结束。有些杂务没有需要准备的工作只描述一个单独的$0$,整个输入文件中不会出现多余的空格。

输出格式

一个整数,表示完成所有杂务所需的最短时间。

样例 #1

样例输入 #1

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

样例输出 #1

1
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

#define MAXN 10001
vector<int> followUpTask[MAXN];//邻接表存图,对于下标为i的节点,其存储的为i的后续任务
int inDrgee[MAXN]; //入度
int timeNeed[MAXN]; //任务所需时间
int timeOver[MAXN]; //第i号任务总耗时

int n; //图的节点数

int _max(int a, int b) {
return a > b ? a : b;
}

int tuopu() {
queue<int> q;
for (int i = 1; i <= n; i++) {
if (!inDrgee[i]) { //统计入度为0的点,放入队列,准备拓扑排序
q.push(i);
timeOver[i] = timeNeed[i]; //对于入度为0的点,它的总耗时就是自己任务所需时间
}
}

while (q.size()) {
//pre 存取的是入度为0的点(任务可以独立进行,或任务前置任务已经完成的任务点)
int pre = q.front(); q.pop();

//对于pre的后续任务,我们进行一个遍历
for (auto& crt : followUpTask[pre]) {
//后续任务的耗时等于:max( 后续任务耗时 , pre任务+后续任务耗时 )
timeOver[crt] = _max(timeOver[crt], timeOver[pre] + timeNeed[crt]);

//如果该后续任务的所有前置均完成(入度为0),将该后续任务放入队列,作为拓扑排序的新节点
if (!--inDrgee[crt])q.push(crt);
}
}

int ans = 0;
for (int i = 1; i <= n; i++) {
ans = _max(ans, timeOver[i]);
}

return ans;
}

int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);

cin >> n;

int idx;
for (int i = 1; i <= n; i++) {
cin >> idx >> timeNeed[i];

int preTask;
cin >> preTask;
while (preTask) {
//反向存储
//对于前置任务preTask,存储其后续任务
followUpTask[preTask].push_back(i);
inDrgee[i]++; //后续任务入度加一
cin >> preTask;
}
}

cout << tuopu();

return 0;
}

自问自答:

Q:为什么是取max?

A:因为任务是不限制人数的可并行完成模式,在已经计算出每个任务所需耗时后,所有任务中的最大耗时即为完成所有任务的最短耗时

利用拓扑排序思维的不建图实现(硬性要求1号任务不需要前置任务)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <vector>
using namespace std;

int itime[10001];
int finishTime[10001];
vector<int> prepare[10001];

int _max(int a, int b) {
return a > b ? a : b;
}

int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;

int idx;
for (int i = 1; i <= n; i++) {
cin >> idx >> itime[i];

int prepa;
cin >> prepa;
while (prepa) {
prepare[i].push_back(prepa);
cin >> prepa;
}
}

int ans = 0;
finishTime[1] = itime[1];
for (int i = 2; i <= n; i++) {
for (auto& t : prepare[i]) {
finishTime[i] = _max(finishTime[i], finishTime[t] + itime[i]);
}
ans = _max(ans, finishTime[i]);
}

cout << ans;

return 0;
}