有向グラフが非巡回であるかどうかを確認するにはどうすればよいですか?そして、アルゴリズムはどのように呼び出されますか?参考にしていただければ幸いです。
有向グラフが非巡回であるかどうかを確認するにはどうすればよいですか?そして、アルゴリズムはどのように呼び出されますか?参考にしていただければ幸いです。
回答:
単純な深さ優先探索を実行するだけでは、サイクルを見つけるのに十分ではありません。サイクルが存在しなくても、DFSでノードに複数回アクセスすることができます。開始する場所によっては、グラフ全体にアクセスできない場合もあります。
グラフの連結成分のサイクルは、次のように確認できます。発信エッジのみを持つノードを見つけます。そのようなノードがない場合は、サイクルがあります。そのノードでDFSを開始します。各エッジをトラバースするときは、エッジがすでにスタック上にあるノードを指しているかどうかを確認してください。これは、サイクルの存在を示しています。そのようなエッジが見つからない場合、その連結成分にサイクルはありません。
Rutger Prinsが指摘しているように、グラフが接続されていない場合は、接続されている各コンポーネントで検索を繰り返す必要があります。
参考までに、タージャンの強連結成分アルゴリズムは密接に関連しています。また、サイクルが存在するかどうかを報告するだけでなく、サイクルを見つけるのにも役立ちます。
本Introduction to Algorithms
(第2版)の補題22.11は、次のように述べています。
有向グラフGは、Gの深さ優先探索で後縁が得られない場合に限り、非巡回です。
Solution1:サイクルをチェックするカーンアルゴリズム。主なアイデア:度数がゼロのノードがキューに追加されるキューを維持します。次に、キューが空になるまでノードを1つずつ剥がします。ノードのインエッジが存在するかどうかを確認します。
解決策2:強連結成分をチェックするTarjanアルゴリズム。
Solution3:DFS。整数配列を使用して、ノードの現在のステータスにタグを付けます。つまり、0-このノードが以前にアクセスされたことがないことを意味します。-1-このノードが訪問され、その子ノードが訪問されていることを意味します。1-このノードにアクセスし、完了したことを意味します。したがって、DFSの実行中にノードのステータスが-1の場合は、サイクルが存在している必要があることを意味します。
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)
m = O(n^2)
完全グラフには正確にm=n^2
エッジがあるからです。つまり、O(n+m) = O(n + n^2) = O(n^2)
です。
これが古いトピックであることは知っていますが、将来の検索者のために、私が作成したC#実装があります(最も効率的であるとは言えません!)。これは、単純な整数を使用して各ノードを識別するように設計されています。ノードオブジェクトが適切にハッシュされ、等しい場合は、好きなように装飾できます。
非常に深いグラフの場合、これは各ノードの深さでハッシュセットを作成するため、オーバーヘッドが高くなる可能性があります(それらは広範囲にわたって破壊されます)。
検索するノードとそのノードへのパスを入力します。
特定のノードの下のサイクルをチェックするときは、空のハッシュセットと一緒にそのノードを渡すだけです。
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;
}
グラフにサイクルがあるかどうかを確認するための迅速なコードは次のとおりです。
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アルゴリズム。これにより、ノードに対してdfsを実行するたびにマーカー配列内の対応するアイテムをtrueに設定します。これにより、既にアクセスしたノードが検出されるたびに、マーカー配列内の対応するアイテムがtrueであるかどうかを確認し、trueの場合は、それ自体を許可するノードの1つをチェックします(したがって、サイクル)、そしてトリックは、ノードのdfsが戻るたびに、対応するマーカーをfalseに戻すことです。これにより、別のルートから再度アクセスした場合でも、だまされません。
これが、ピールオフリーフノードアルゴリズムのルビー実装です。
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
Googleのインタビューでこの質問がありました。
トポロジカルソートを試みることができます。これはO(V + E)です。ここで、Vは頂点の数、Eはエッジの数です。有向グラフは、これが実行できる場合に限り、非循環です。
リーフノードがなくなるまで再帰的に削除します。ノードが複数残っている場合は、サイクルがあります。私が間違えない限り、これはO(V ^ 2 + VE)です。
ただし、効率的な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);
}
あなたはここで私の答えからの発見サイクルの反転を使用することができますhttps://stackoverflow.com/a/60196714/1763149
def is_acyclic(graph):
return not has_cycle(graph)