目录
拓扑排序是一种有向无环图(DAG)的顶点排序方法,它将一个有向无环图中的所有顶点排成一个线性序列,使得图中任意一条有向边上的起点排在终点的前面。
这样说还不够具体,我们先来看一个例子。假设某大学的课程安排如下:
| 课程编号 | 课程名称 | 先修课程 |
|---|---|---|
| 1 1 1 | 高等数学 | − - − |
| 2 2 2 | 程序设计基础 | − - − |
| 3 3 3 | 离散数学 | 1 , 2 1,\,2 1,2 |
| 4 4 4 | 数据结构 | 2 , 3 2,\,3 2,3 |
| 5 5 5 | 高级语言程序设计 | 2 2 2 |
| 6 6 6 | 编译方法 | 4 , 5 4,\,5 4,5 |
| 7 7 7 | 操作系统 | 4 , 9 4,\,9 4,9 |
| 8 8 8 | 普通物理 | 1 1 1 |
| 9 9 9 | 计算机原理 | 8 8 8 |
为了顺利修完这九门课程,我们必须安排一个合理的学习顺序。
首先根据以上表格构建一个有向图,即若 j j j 的先修课程有 i i i,则画一条 i i i 到 j j j 的有向边,于是可以得到:
一个合理的学习顺序是: 1 → 8 → 9 → 2 → 3 → 5 → 4 → 7 → 6 1\to8\to9\to2\to3\to5\to4\to7\to6 1→8→9→2→3→5→4→7→6,该序列又称拓扑序列,是对上述有向图进行拓扑排序后的结果。
可以看出,拓扑序列不唯一。例如, 1 → 8 → 9 → 2 → 3 → 5 → 4 → 6 → 7 1\to8\to9\to2\to3\to5\to4\to6\to7 1→8→9→2→3→5→4→6→7 也是满足要求的学习顺序。
此外还可以证明,DAG一定存在拓扑序列,存在拓扑序列的图也一定是DAG,因此DAG又被称为拓扑图。
拓扑排序的具体步骤如下:
代码实现:
int n, m; // n表示节点数量,m表示边的数量
int h[N], e[N], ne[N], idx; // 邻接表存图
int in[N]; // 保存每个点的入度
vector<int> L; // 存储拓扑序列
// 拓扑排序算法
void topo_sort() {
queue<int> q;
for (int i = 1; i <= n; i++)
// 找到所有入度为0的点然后将其添加到队列中,同时也输出到拓扑序列中
if (in[i] == 0) {
q.push(i);
L.push_back(i);
}
while (!q.empty()) {
auto t = q.front();
q.pop();
for (int i = h[t]; ~i; i = ne[i]) { // 遍历t的所有相邻节点
int j = e[i];
in[j]--; // 将相邻节点的入度-1
// 如果相邻节点的入度减到0,则入队列,同时将其输出到拓扑序列中
if (in[j] == 0) {
q.push(j);
L.push_back(i);
}
}
}
}
如果 L.size() == n 成立,说明该图存在拓扑序列,否则说明该图不是DAG。
拓扑排序的时间复杂度和BFS相同,均为 O ( n + m ) O(n+m) O(n+m)。
上述代码显得过于臃肿,我们可以进一步简化以形成模版(务必背过)。
void topo_sort() {
queue<int> q;
for (int i = 1; i <= n; i++)
if (!in[i]) q.push(i);
while (!q.empty()) {
auto t = q.front();
q.pop();
L.push_back(t);
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (!--in[j]) q.push(j);
}
}
}
🔗 原题链接:AcWing 848. 有向图的拓扑序列
直接套模板即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int h[N], e[N], ne[N], idx;
int in[N];
vector<int> L;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topo_sort() {
queue<int> q;
for (int i = 1; i <= n; i++)
if (!in[i]) q.push(i);
while (!q.empty()) {
auto t = q.front();
q.pop();
L.push_back(t);
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (!--in[j]) q.push(j);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--) {
int x, y;
cin >> x >> y;
add(x, y), in[y]++;
}
topo_sort();
if (L.size() == n) {
for (auto i: L) cout << i << ' ';
cout << "\n";
} else cout << -1 << "\n";
return 0;
}
🔗 原题链接:AcWing 1191. 家谱树
如果 b b b 是 a a a 的孩子,则画一条由 a a a 指向 b b b 的有向边,然后拓扑排序即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = N * N / 2;
int n;
int h[N], e[M], ne[M], idx;
int in[N];
vector<int> L;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topo_sort() {
queue<int> q;
for (int i = 1; i <= n; i++)
if (!in[i]) q.push(i);
while (!q.empty()) {
auto t = q.front();
q.pop();
L.push_back(t);
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (!--in[j]) q.push(j);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
while (cin >> x, x) {
add(i, x);
in[x]++;
}
}
topo_sort();
for (auto i: L) cout << i << ' ';
cout << "\n";
return 0;
}
🔗 原题链接:AcWing 1192. 奖金
对于建图,如果 b b b 的奖金比 a a a 高,则画一条 a a a 到 b b b 的有向边。
要使总奖金最少,很显然,初始时所有入度为 0 0 0 的点的奖金都为 100 100 100 元。之后,对于图中任意两点 x , y x,y x,y,如果存在 x x x 到 y y y 的有向边,那么 y y y 的奖金应当比 x x x 的奖金多一元。
我们可以让队列不止保存员工的编号,还保存每位员工的奖金,这样在遍历的时候就能够方便的去计算每位员工的奖金了。
#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 20010;
int n, m;
int h[N], e[M], ne[M], idx;
int in[N];
vector<int> L;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topo_sort() {
queue<pair<int, int>> q;
for (int i = 1; i <= n; i++)
if (!in[i]) q.emplace(i, 100);
while (!q.empty()) {
auto [t, bonus] = q.front();
q.pop();
L.push_back(bonus);
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (!--in[j]) q.emplace(j, bonus + 1); // 多一元
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--) {
int x, y;
cin >> x >> y;
add(y, x), in[x]++;
}
topo_sort();
if (L.size() == n) cout << accumulate(L.begin(), L.end(), 0) << "\n";
else cout << "Poor Xed\n";
return 0;
}
🔗 原题链接:AcWing 164. 可达性统计
本题可以用BFS暴力求解,时间复杂度为 O ( n ( n + m ) ) ≈ 1.8 × 1 0 9 O(n(n+m))\approx1.8\times 10^9 O(n(n+m))≈1.8×109,只能过 3 / 5 3/5 3/5 的测试点。
不妨设从 x x x 出发能够到达的点的集合为 f ( x ) f(x) f(x),易知
f ( x ) = { x } ∪ ⋃ y ∈ N ( x ) f ( y ) f(x)=\{x\}\cup \bigcup_{y\in N(x)} f(y) f(x)={x}∪y∈N(x)⋃f(y)
也就是说,从 x x x 出发能够到达的点的集合为从「 x x x 的各个后继节点」出发能够到达的点的集合的并集再加上 x x x 自身。所以,只有在计算出一个点的所有后继节点的 f f f 值之后,我们才能够算出该点的 f f f 值。这启发我们先对图进行拓扑排序得到一个拓扑序列,然后再倒序计算。
我们可以用 n n n 位二进制数来存储每个 f ( x ) f(x) f(x),其中第 i i i 位是 1 1 1 表示 x x x 能到 i i i,第 i i i 位是 0 0 0 表示 x x x 不能到 i i i。这样一来,对若干个集合求并就相当于对若干个二进制数进行按位或运算,每个 f ( x ) f(x) f(x) 中 1 1 1 的个数就代表了从 x x x 出发能够到达的节点的数量。
#include <bits/stdc++.h>
using namespace std;
const int N = 30010;
int n, m;
int h[N], e[N], ne[N], idx;
int in[N];
vector<int> L;
bitset<N> f[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topo_sort() {
queue<int> q;
for (int i = 1; i <= n; i++)
if (!in[i]) q.push(i);
while (!q.empty()) {
auto t = q.front();
q.pop();
L.push_back(t);
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (!--in[j]) q.push(j);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--) {
int x, y;
cin >> x >> y;
add(x, y), in[y]++;
}
topo_sort();
for (int i = n - 1; ~i; i--) { // 倒序遍历
int j = L[i];
f[j][j] = 1; // j一定能到达自身
for (int k = h[j]; ~k; k = ne[k])
f[j] |= f[e[k]]; // 按照公式计算出f[j]的值
}
for (int i = 1; i <= n; i++) cout << f[i].count() << "\n";
return 0;
}
🔗 原题链接:CF 1385E
本题说白了就是给图中的所有无向边指定方向,使得最终得到的图是一个DAG。
我们可以先考虑仅由有向边构成的子图,如果这个子图都不是DAG,那么后续无论怎样为无向边指定方向(相当于添加有向边)都不可能构成DAG,此时应当输出 NO。如果这个子图是DAG,那么我们一定可以通过指定无向边的方向来构造出新的DAG。
具体来说,我们先对有向边构成的子图进行拓扑排序,于是可以得到一个拓扑序列。对于每一个无向边 ( a , b ) (a,b) (a,b),若 a a a 在拓扑序列中的下标小于 b b b 在拓扑序列中的下标,则添加有向边 a → b a\to b a→b,否则添加有向边 b → a b\to a b→a,这样可以保证添加完有向边后得到的图仍然是DAG。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, m;
int h[N], e[N], ne[N], idx;
int in[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topo_sort(vector<int> &L) {
queue<int> q;
for (int i = 1; i <= n; i++)
if (!in[i]) q.push(i);
while (!q.empty()) {
auto t = q.front();
q.pop();
L.push_back(t);
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (!--in[j]) q.push(j);
}
}
}
void solve() {
memset(h, -1, sizeof h);
memset(in, 0, sizeof in);
vector<int> L;
vector<pair<int, int>> edges;
cin >> n >> m;
while (m--) {
int t, x, y;
cin >> t >> x >> y;
if (t) add(x, y), in[y]++;
else edges.emplace_back(x, y);
}
topo_sort(L);
if (L.size() != n) cout << "NO\n";
else {
cout << "YES\n";
// 先输出有向图部分
for (int i = 1; i <= n; i++)
for (int j = h[i]; ~j; j = ne[j])
cout << i << ' ' << e[j] << "\n";
vector<int> pos(n + 1); // 因为节点的编号是从1开始的,所以要多开一个
for (int i = 0; i < n; i++) pos[L[i]] = i;
// 再输出补全的部分
for (auto [a, b]: edges) {
if (pos[a] > pos[b]) swap(a, b);
cout << a << ' ' << b << "\n";
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我需要用任何语言编写一个算法,根据3个因素对数组进行排序。我以度假村为例(如Hipmunk)。假设我想去度假。我想要最便宜的地方、最好的评论和最多的景点。但是,显然我找不到在所有3个中都排名第一的方法。Example(assumingthereare20importantattractions):ResortA:$150/night...98/100infavorablereviews...18of20attractionsResortB:$99/night...85/100infavorablereviews...12of20attractionsResortC:$120/night
我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的
我正在尝试按Rails相关模型中的字段进行排序。我研究的所有解决方案都没有解决如果相关模型被另一个参数过滤?元素模型classItem相关模型:classPriority我正在使用where子句检索项目:@items=Item.where('company_id=?andapproved=?',@company.id,true).all我需要按相关表格中的“位置”列进行排序。问题在于,在优先级模型中,一个项目可能会被多家公司列出。因此,这些职位取决于他们拥有的company_id。当我显示项目时,它是针对一个公司的,按公司内的职位排序。完成此任务的正确方法是什么?感谢您的帮助。PS-我
我有可变数量的表格和可变数量的行,我想让它们一个接一个地显示,但如果表格不适合当前页面,请将其放在下一页,然后继续。我已将表格放入事务中,以便我可以回滚然后打印它(如果高度适合当前页面),但我如何获得表格高度?我现在有这段代码pdf.transactiondopdf.table@data,:font_size=>12,:border_style=>:grid,:horizontal_padding=>10,:vertical_padding=>3,:border_width=>2,:position=>:left,:row_colors=>["FFFFFF","DDDDDD"]pdf.
我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排
例如,假设我有一个名为Products的模型,并且在ProductsController中,我有以下代码用于product_listView以显示已排序的产品。@products=Product.order(params[:order_by])让我们想象一下,在product_listView中,用户可以使用下拉菜单按价格、评级、重量等进行排序。数据库中的产品不会经常更改。我很难理解的是,每次用户选择新的order_by过滤器时,rails是否必须查询,或者rails是否能够以某种方式缓存事件记录以在服务器端重新排序?有没有一种方法可以编写它,以便在用户排序时rails不会重新查询结果
我有一个对象如下:[{:id=>2,:fname=>"Ron",:lname=>"XXXXX",:photo=>"XXX"},{:id=>3,:fname=>"Dain",:lname=>"XXXX",:photo=>"XXXXXXX"},{:id=>1,:fname=>"Bob",:lname=>"XXXXXX",:photo=>"XXXX"}]我想按fname排序,不区分大小写,所以它会导致编号:1,3,2我该如何排序?我正在尝试:@people.sort!{|x,y|y[:fname]x[:fname]}但这没有任何效果。 最佳答案
有人可以告诉我如何根据自定义字符串对嵌套数组进行排序吗?比如有没有办法排序:[['Red','Blue'],['Green','Orange'],['Purple','Yellow']]“橙色”、“黄色”,然后是“蓝色”?最终结果如下所示:[['Green','Orange'],['Purple','Yellow'],['Red','Blue']]它不是按字母顺序排序的。我很想知道我是否可以定义要排序的值以实现上述目标。 最佳答案 sort_by对于这种排序总是非常方便:a=[['Red','Blue'],['Green','Ora