web-dev-qa-db-ja.com

有向グラフが非循環であるかどうかを確認するにはどうすればよいですか?

有向グラフが非循環であるかどうかを確認するにはどうすればよいですか?そして、アルゴリズムはどのように呼び出されますか?参考にしていただければ幸いです。

78
nes1983

グラフをトポロジカルに並べ替える を試みます。できなければ、サイクルがあります。

88
FryGuy

単純な深さ優先検索を行うことは、notサイクルを見つけるのに十分です。サイクルが存在しなくても、DFSでノードに複数回アクセスすることが可能です。開始場所によっては、グラフ全体にアクセスできない場合もあります。

次のように、グラフの接続コンポーネントのサイクルを確認できます。外向きのエッジのみを持つノードを見つけます。そのようなノードがない場合、サイクルがあります。そのノードでDFSを開始します。各Edgeをトラバースするとき、Edgeが既にスタックにあるノードを指しているかどうかを確認します。これは、サイクルの存在を示しています。そのようなEdgeが見つからない場合、その接続されたコンポーネントにはサイクルはありません。

Rutger Prinsが指摘しているように、グラフが接続されていない場合は、接続されている各コンポーネントで検索を繰り返す必要があります。

参考として、 タージャンの強連結成分アルゴリズム は密接に関連しています。また、サイクルが存在するかどうかを報告するだけでなく、サイクルを見つけるのにも役立ちます。

32
Jay Conrod

本の補題22.11 Introduction to Algorithms(第2版)の状態:

有向グラフGは、Gの深さ優先探索で後端が得られない場合にのみ非周期的です。

13

Solution1サイクルをチェックするカーンアルゴリズム。主なアイデア:ゼロ度のノードがキューに追加されるキューを維持します。次に、キューが空になるまでノードを1つずつ剥離します。ノードのインエッジが存在するかどうかを確認します。

Solution2Tarjanアルゴリズム Strong connected componentを確認します。

Solution[〜#〜] dfs [〜#〜]。整数配列を使用して、ノードの現在のステータスにタグを付けます:0-このノードは以前に訪れたことがないことを意味します。 -1-このノードが訪問され、その子ノードが訪問されていることを意味します。 1-このノードが訪問され、完了したことを意味します。したがって、DFSの実行中にノードのステータスが-1である場合、サイクルが存在している必要があることを意味します。

8
Chris Su

DFSを実行中にバックエッジがないはずです。DFSを実行中に既にアクセスしたノードを追跡し、現在のノードと既存のノードの間にエッジが発生した場合、グラフにサイクルがあります。

1
santhosh

Swift=グラフにサイクルがあるかどうかを確認するコード:

func isCyclic(G : Dictionary<Int,Array<Int>>,root : Int , var visited : Array<Bool>,var breadCrumb : Array<Bool>)-> Bool
{

    if(breadCrumb[root] == true)
    {
        return true;
    }

    if(visited[root] == true)
    {
        return false;
    }

    visited[root] = true;

    breadCrumb[root] = true;

    if(G[root] != nil)
    {
        for child : Int in G[root]!
        {
            if(isCyclic(G,root : child,visited : visited,breadCrumb : breadCrumb))
            {
                return true;
            }
        }
    }

    breadCrumb[root] = false;
    return false;
}


let G = [0:[1,2,3],1:[4,5,6],2:[3,7,6],3:[5,7,8],5:[2]];

var visited = [false,false,false,false,false,false,false,false,false];
var breadCrumb = [false,false,false,false,false,false,false,false,false];




var isthereCycles = isCyclic(G,root : 0, visited : visited, breadCrumb : breadCrumb)

アイデアは次のとおりです:訪問したノードを追跡する配列を備えた通常のdfsアルゴリズム、および現在のノードにつながったノードのマーカーとして機能する追加の配列。マーカー配列内の対応するアイテムをtrueに設定します。これにより、既に訪れたノードが検出されたときに、マーカー配列内の対応するアイテムがtrueであるかどうかを確認します。サイクル)、およびノー​​ドのdfsが返されるたびに対応するマーカーをfalseに戻すので、別のルートから再度アクセスした場合にだまされません.

