当前位置: 首页 > 知识库问答 >
问题:

使用dfs的无向图中的圈

上官恩
2023-03-14

请告诉我为什么这段代码不能分析无向图中是否有循环?代码如下。这是Spoj上的PT07Y问题。我正在做的是取一个节点并执行dfs。当执行dfs时,如果我访问同一个节点,那么我说有一个循环。从一个节点执行dfs后,我使访问的数组为假,并为下一个节点执行,直到我得到一个周期或所有节点都被访问。

#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个答案

景震博
2023-03-14

图形被指定为无向,但输入格式将只指定每条边一次(对于其中一个方向)。因此,当您从输入中读取类似于12的行时,您必须通过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方法中进行递归调用dfs(v[n][i], v, a);,并且该调用返回1时,当前的dfs调用将忽略此结果。如果当前dfs调用没有在当前邻接列表中找到目标顶点,那么它将完成循环的并返回0

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

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

应用此修复程序后,会发现另一个问题:当dfs方法在相邻顶点列表中发现目标顶点时,它不排除当前顶点所在的边缘。所以如果你有一个图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;
}

实现的性能不是最佳的(这会导致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系列。

 类似资料:
  • 因此,我为DFS编写了以下代码: 现在,我读到,在一个无向图中,如果当DFS时,它再次返回到同一个顶点,有一个循环。所以我做的是这样,, 但是,我的检查周期函数返回true,即使他们没有周期。那是为什么呢?我的功能有问题吗?没有执行问题,否则我早就调试了,但他们似乎在我的逻辑中有问题。

  • 假设我有一个有V节点和E边的无向图。如果我用邻接列表表示图形,如果我有x和y之间边的表示,我还必须在邻接列表中有y和x之间边的表示。 我知道有向图的DFS具有VE复杂性。对于无向图,它没有v 2*e复杂性,因为您访问每个边2次?抱歉,如果这是一个愚蠢的问题...我真的很想明白这个想法。谢谢你,

  • 我很难理解Kosaraju寻找有向图的强连通成分的算法。以下是我笔记本上的内容(我是学生:D): 从任意顶点开始(用#1标记)并执行DFS。如果无法继续,请使用#2标记上次访问的顶点,然后启动另一个DFS(跳过已标记的顶点),依此类推。 变换图形的位置。 按相反顺序执行DFS从每个顶点开始,在每个DFS之后结束访问的顶点属于同一个SCC 我举了一个例子: 从E开始的第一步后,标签为: E G K

  • 你有一辆2005年本田雅阁,油箱里还剩50英里(最大重量)。在50英里半径范围内,你可以访问哪些麦当劳的位置(图节点)?这是我的问题。 如果你有一个加权有向无环图,你如何找到在给定的权限制内可以访问的所有节点? 我知道Dijkstra的算法,但我似乎找不到任何关于它在最小路径问题之外的用途的文档。在我的例子中,我们没有特别想结束的节点,我们只想在不超过最大权重的情况下尽可能多地结束。似乎您应该能够

  • 本文会围绕算法中DFS求有向图或无向图两点间所有路径,先讲解DFS以及有向图或无向图的意思。 有向图在图中的边是有方向的,表现出来就是有个箭头指示方向,节点只能单向通信或传递消息,相当于单行道,无向图边没方向是双向的,边连接的两个节点有通路可以双向通信,类似于双行道。 无向图,边没有方向的图称为无向图。邻接矩阵则是对称的,且只有0和1,因为没有方向的区别后,要么有边,要么没边。 DFS作为搜索算法

  • 我不太确定这是不是解决办法,有人能帮我弄清楚吗? 谢谢!