web-dev-qa-db-ja.com

DFSを使用したグラフのサイクルの検出:2つの異なるアプローチとその違い

グラフは隣接リストとして表されることに注意してください。

グラフ内のサイクルを見つけるための2つのアプローチについて聞いたことがあります。

  1. ブール値の配列を保持して、以前にノードにアクセスしたかどうかを追跡します。 (すでに行ったノードにヒットすることなく)移動するために新しいノードを使い果たした場合は、バックトラックして別のブランチを試してください。

  2. CormenのCLRSまたはSkienaからのもの:無向グラフの深さ優先検索には、ツリーとバックの2種類のエッジがあります。バックエッジが存在する場合にのみ、グラフにサイクルがあります。

誰かがグラフの後ろの端と、上記の2つの方法の違いを説明できますか。

ありがとう。

更新:両方のケースでサイクルを検出するコードを次に示します。グラフは、簡単にするためにすべてのグラフノードを一意の番号として表す単純なクラスです。各ノードには隣接する隣接ノードがあります(g.getAdjacentNodes(int))。

public class Graph {

  private int[][] nodes; // all nodes; e.g. int[][] nodes = {{1,2,3}, {3,2,1,5,6}...};

  public int[] getAdjacentNodes(int v) {
    return nodes[v];
  }

  // number of vertices in a graph
  public int vSize() {
    return nodes.length;
  }

}

無向グラフのサイクルを検出するJavaコード:

    public class DFSCycle {

    private boolean marked[];
    private int s;
    private Graph g;
    private boolean hasCycle;

    // s - starting node
    public DFSCycle(Graph g, int s) {
        this.g = g;
        this.s = s;
        marked = new boolean[g.vSize()];
        findCycle(g,s,s);
    }

    public boolean hasCycle() {
        return hasCycle;
    }

    public void findCycle(Graph g, int v, int u) {

        marked[v] = true;

        for (int w : g.getAdjacentNodes(v)) {
            if(!marked[w]) {
                marked[w] = true;
                findCycle(g,w,v);
            } else if (v != u) {
                hasCycle = true;
                return;
            }
        }

    }  
}

有向グラフのサイクルを検出するJavaコード:

public class DFSDirectedCycle {

    private boolean marked[];
    private boolean onStack[];
    private int s;
    private Graph g;
    private boolean hasCycle;

    public DFSDirectedCycle(Graph g, int s) {
        this.s = s
        this.g = g;
        marked = new boolean[g.vSize()];
        onStack = new boolean[g.vSize()];
        findCycle(g,s);
    }

    public boolean hasCycle() {
        return hasCycle;
    }

    public void findCycle(Graph g, int v) {

        marked[v] = true;
        onStack[v] = true;

        for (int w : g.adjacentNodes(v)) {
            if(!marked[w]) {
                findCycle(g,w);
            } else if (onStack[w]) {
                hasCycle = true;
                return;
            }
        }

        onStack[v] = false;
    }
}
36
Ivan Voroshilin

質問への回答:

バックエッジが存在する場合にのみ、グラフにサイクルがあります。バックエッジは、ノードからそれ自体(セルフループ)またはサイクルを形成するDFSによって生成されたツリーの祖先の1つであるエッジです。

上記の両方のアプローチは実際には同じことを意味します。ただし、この方法は無向グラフにのみ適用できます。

このアルゴリズムが有向グラフで機能しない理由は、有向グラフでは、同じ頂点への2つの異なるパスが循環しないためです。たとえば、A-> B、B-> C、A-> C-サイクルを作成しないでください。一方、無向の場合はA--B、B--C、C--Aが作成されます。

無向グラフのサイクルを見つける

無向グラフは、深さ優先検索(DFS)が既に訪れた頂点(バックエッジ)を指すエッジを検出した場合にのみサイクルを持ちます。

有向グラフのサイクルを見つける

訪れた頂点に加えて、DFSトラバーサルの関数の再帰スタックに現在ある頂点を追跡する必要があります。すでに再帰スタックにある頂点に到達すると、ツリー内にサイクルがあります。

更新:作業コードは上記の質問セクションにあります。

48
Ivan Voroshilin

完了のために、DFSを使用して有向グラフでサイクルを見つけることができます( wikipedia から):

 L ← Empty list that will contain the sorted nodes
while there are unmarked nodes do
    select an unmarked node n
    visit(n) 
function visit(node n)
    if n has a temporary mark then stop (not a DAG)
    if n is not marked (i.e. has not been visited yet) then
        mark n temporarily
        for each node m with an Edge from n to m do
            visit(m)
        mark n permanently
        unmark n temporarily
        add n to head of L
10
Igor Zelaya

これは、特定の無向グラフが接続/循環しているかどうかを調べるために、DFSに基づいてCで記述したコードです。最後にサンプル出力があります。役に立てば幸いです:)

#include<stdio.h>
#include<stdlib.h>

/****Global Variables****/
int A[20][20],visited[20],count=0,n;
int seq[20],connected=1,acyclic=1;

/****DFS Function Declaration****/
void DFS();

/****DFSearch Function Declaration****/
void DFSearch(int cur);

/****Main Function****/
int main() 
   {    
    int i,j;

    printf("\nEnter no of Vertices: ");
    scanf("%d",&n);

    printf("\nEnter the Adjacency Matrix(1/0):\n");
    for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
        scanf("%d",&A[i][j]);

    printf("\nThe Depth First Search Traversal:\n");

    DFS();

    for(i=1;i<=n;i++)
        printf("%c,%d\t",'a'+seq[i]-1,i);

    if(connected && acyclic)    printf("\n\nIt is a Connected, Acyclic Graph!");
    if(!connected && acyclic)   printf("\n\nIt is a Not-Connected, Acyclic Graph!");
    if(connected && !acyclic)   printf("\n\nGraph is a Connected, Cyclic Graph!");
    if(!connected && !acyclic)  printf("\n\nIt is a Not-Connected, Cyclic Graph!");

    printf("\n\n");
    return 0;
   }

/****DFS Function Definition****/
void DFS()
    { 
    int i;
    for(i=1;i<=n;i++)
        if(!visited[i])
          {
        if(i>1) connected=0;
        DFSearch(i);    
              } 
    }

/****DFSearch Function Definition****/
void DFSearch(int cur) 
    {
    int i,j;
    visited[cur]=++count;

        seq[count]=cur; 
        for(i=1;i<count-1;i++)
                if(A[cur][seq[i]]) 
                   acyclic=0;

    for(i=1;i<=n;i++)
        if(A[cur][i] && !visited[i])
           DFSearch(i);

    }

サンプル出力:

[email protected]:~/Desktop$ gcc BFS.c

[email protected]:~/Desktop$ ./a.out
************************************

Enter no of Vertices: 10

Enter the Adjacency Matrix(1/0):

0 0 1 1 1 0 0 0 0 0 
0 0 0 0 1 0 0 0 0 0 
0 0 0 1 0 1 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 1 0 0 1 0 0 0 0 0 
0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 1 0 
0 0 0 0 0 0 0 0 0 1 
0 0 0 0 0 0 1 0 0 0 

The Depdth First Search Traversal:
a,1 c,2 d,3 f,4 b,5 e,6 g,7 h,8 i,9 j,10    

It is a Not-Connected, Cyclic Graph!


[email protected]:~/Desktop$ ./a.out
************************************

Enter no of Vertices: 4

Enter the Adjacency Matrix(1/0):
0 0 1 1
0 0 1 0
1 1 0 0
0 0 0 1

The Depth First Search Traversal:
a,1 c,2 b,3 d,4 

It is a Connected, Acyclic Graph!


[email protected]:~/Desktop$ ./a.out
************************************

Enter no of Vertices: 5

Enter the Adjacency Matrix(1/0):
0 0 0 1 0
0 0 0 1 0
0 0 0 0 1
1 1 0 0 0 
0 0 1 0 0

The Depth First Search Traversal:
a,1 d,2 b,3 c,4 e,5 

It is a Not-Connected, Acyclic Graph!

*/
1
Majid NK