📜  资质 |门 CS 1998 |第 65 题(1)

📅  最后修改于: 2023-12-03 15:41:49.856000             🧑  作者: Mango

资质 |门 CS 1998 |第 65 题

题目描述

有一张 $n$ 个点 $m$ 条边的有向图,请你判断这张图是否存在环。

输入格式

第一行两个整数 $n$ 和 $m$。

接下来 $m$ 行,每行两个整数 $a$ 和 $b$,表示存在一条 $a$ 到 $b$ 的有向边。

输出格式

如果存在环则输出 Yes,否则输出 No。

数据范围

$1\leq n,m\leq 10^5$

示例

输入:

3 3
1 2
2 3
3 1

输出:

Yes
解题思路

可以使用深度优先搜索(DFS)或拓扑排序解决该问题。

方法一:DFS

利用DFS探索每个节点,若当前节点已经访问过,则说明存在环。

bool dfs(int u) {
    if (st[u]) return true; // 如果已经访问过则说明存在环
    st[u] = true;
    for (int i = h[u]; i != -1; i = e[i].ne) {
        int j = e[i].j;
        if (dfs(j)) return true;
    }
    st[u] = false; // 回溯
    return false;
}

bool check_circle() {  // 返回是否有环
    memset(st, false, sizeof st);
    for (int i = 1; i <= n; i++)
        if (dfs(i)) return true;
    return false;
}
方法二:拓扑排序

采用拓扑排序,如果存在环,则必定有点的入度为0,但是遍历完毕后入度为0的点数量不足 $n$ 个,说明存在环。

bool topsort() { // 判断图是否存在环
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i++)
        if (!din[i]) q[++tt] = i;
    while (hh <= tt) {
        int t = q[hh++];
        for (int i = h[t]; i != -1; i = e[i].ne) {
            int j = e[i].j;
            if (--din[j] == 0) q[++tt] = j;
        }
    }
    return tt + 1 < n;
}

bool check_circle() { // 返回是否有环
    memset(h, -1, sizeof h);
    memset(din, 0, sizeof din);
    int cnt = 0;
    for (int i = 0; i < m; i++) {
        int a = edges[i].a, b = edges[i].b;
        add(a, b);
        din[b]++;
    }
    return topsort();
}

最后,我们在主函数中调用上述方法即可。

int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) cin >> edges[i].a >> edges[i].b;
    if (check_circle()) cout << "Yes" << endl;
    else cout << "No" << endl;
    return 0;
}
时间复杂度

用 BFS 的拓扑排序时间复杂度是 $O(n+m)$,DFS 的时间复杂度是 $O(n^2)$,因为最坏情况下每个点都被遍历了一次,每次遍历时需要 $O(n)$ 的时间;空间复杂度均为 $O(n+m)$。