拓扑排序dfs版+判环

版权声明:看我干嘛? 你又没打算转载我的博客~ https://blog.csdn.net/wjh2622075127/article/details/82712940

以前就听说拓扑排序可以用dfs来写了,只是一直没有去尝试,想一想的话会觉得很复杂,dfs怎么排?

要从入度为0的点出发吗?
如果有多个入度为0的点,每个都dfs一遍吗?那他们不是会有重复不是会乱套?

总之,对于从来都是用bfs写拓扑的我来说,觉得用dfs简直不可思议。但是了解之后,买毛病!精彩!而且还学会了一张图如何判环(因为有环的图是不能拓扑排序的)。

总体思路就是:dfs + 栈。


首先我们讨论一下拓扑排序的性质,对于一个图,它可能会有好几种拓扑排序,但他们同时满足一个规律,那就是如果存在有向边u->v, 那么结点u必须排在v之前(前驱)。同时这种性质具有传递性,也就是说如果同时存在v->t, 那么满足ut之前。同样的,如果uv两个结点在图中并不满足这种性质,那么谁在前谁在后就无所谓了。正是利用这个规则,我们进行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

核心代码如下

扫描二维码关注公众号,回复: 3328587 查看本文章
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();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/wjh2622075127/article/details/82712940