以前就听说拓扑排序可以用dfs来写了,只是一直没有去尝试,想一想的话会觉得很复杂,dfs怎么排?
要从入度为0的点出发吗?
如果有多个入度为0的点,每个都dfs一遍吗?那他们不是会有重复不是会乱套?
总之,对于从来都是用bfs写拓扑的我来说,觉得用dfs简直不可思议。但是了解之后,买毛病!精彩!而且还学会了一张图如何判环(因为有环的图是不能拓扑排序的)。
总体思路就是:dfs + 栈。
首先我们讨论一下拓扑排序的性质,对于一个图,它可能会有好几种拓扑排序,但他们同时满足一个规律,那就是如果存在有向边u->v
, 那么结点u
必须排在v
之前(前驱)。同时这种性质具有传递性,也就是说如果同时存在v->t
, 那么满足u
在t
之前。同样的,如果u
和v
两个结点在图中并不满足这种性质,那么谁在前谁在后就无所谓了。正是利用这个规则,我们进行dfs的顺序是无所谓的。
为何?因为我们从root
结点开始dfs一遍,可以找到所有的必须在这个root
结点之后的点,那么我们就满足了拓扑序的规则了,那么我们无论先dfs(u)
还是先dfs(v)
, 都不会违背这个规则(除非有环),那么同时我们只要按照某种合理的方式存储所有这些点,那么他们就是拓扑序了。
什么是合理的方式?栈!考量一个dfs(u)
, 在它结束该退出时,它代表它的结点u。在dfs递归中,什么点会最先exit?没有后继结点的点(或者后继已入栈的点)!那么把所有点分成两个集合,一个是待处理的点集D,一个是已拓扑排序后的点集A,当且仅当D中某个点没有后继结点(或该后继结点已经加入了点集A中)时,它可以从D转移到A,而dfs的回溯方式,恰恰就自动实现了这样的功能。 结合代码更容易体会。
不判环拓扑排序代码,如果你已知图是DAG的话。
#include <iostream>
#include <stack>
using namespace std;
struct Edge {
int to, next;
};
const int maxn = 10010;
int head[maxn] = {};
int n, m, cnt = 1;
bool vis[maxn] = {};
Edge edge[maxn];
stack<int> S;
void add(int u, int v)
{
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
void dfs(int u)
{
vis[u] = true;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (!vis[v]) dfs(v);
}
S.push(u);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int u, v;
cin >> u >> v;
add(u, v);
}
for (int i = 1; i <= n; ++i) {
if (vis[i] == 0) dfs(i);
}
while (!S.empty()) {
cout << S.top() << ' ';
S.pop();
}
}
那么,如何判环呢?判环只是在dfs函数上稍做些修改,其中最主要的是vis数组
的含义有所扩展,以及对下一结点进行dfs的条件判断。
不判环的拓扑排序,vis
只代表某一结点有没有被放问过,而现在,vis有三个值,-1,0,1
。-1代表已访问过,但不是从当前系列dfs访问来的,0代表未访问过,1代表访问国,且是当前系列访问过的(意味着有环,如u->v, v->t, t->u
)
核心代码如下
bool dfs(int u)
{
vis[u] = 1;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] == 1) return false;
if (vis[v] == 0 && !dfs(v)) return false;
}
vis[u] = -1;
S.push(u);
return true;
}
dfs()函数的值意味着是否有环。
可以看到,如果发现将要访问的节点在这个dfs圈子里(为1),那么直接返回false,否则如果没访问过(为0),那么就进去访问吧,不过因为我们要维护是否有环的性质,所以对其返回值进行判断。如果为false
,对不起,有环,拓扑排序直接终止了。
完整代码:
#include <iostream>
#include <stack>
using namespace std;
struct Edge {
int to, next;
};
const int maxn = 10010;
int n, m, cnt = 1;
int head[maxn] = {};
int vis[maxn] = {};
Edge edge[maxn];
stack<int> S;
void add(int u, int v)
{
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
bool dfs(int u)
{
vis[u] = 1;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] == 1) return false;
if (vis[v] == 0 && !dfs(v)) return false;
}
vis[u] = -1;
S.push(u);
return true;
}
bool topsort()
{
for (int i = 1; i <= n; ++i) {
if (!vis[i]) if (!dfs(i)) {
return false;
}
}
return true;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int u, v;
cin >> u >> v;
add(u, v);
}
bool ans = topsort();
if (ans == false) cout << "图中存在环,不能进行拓扑排序" << endl;
else {
while (!S.empty()) {
cout << S.top() << ' ';
S.pop();
}
}
}