1
m.eldehairy

これは古いトピックであることは知っていますが、将来の検索者にとっては、私が作成したC#実装です(最も効率的だという主張はありません!)。これは、単純な整数を使用して各ノードを識別するように設計されています。ノードオブジェクトのハッシュとイコールが適切であれば、好きなように装飾できます。

非常に深いグラフの場合、各ノードに深さのあるハッシュセットを作成するため、オーバーヘッドが大きくなる可能性があります(それらは幅を超えて破棄されます)。

検索するノードとそのノードへのパスを入力します。

  • 単一のルートノードを持つグラフの場合、そのノードと空のハッシュセットを送信します
  • 複数のルートノードを持つグラフの場合、これをそれらのノードのforeachでラップし、反復ごとに新しい空のハッシュセットを渡します
  • 特定のノードより下のサイクルをチェックするときは、空のハッシュセットとともにそのノードを渡すだけです

    private bool FindCycle(int node, HashSet<int> path)
    {
    
        if (path.Contains(node))
            return true;
    
        var extendedPath = new HashSet<int>(path) {node};
    
        foreach (var child in GetChildren(node))
        {
            if (FindCycle(child, extendedPath))
                return true;
        }
    
        return false;
    }
    
1
Matthew

ShuggyCoUkが提供するソリューションは、すべてのノードをチェックしない可能性があるため、不完全です。


def isDAG(nodes V):
    while there is an unvisited node v in V:
        bool cycleFound = dfs(v)
        if cyclefound:
            return false
    return true

これには時間の複雑さO(n + m)またはO(n ^ 2)があります

1
Rutger Prins

Googleのインタビューでこの質問がありました。

トポロジカルソート

トポロジカルに並べ替えることができます。これはO(V + E)です。Vは頂点の数で、Eはエッジの数です。有向グラフは、これが可能な場合にのみ非周期的です。

再帰的な葉の除去

残りがなくなるまで再帰的にリーフノードを削除し、1つ以上のノードが残っている場合はサイクルを取得します。誤解がない限り、これはO(V ^ 2 + VE)です。

DFSスタイル〜O(n + m)

ただし、効率的なDFS風のアルゴリズム、最悪の場合O(V + E)は次のとおりです。

function isAcyclic (root) {
    const previous = new Set();

    function DFS (node) {
        previous.add(node);

        let isAcyclic = true;
        for (let child of children) {
            if (previous.has(node) || DFS(child)) {
                isAcyclic = false;
                break;
            }
        }

        previous.delete(node);

        return isAcyclic;
    }

    return DFS(root);
}
0
Tom Golden

ここに私のRuby peel off leaf node algorithm の実装があります。

def detect_cycles(initial_graph, number_of_iterations=-1)
    # If we keep peeling off leaf nodes, one of two things will happen
    # A) We will eventually peel off all nodes: The graph is acyclic.
    # B) We will get to a point where there is no leaf, yet the graph is not empty: The graph is cyclic.
    graph = initial_graph
    iteration = 0
    loop do
        iteration += 1
        if number_of_iterations > 0 && iteration > number_of_iterations
            raise "prevented infinite loop"
        end

        if graph.nodes.empty?
            #puts "the graph is without cycles"
            return false
        end

        leaf_nodes = graph.nodes.select { |node| node.leaving_edges.empty? }

        if leaf_nodes.empty?
            #puts "the graph contain cycles"
            return true
        end

        nodes2 = graph.nodes.reject { |node| leaf_nodes.member?(node) }
        edges2 = graph.edges.reject { |Edge| leaf_nodes.member?(Edge.destination) }
        graph = Graph.new(nodes2, edges2)
    end
    raise "should not happen"
end
0
neoneye