簡體   English   中英

使用 dfs 在無向圖中循環

[英]Cycle in an undirected graph using dfs

請告訴我為什么這段代碼無法分析無向圖中是否存在循環? 代碼如下。 這是 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

該圖被指定為無向的,但輸入格式將只指定每條邊一次(對於其中一個方向)。 因此,當您從輸入中讀取像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(v[n][i], v, a); 在您的dfs方法中,並且該調用返回1 ,當前dfs調用將忽略此結果。 如果當前的dfs調用沒有在當前的鄰接表中找到目標頂點,那么它將完成for循環並返回0

要解決此問題,您必須確保如果遞歸dfs調用返回1 ,則for循環中止並且當前dfs調用也返回1 ,例如:

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

應用此修復程序后,會出現另一個問題:當dfs方法在相鄰頂點列表中發現目標頂點時,它不排除到達當前頂點的邊。 因此,如果您有一個圖1 <-> 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系列。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM