【问题标题】:Cycle in an undirected graph using dfs使用 dfs 在无向图中循环
【发布时间】:2018-08-19 18:17:57
【问题描述】:

请告诉我为什么这段代码无法分析是否存在循环 无向图?代码如下。这是关于 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;
}

【问题讨论】:

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


    【解决方案1】:

    问题 1

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

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

      for (i = 0; i < m; i++) {
        cin >> a >> b;
        v[a].push_back(b);
        v[b].push_back(a);
      }
    

    问题 2

    您的 dfs 方法行为不正确:

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

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

    if (dfs(v[n][i], v, a, n) == 1) {
      return 1;
    }
    

    应用此修复后,另一个问题被揭示:当dfs 方法在相邻顶点列表中发现目标顶点时,它不排除到达当前顶点的边。因此,如果您有一个图形1 &lt;-&gt; 2,并且您从1 开始dfs,目标顶点为1,您将检查包含2 的相邻顶点列表。遍历从12 的边,您将检查包含1 的相邻顶点列表。然后,您的方法将报告已找到目标顶点,但这无效,因为它仅在您来的边缘找到。

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

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

    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;
    }
    

    问题 3

    您的实施性能不是最佳的(这会导致 SPOJ 超时)。您可以通过在 main 函数中的每次循环检查后不清除 vis 数组来改善这一点。相反,重用vis数组,只对尚未访问的顶点执行循环检查:

      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));
    

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

    但是,您可以在代码中进一步优化连通性检查:在循环检查之后清除 vis 数组并为每个顶点执行另一系列 dfs 直到发现第二个组件之前,您可以重用vis 数组并简单地检查循环检查后是否有任何未访问的顶点。这将允许您完全省略第二个dfs 系列。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-27
      • 1970-01-01
      • 2016-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多