Description
在图论中,拓扑序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列. 且该序列必须满足下面两个条件:
- 每个顶点出现且只出现一次.
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面.
对于一个含有n个节点的有向无环图(节点编号0到n-1),输出它的一个拓扑序.
图的节点数和边数均不多于100000,保证输入的图是一个无环图.
请为下面的Solution类实现解决上述问题的topologicalSort函数,函数参数中n为图的节点数,edges是边集,edges[i]表示第i条边从edges[i].first指向edges[i].second. 函数返回值为有向图的一个拓扑序. 有向图有多个拓扑序时,输出任意一个即可.
class Solution {
public:
vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {
}
};
例1:
n = 3,edges = {(0, 1), (0, 2)},函数应返回{0, 1, 2}或者{0, 2, 1}.
例2:
n = 4,edges = {(0, 1), (0, 2), (1, 2), (3, 0)},函数应返回{3, 0, 1, 2}.
解析:
以下有关拓扑排序的解释来自于这篇博文:
拓扑排序
一、什么是拓扑排序
在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
每个顶点出现且只出现一次。
若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
例如,下面这个图:
它是一个 DAG 图,那么如何写出它的拓扑排序呢?这里说一种比较常用的方法:
1、从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
2、从图中删除该顶点和所有以它为起点的有向边。
重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
于是,得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。
通常,一个有向无环图可以有一个或多个拓扑排序序列。
二、拓扑排序的应用
拓扑排序通常用来“排序”具有依赖关系的任务。
比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边
代码
测试main函数:
#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
#include<time.h>
#include<queue>
using namespace std;
int main() {
Solution solution;
vector<pair<int, int>> edges = { {0,1}, {0,2}, {1,2}, {3,0} };
vector<int> res = solution.topologicalSort(4, edges);
for (int i = 0; i < res.size(); i++) {
cout << res[i] << " ";
}
system("pause");
return 0;
}
超时了orz,毕竟太粗暴了,复杂度都到三次方了2333。
class Solution {
public:
vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {
vector<int> result;
// 找出所有点的入度
vector<int> degree(n, 0);
for (int i = 0; i < edges.size(); i++) {
degree[edges[i].second]++;
}
// 进行拓扑排序
for (int j = 0; j < n; j++) { // 要把n个点轮流输出
for (int i = 0; i < n; i++) {
if (degree[i] == 0) {
// 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出
result.push_back(i);
// 从图中删除该顶点和所有以它为起点的有向边
degree[i] = -1;
for (int k = 0; k < edges.size(); k++) {
if (edges[k].first == i) {
degree[edges[k].second]--;
}
}
break;
}
}
}
return result;
}
};
改进1
使用队列,卧槽,居然第四个测试样例还是超时….这复杂度已经是O(V·E)了…
class Solution {
public:
vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {
vector<int> result;
// 找出所有点的入度
vector<int> degree(n, 0);
for (int i = 0; i < edges.size(); i++) {
degree[edges[i].second]++;
}
// 进行拓扑排序
queue<int> q;
// 将所有入度为0的顶点入队
for (int j = 0; j < n; j++) {
if (degree[j] == 0) {
q.push(j);
}
}
while (!q.empty()) {
int v = q.front(); // 从队列中取出一个顶点并删除
q.pop();
result.push_back(v);
// 将以该定点为起始边的终点的入度--,如果减过入度为0则入队。
for (int k = 0; k < edges.size(); k++) {
if (edges[k].first == v) {
if (--degree[edges[k].second] == 0)
q.push(edges[k].second);
}
}
}
return result;
}
};
改进二
AC了!!!喜大普奔!!!
利用数组可以直接存取数据的特性,用空间换时间。把题目要求的vector<pair<int, int>>& edges
换成
vector<vector<int>> edgesMatrix(n, vector<int>())
第i行的vector表示第i个点能到达哪些点(的序号)。复杂度是O(V+E)。
class Solution {
public:
vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {
vector<int> result;
vector<vector<int>> edgesMatrix(n, vector<int>());
// 找出所有点的入度和矩阵(第i行的vector表示第i个点能到达哪些点(的序号))
vector<int> degree(n, 0);
for (int i = 0; i < edges.size(); i++) {
degree[edges[i].second]++;
edgesMatrix[edges[i].first].push_back(edges[i].second);
}
// 进行拓扑排序
queue<int> q;
// 将所有入度为0的顶点入队
for (int j = 0; j < n; j++) {
if (degree[j] == 0) {
q.push(j);
}
}
while (!q.empty()) {
int v = q.front(); // 从队列中取出一个顶点并删除
q.pop();
result.push_back(v);
// 将以该定点为起始边的终点的入度--,如果减后入度为0则入队。
for (int k = 0; k < edgesMatrix[v].size(); k++) {
if (--degree[edgesMatrix[v][k]] == 0)
q.push(edgesMatrix[v][k]);
}
}
return result;
}
};
讲真,我这样慢慢尝试,考试药丸
萌萌的算法老师给的代码:
求原图的逆,再深搜
class Solution {
private:
vector< vector<int> > V;
vector<int> ans;
vector<bool> vis;
// 深度搜索,因为是有向无环图,
// 所以从一个点dfs得到最后的点一定是汇点
// 存储该汇点后,倒数第二个点也就成了汇点
// 执行dfs后,vis[u] = true 相当于删除从该路找到的汇点
void dfs(int u)
{
int v, i;
vis[u] = true;
for (i=0; i<V[u].size(); i++)
{
v = V[u][i];
if (!vis[v]) dfs(v);
}
ans.push_back(u);
}
public:
vector<int> topologicalSort(int n, vector< pair<int, int> >& edges) {
int u, v, i;
V.resize(n);
vis.resize(n);
// 求原图的逆Gr(即箭头方向反转)
// 新构成的V,第i行表示逆图中能到达的点的vector
// 原图入度为0的点就成了新图的汇点
for (i=0; i<edges.size(); i++)
{
u = edges[i].first;
v = edges[i].second;
V[v].push_back(u);
}
for (i=0; i<n; i++) vis[i] = false;
for (i=0; i<n; i++) if (!vis[i]) dfs(i);
return ans;
}
};