使用dfs

时间:2018-03-11 17:50:03

标签: c++ graph tree depth-first-search cycle

请告诉我为什么这段代码无法分析是否存在循环 无方向图?代码如下。这是关于spoj的PT07Y问题。我正在做的是获取节点并执行dfs。在我访问同一个节点时执行dfs,然后我说有一个循环。在从节点执行dfs之后,我将被访问数组设为false并执行下一个节点,直到我获得一个循环或访问所有节点。

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
bool vis[10001];

int dfs(int n,vector <int> v[],int a)
{
    vis[n]=true;
    for(int i=0;i<v[n].size();i++)
    {
        if(v[n][i]==a)
            return 1;
        if(vis[v[n][i]]==false)
        {
            dfs(v[n][i],v,a);
        }
    }
    return 0;
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int n,m,i,a,b,cc;
    cin>>n>>m;
    vector <int> v[n+1];
    memset(vis,false,n+1);
    for(i=0;i<m;i++)
    {
        cin>>a>>b;
        v[a].push_back(b);
    }
    for(i=1;i<=n;i++)
    {
        if(dfs(i,v,i))
        {
            cout<<"NO"<<endl;
            return 0;
        }
        memset(vis,false,sizeof(vis));
    }
    cc=-1;
    for(i=1;i<=n;i++)
    {
        if(vis[i]==false)
        {
            cc++;
            if(cc>0)
            {
                cout<<"NO"<<endl;
                return 0;
            }  
            int m= dfs(i,v,-1);
        }
    }
    cout<<"YES"<<endl;
    return 0;
}

1 个答案:

答案 0 :(得分:0)

问题 1

该图被指定为无向图,但输入格式将只指定每条边一次(对于方向之一)。因此,当您从输入中读取类似 1 2 的行时,您必须将 1 的邻接表扩展为 2,并将 2 的邻接表扩展为 {{ 1}}。但是,在您的程序中,您只存储第一个方向。因此,您对图表的表示将是不完整的。

要解决此问题,请扩展您的输入处理以存储两个方向:

1

问题 2

您的 for (i = 0; i < m; i++) { cin >> a >> b; v[a].push_back(b); v[b].push_back(a); } 方法行为不正确:

如果找到目标顶点,dfs 方法将返回 dfs。但是,此返回码不会沿递归传播。因此,当您在 1 方法中进行递归调用 dfs(v[n][i], v, a); 并且该调用返回 dfs 时,当前的 1 调用会忽略此结果。如果当前的 dfs 调用在当前邻接列表中没有找到目标顶点,那么它将完成 dfs 循环并返回 for

要解决此问题,您必须确保如果递归 0 调用返回 dfs,则 1 循环中止并且 for 也由当前的 1 调用,例如:

dfs

应用此修复后,另一个问题被揭示:当 if (dfs(v[n][i], v, a, n) == 1) { return 1; } 方法在相邻顶点列表中发现目标顶点时,它不排除到达当前顶点的边。因此,如果您有一个图 dfs 并且您从 1 <-> 2 处开始 dfs,目标顶点为 1,您将检查包含 1 的相邻顶点列表。遍历从 21 的边,您将检查包含 2 的相邻顶点列表。然后,您的方法将报告已找到目标顶点,但这是无效的,因为它仅沿着您来的地方的边缘找到。

要解决此问题,您可以跟踪每个 1 调用的父顶点。最初,没有设置父级(即 dfs)。当您从 -1 转到 a 时,您会将 b 作为父项传递。在 a 处,您将忽略 b 的邻接列表中的父 a

所以你的 b 看起来像像这样:

dfs

问题 3

您的实现的性能不是最佳的(这会导致 SPOJ 超时)。您可以通过在 int dfs(int n, vector<int> v[], int a, int parent) { vis[n] = true; for (int i = 0; i < v[n].size(); i++) { if (v[n][i] == parent) { continue; } if (v[n][i] == a) return 1; if (vis[v[n][i]] == false) { if (dfs(v[n][i], v, a, n) == 1) { return 1; } } } return 0; } 函数中每次循环检查后不清除 vis 数组来改进这一点。相反,重用 main 数组并仅对尚未访问过的顶点执行循环检查:

vis

这足以让 SPOJ 接受您的代码。

但是,您可以进一步优化代码中的连通性检查:而不是在循环检查后清除 for (i = 1; i <= n; i++) { if (vis[i] == false) { if (dfs(i, v, i, -1)) { cout << "NO" << endl; return 0; } } } memset(vis, false, sizeof(vis)); 数组并为每个顶点执行另一系列 vis 直到发现第二个组件,您可以重用 dfs 数组,并在循环检查后简单地检查是否有任何未访问的顶点。这将允许您完全省略第二个 vis 系列。