有向グラフですべてのサイクルを見つける


198

特定のノードから/への有向グラフですべてのサイクルを見つける(反復する)にはどうすればよいですか?

たとえば、私はこのようなものが欲しい:

A->B->A
A->B->C->A

ただし、B-> C-> B


1
宿題?me.utexas.edu/~bard/IP/Handouts/cycles.pdf有効な質問ではないというわけではありません:)
ShuggyCoUk

5
これは少なくともNPハードであることに注意してください。おそらくPSPACEは、私はそれについて考える必要があるだろうが、それはあまりにも早く複雑性理論B-)のための朝のだ
ブライアンPostow

2
入力グラフにv頂点とeエッジがある場合、2 ^(e-v +1)-1の異なるサイクルがあります(すべて単純なサイクルであるとは限りません)。それはかなりたくさんです-あなたはそれらすべてを明示的に書きたくないかもしれません。また、出力サイズは指数関数的であるため、アルゴリズムの複雑さを多項式にすることはできません。この質問に対する答えはまだないと思います。
CygnusX1、2011年

1
:私のための私の最高のオプションは、このでしたpersonal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/...
Melsi

回答:


105

このページを検索で見つけました。サイクルは強く接続されたコンポーネントと同じではないため、検索を続け、最後に有向グラフのすべての(基本)サイクルをリストする効率的なアルゴリズムを見つけました。それはドナルドB.ジョンソンからのものであり、論文は次のリンクにあります。

http://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF

Java実装は次の場所にあります。

http://normalisiert.de/code/java/elementaryCycles.zip

Mathematicaのジョンソンのアルゴリズムのデモンストレーションを見ることができ、ここで、実装が右(からダウンロードすることができ、「ダウンロード作者コード」)。

注:実際には、この問題には多くのアルゴリズムがあります。それらのいくつかはこの記事にリストされています:

http://dx.doi.org/10.1137/0205007

記事によると、ジョンソンのアルゴリズムは最速のものです。


1
私はそれを紙から実装するのはそのような面倒であると私は思います、そして結局この盛大さはまだTarjanの実装を必要とします。そして、Javaコードも恐ろしいです。:(
Gleno

7
@Glenoさて、もしあなたがTarjanを使って残りのグラフを実装する代わりにグラフ内のすべてのサイクルを見つけることができるということを意味しているなら、あなたは間違っています。ここでは、強く接続されたコンポーネントとすべてのサイクルの違いを確認できます(cdとghのサイクルはTarjanのalgによって返されません)(@ batbrat混乱の答えもここに隠されています:すべての可能なサイクルはTarjanによって返されませんalgなので、その複雑さは指数関数よりも小さくなる可能性があります)。Javaコードはもっと良いかもしれませんが、それは私が紙から実装する労力を節約しました。
eminsenay

4
この回答は、選択した回答よりもはるかに優れています。強く接続されたコンポーネントからすべての単純なサイクルを取得する方法を理解しようとする間、私はかなり苦労しました。これは重要なことです。ジョンソンの論文には優れたアルゴリズムが含まれていますが、理解するのが少し難しいです。私はJavaの実装を調べて、Matlabで自分で実装しました。コードはgist.github.com/1260153で入手できます。
codehippo 2011年

5
@moteutsch:何か足りないかもしれませんが、Johnsonの論文(および他のソース)によると、(開始/終了を除いて)頂点が2回以上表示されない場合、サイクルは初歩的です。その定義では、A->B->C->A初等でもないですか?
psmears 2014

9
このためにpythonを使用している人への注意:Johnsonアルゴリズムはsimple_cyclenetworkxのように実装されています。
Joel

35

ここでは、バックトラッキングを使用した深さ優先検索が機能します。ブール値の配列を保持して、以前にノードにアクセスしたかどうかを追跡します。新しいノードが足りなくなって(すでに行ったノードにヒットすることなく)進む場合は、バックトラックして別のブランチを試してください。

グラフを表す隣接リストがある場合、DFSは簡単に実装できます。たとえば、adj [A] = {B、C}は、BとCがAの子であることを示します。

たとえば、以下の擬似コード。「開始」は、開始するノードです。

dfs(adj,node,visited):  
  if (visited[node]):  
    if (node == start):  
      "found a path"  
    return;  
  visited[node]=YES;  
  for child in adj[node]:  
    dfs(adj,child,visited)
  visited[node]=NO;

上記の関数を開始ノードで呼び出します。

visited = {}
dfs(adj,start,visited)

2
ありがとう。おそらく最適ではないかもしれませんが、理解するのは簡単であり、妥当な時間の複雑さがあるため、ここで言及した他のいくつかよりもこのアプローチを好みます。
redcalx 2011

1
これはどのようにすべてのサイクルを見つけるのですか?
ブレインストーム

3
if (node == start): - node and start最初の電話の内容
ブレインストーム

2
@ user1988876これは、指定された頂点()を含むすべてのサイクルを見つけるようstartです。その頂点から始まり、再びその頂点に戻るまでDFSを実行します。その後、サイクルが見つかったことを認識します。しかし、それは実際にはサイクルを出力するのではなく、それらの数を出力するだけです(しかし、そのように変更することは、それほど難しくないはずです)。
ベルンハルト・バーカー

1
@ user1988876ええと、「見つかったパス」を、見つかったサイクル数に等しい回数だけ出力します(これは簡単にカウントに置き換えることができます)。はい、それはからのみサイクルを検出しstartます。のために各訪問済みフラグがクリアされるため、訪問済みフラグをクリアする必要はありませんvisited[node]=NO;。ただし、サイクルA->B->C->Aがある場合は3回検出されるstartことに注意してください。これを防ぐための1つのアイデアはstart、ある時点でノードであったすべてのノードが設定されている別の訪問済み配列を持つことであり、それらを再度訪問することはありません。
Bernhard Barker

23

まず第一に、あなたは本当に文字通りすべてのサイクルを見つけようとは思わないでしょう。なぜなら、1があると、それらの数は無限にあるからです。たとえば、ABA、ABABAなど。または、2サイクルを8のようなサイクルなどに結合することもできます。意味のあるアプローチは、いわゆる単純なサイクルをすべて探すことです。開始/終了ポイント。次に、必要に応じて、単純なサイクルの組み合わせを生成できます。

有向グラフ内のすべての単純なサイクルを見つけるためのベースラインアルゴリズムの1つは、次のとおりです。グラフ内のすべての単純なパス(交差しないもの)の深さ優先トラバーサルを実行します。現在のノードがスタック上に後続ノードを持つたびに、単純なサイクルが発見されます。これは、識別された後続ノードで始まり、スタックの最上部で終わるスタック上の要素で構成されます。すべての単純なパスの深さ優先トラバーサルは深さ優先検索と似ていますが、現在スタック上にあるノード以外の訪問済みノードをストップポイントとしてマーク/記録しません。

上記の総当たりアルゴリズムはひどく非効率的であり、それに加えてサイクルの複数のコピーが生成されます。ただし、パフォーマンスを改善し、サイクルの重複を回避するためにさまざまな拡張機能を適用する複数の実用的なアルゴリズムの開始点です。これらのアルゴリズムが教科書やWebですぐに利用できないことを少し前に知って驚いた。そこで、私はいくつかの調査を行い、オープンソースJavaライブラリの無向グラフで、このようなアルゴリズムを4つとサイクルのアルゴリズムを1つ実装しました:http : //code.google.com/p/niographs/

ところで、無向グラフについて述べたので、それらのアルゴリズムは異なります。スパニングツリーを構築すると、ツリーの一部ではないすべてのエッジが、ツリー内のいくつかのエッジとともに単純なサイクルを形成します。この方法で見つかったサイクルは、いわゆるサイクルベースを形成します。次に、2つ以上の異なる基本サイクルを組み合わせることにより、すべての単純なサイクルを見つけることができます。詳細については、たとえばこれを参照してください:http : //dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf


一例として、どのように使用することjgraphtで使用されているhttp://code.google.com/p/niographs/から例を取ることができますgithub.com/jgrapht/jgrapht/wiki/DirectedGraphDemo
Vishrant

19

この問題を解決するために私が見つけた最も簡単な選択は、というpython libを使用することでしたnetworkx

この質問のベストアンサーに記載されているジョンソンのアルゴリズムを実装していますが、実行は非常に簡単です。

つまり、次のものが必要です。

import networkx as nx
import matplotlib.pyplot as plt

# Create Directed Graph
G=nx.DiGraph()

# Add a list of nodes:
G.add_nodes_from(["a","b","c","d","e"])

# Add a list of edges:
G.add_edges_from([("a","b"),("b","c"), ("c","a"), ("b","d"), ("d","e"), ("e","a")])

#Return a list of cycles described as a list o nodes
list(nx.simple_cycles(G))

回答: [['a'、 'b'、 'd'、 'e']、['a'、 'b'、 'c']]

ここに画像の説明を入力してください


1
またnetworkxグラフに辞書をcnovertすることができますnx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
ルークマイル

開始頂点を指定するにはどうすればよいですか?
ナンセンス

5

明確にするために:

  1. 強く接続されたコンポーネントは、グラフ内のすべての可能なサイクルではなく、少なくとも1つのサイクルを持つすべてのサブグラフを見つけます。たとえば、強く接続されたすべてのコンポーネントを取得し、それらを1つにまとめて1つのノード(つまり、コンポーネントごとのノード)に縮小/グループ化/マージすると、サイクルのないツリー(実際にはDAG)が得られます。各コンポーネント(基本的には少なくとも1つのサイクルを持つサブグラフ)は、内部にさらに多くの可能なサイクルを含むことができるため、SCCはすべての可能なサイクルを見つけるのではなく、少なくとも1つのサイクルを持つすべての可能なグループを見つけます。それらの場合、グラフにはサイクルがありません。

  2. 他の人が述べたように、グラフ内のすべての単純なサイクルを見つけるには、ジョンソンのアルゴリズムが候補です。


3

私はこれをインタビューの質問として一度与えられました、私はこれがあなたに起こったと思います、そしてあなたは助けのためにここに来ています。問題を3つの質問に分ければ、簡単になります。

  1. 次の有効なルートをどのように決定しますか
  2. ポイントが使用されたかどうかをどのように判断しますか
  3. どのようにして同じ点をまたぐのを避けますか

問題1)ルートの結果を反復する方法を提供するためにイテレーターパターンを使用します。次のルートを取得するためのロジックを配置するのに適した場所は、おそらくイテレーターの「moveNext」です。有効なルートを見つけるには、データ構造によって異なります。私にとっては、有効なルートの可能性でいっぱいのSQLテーブルだったので、ソースを指定して有効な宛先を取得するためにクエリを作成する必要がありました。

問題2)各ノードを見つけたら、それらをコレクションにプッシュします。つまり、構築中のコレクションにその場で問い合わせることで、ポイントを非常に簡単に「倍加」しているかどうかを確認できます。

問題3)いずれかの時点で倍増していることがわかった場合は、コレクションから物をポップして「バックアップ」できます。その後、その時点からもう一度「前進」してみてください。

ハック:SQL Server 2008を使用している場合、データをツリーで構造化する場合、これをすばやく解決するために使用できる新しい「階層」のものがいくつかあります。


3

バックエッジを持つDFSベースのバリアントは実際にサイクルを検出しますが、多くの場合、それは最小サイクルではありません。一般に、DFSはサイクルがあることを示すフラグを提供しますが、実際にサイクルを見つけるには十分ではありません。たとえば、2つのエッジを共有する5つの異なるサイクルを想像してください。DFSだけを使用してサイクルを特定する簡単な方法はありません(バックトラックバリアントを含む)。

ジョンソンのアルゴリズムは、確かにすべてのユニークな単純なサイクルを提供し、時間と空間の複雑さも優れています。

しかし、MINIMALサイクルだけを見つけたい場合(つまり、任意の頂点を通過するサイクルが1つ以上ある可能性があり、最小のサイクルを見つけることに関心がある場合)、グラフがそれほど大きくない場合は、以下の簡単な方法を使用してみてください。それは非常にシンプルですが、ジョンソンのものと比較してかなり遅いです。

だから、のいずれかの絶対 MINIMALサイクルを見つけるための最も簡単な方法は、隣接行列を使用して、すべての頂点の間の最小のパスを見つけるために、フロイドのアルゴリズムを使用することです。このアルゴリズムは、ジョンソンのアルゴリズムほど最適ではありませんが、非常に単純であり、その内部ループは非常にタイトなので、小さいグラフ(<= 50-100ノード)の場合は、それを使用することが絶対に意味があります。時間の複雑さはO(n ^ 3)、スペースの複雑さは親トラッキングを使用する場合はO(n ^ 2)、使用しない場合はO(1)です。まず、サイクルがあるかどうかの質問に対する答えを見つけましょう。アルゴリズムは非常に単純です。以下はScalaのスニペットです。

  val NO_EDGE = Integer.MAX_VALUE / 2

  def shortestPath(weights: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        weights(i)(j) = throughK
      }
    }
  }

もともとこのアルゴリズムは、重み付けされたエッジのグラフに作用して、ノードのすべてのペア間のすべての最短パスを見つけます(したがって、重みの引数)。これが正しく機能するには、ノード間に有向エッジがある場合は1を、そうでない場合はNO_EDGEを指定する必要があります。アルゴリズムの実行後、このノードが値と等しい長さのサイクルに参加するよりもNO_EDGE未満の値がある場合、主対角線を確認できます。同じサイクルの他のすべてのノードは(主対角線上で)同じ値になります。

サイクル自体を再構築するには、わずかに変更したバージョンのアルゴリズムと親の追跡を使用する必要があります。

  def shortestPath(weights: Array[Array[Int]], parents: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        parents(i)(j) = k
        weights(i)(j) = throughK
      }
    }
  }

親マトリックスは、頂点間にエッジがある場合、最初はエッジセルにソース頂点インデックスを含み、それ以外の場合は-1を含む必要があります。関数が戻った後、各エッジについて、最短パスツリー内の親ノードへの参照があります。そして、実際のサイクルを回復するのは簡単です。

全体として、すべての最小サイクルを見つける次のプログラムがあります

  val NO_EDGE = Integer.MAX_VALUE / 2;

  def shortestPathWithParentTracking(
         weights: Array[Array[Int]],
         parents: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        parents(i)(j) = parents(i)(k)
        weights(i)(j) = throughK
      }
    }
  }

  def recoverCycles(
         cycleNodes: Seq[Int], 
         parents: Array[Array[Int]]): Set[Seq[Int]] = {
    val res = new mutable.HashSet[Seq[Int]]()
    for (node <- cycleNodes) {
      var cycle = new mutable.ArrayBuffer[Int]()
      cycle += node
      var other = parents(node)(node)
      do {
        cycle += other
        other = parents(other)(node)
      } while(other != node)
      res += cycle.sorted
    }
    res.toSet
  }

結果をテストするだけの小さなメインメソッド

  def main(args: Array[String]): Unit = {
    val n = 3
    val weights = Array(Array(NO_EDGE, 1, NO_EDGE), Array(NO_EDGE, NO_EDGE, 1), Array(1, NO_EDGE, NO_EDGE))
    val parents = Array(Array(-1, 1, -1), Array(-1, -1, 2), Array(0, -1, -1))
    shortestPathWithParentTracking(weights, parents)
    val cycleNodes = parents.indices.filter(i => parents(i)(i) < NO_EDGE)
    val cycles: Set[Seq[Int]] = recoverCycles(cycleNodes, parents)
    println("The following minimal cycle found:")
    cycles.foreach(c => println(c.mkString))
    println(s"Total: ${cycles.size} cycle found")
  }

そして出力は

The following minimal cycle found:
012
Total: 1 cycle found

2

無向グラフの場合、最近発行された論文(無向グラフのサイクルとst-pathの最適リスト)は、漸近的に最適なソリューションを提供します。ここで読むことができますhttp://arxiv.org/abs/1205.2766またはここhttp://dl.acm.org/citation.cfm?id=2627951 私はそれがあなたの質問に答えていないことを知っていますが、あなたの質問は方向を述べていません、それはまだGoogle検索に役立つかもしれません


1

ノードXから開始して、すべての子ノードを確認します(指示がない場合、親ノードと子ノードは同等です)。それらの子ノードをXの子としてマークします。そのような子ノードAから、その子をA、X 'の子としてマークします。X'は2ステップ離れているとマークされています。)後でXを押して、Xの子としてマークする場合、Xは3ノードサイクルにあることを意味します。その親へのバックトラックは簡単です(現状では、アルゴリズムはこれをサポートしていないため、X 'を持つ親を見つけることができます)。

注:グラフが無向であるか、双方向のエッジがある場合、サイクルで同じエッジを2回トラバースしたくない場合、このアルゴリズムはより複雑になります。


1

グラフ内のすべての基本回路を検索する場合は、1970年以降の論文で見つかったJAMES C. TIERNANによるECアルゴリズムを使用できます。

非常にオリジナルの ECアルゴリズム私は(間違いがない希望を以下に示します)PHPでそれを実装するために管理として。ループがあればそれも見つけることができます。この実装の回路(オリジナルを複製しようとする)は、ゼロ以外の要素です。ここでゼロは存在しないことを意味します(私たちが知っているようにnull)。

以下の例とは別に、アルゴリズムをより独立させる他の実装に従います。これは、ノードが負の数からでもどこからでも開始できることを意味します(例:-4、-3、-2など)。

どちらの場合も、ノードは連続している必要があります。

元の論文James C. Tiernan Elementary Circuit Algorithmを検討する必要があるかもしれません

<?php
echo  "<pre><br><br>";

$G = array(
        1=>array(1,2,3),
        2=>array(1,2,3),
        3=>array(1,2,3)
);


define('N',key(array_slice($G, -1, 1, true)));
$P = array(1=>0,2=>0,3=>0,4=>0,5=>0);
$H = array(1=>$P, 2=>$P, 3=>$P, 4=>$P, 5=>$P );
$k = 1;
$P[$k] = key($G);
$Circ = array();


#[Path Extension]
EC2_Path_Extension:
foreach($G[$P[$k]] as $j => $child ){
    if( $child>$P[1] and in_array($child, $P)===false and in_array($child, $H[$P[$k]])===false ){
    $k++;
    $P[$k] = $child;
    goto EC2_Path_Extension;
}   }

#[EC3 Circuit Confirmation]
if( in_array($P[1], $G[$P[$k]])===true ){//if PATH[1] is not child of PATH[current] then don't have a cycle
    $Circ[] = $P;
}

#[EC4 Vertex Closure]
if($k===1){
    goto EC5_Advance_Initial_Vertex;
}
//afou den ksana theoreitai einai asfales na svisoume
for( $m=1; $m<=N; $m++){//H[P[k], m] <- O, m = 1, 2, . . . , N
    if( $H[$P[$k-1]][$m]===0 ){
        $H[$P[$k-1]][$m]=$P[$k];
        break(1);
    }
}
for( $m=1; $m<=N; $m++ ){//H[P[k], m] <- O, m = 1, 2, . . . , N
    $H[$P[$k]][$m]=0;
}
$P[$k]=0;
$k--;
goto EC2_Path_Extension;

#[EC5 Advance Initial Vertex]
EC5_Advance_Initial_Vertex:
if($P[1] === N){
    goto EC6_Terminate;
}
$P[1]++;
$k=1;
$H=array(
        1=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        2=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        3=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        4=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        5=>array(1=>0,2=>0,3=>0,4=>0,5=>0)
);
goto EC2_Path_Extension;

#[EC5 Advance Initial Vertex]
EC6_Terminate:
print_r($Circ);
?>

次に、これは他の実装であり、グラフからより独立しており、gotoも配列値もありません。代わりに、配列キーを使用します。パス、グラフ、回路は配列キーとして保存されます(必要に応じて配列値を使用し、必要なだけ変更します)行)。グラフの例は、独立性を示すために-4から始まります。

<?php

$G = array(
        -4=>array(-4=>true,-3=>true,-2=>true),
        -3=>array(-4=>true,-3=>true,-2=>true),
        -2=>array(-4=>true,-3=>true,-2=>true)
);


$C = array();


EC($G,$C);
echo "<pre>";
print_r($C);
function EC($G, &$C){

    $CNST_not_closed =  false;                          // this flag indicates no closure
    $CNST_closed        = true;                         // this flag indicates closure
    // define the state where there is no closures for some node
    $tmp_first_node  =  key($G);                        // first node = first key
    $tmp_last_node  =   $tmp_first_node-1+count($G);    // last node  = last  key
    $CNST_closure_reset = array();
    for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
        $CNST_closure_reset[$k] = $CNST_not_closed;
    }
    // define the state where there is no closure for all nodes
    for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
        $H[$k] = $CNST_closure_reset;   // Key in the closure arrays represent nodes
    }
    unset($tmp_first_node);
    unset($tmp_last_node);


    # Start algorithm
    foreach($G as $init_node => $children){#[Jump to initial node set]
        #[Initial Node Set]
        $P = array();                   // declare at starup, remove the old $init_node from path on loop
        $P[$init_node]=true;            // the first key in P is always the new initial node
        $k=$init_node;                  // update the current node
                                        // On loop H[old_init_node] is not cleared cause is never checked again
        do{#Path 1,3,7,4 jump here to extend father 7
            do{#Path from 1,3,8,5 became 2,4,8,5,6 jump here to extend child 6
                $new_expansion = false;
                foreach( $G[$k] as $child => $foo ){#Consider each child of 7 or 6
                    if( $child>$init_node and isset($P[$child])===false and $H[$k][$child]===$CNST_not_closed ){
                        $P[$child]=true;    // add this child to the path
                        $k = $child;        // update the current node
                        $new_expansion=true;// set the flag for expanding the child of k
                        break(1);           // we are done, one child at a time
            }   }   }while(($new_expansion===true));// Do while a new child has been added to the path

            # If the first node is child of the last we have a circuit
            if( isset($G[$k][$init_node])===true ){
                $C[] = $P;  // Leaving this out of closure will catch loops to
            }

            # Closure
            if($k>$init_node){                  //if k>init_node then alwaya count(P)>1, so proceed to closure
                $new_expansion=true;            // $new_expansion is never true, set true to expand father of k
                unset($P[$k]);                  // remove k from path
                end($P); $k_father = key($P);   // get father of k
                $H[$k_father][$k]=$CNST_closed; // mark k as closed
                $H[$k] = $CNST_closure_reset;   // reset k closure
                $k = $k_father;                 // update k
        }   } while($new_expansion===true);//if we don't wnter the if block m has the old k$k_father_old = $k;
        // Advance Initial Vertex Context
    }//foreach initial


}//function

?>

ECを分析して文書化しましたが、残念ながら文書化はギリシャ語で行われています。


1

DAG内のすべてのサイクルを見つけるには、2つのステップ(アルゴリズム)が必要です。

最初のステップは、Tarjanのアルゴリズムを使用して、強く接続されたコンポーネントのセットを見つけることです。

  1. 任意の頂点から開始します。
  2. その頂点からのDFS。各ノードxについて、dfs_index [x]とdfs_lowval [x]の2つの数値を保持します。dfs_index [x]は、そのノードにアクセスしたときに格納されます。一方、dfs_lowval [x] = min(dfs_low [k])ここで、kは、dfs全域ツリーでxの直接の親ではないxのすべての子です。
  3. 同じdfs_lowval [x]を持つすべてのノードは、同じ強結合コンポーネントにあります。

2番目のステップは、接続されたコンポーネント内のサイクル(パス)を見つけることです。私の提案は、Hierholzerのアルゴリズムの修正バージョンを使用することです。

アイデアは:

  1. 開始頂点vを選択し、vに戻るまで、その頂点からのエッジの軌跡をたどります。すべての頂点の偶数の度合いにより、軌跡が別の頂点に入るときに、v以外の頂点でスタックすることはありません。頂点wにはwを残す未使用のエッジが必要です。このようにして形成されたツアーはクローズドツアーですが、最初のグラフのすべての頂点とエッジをカバーしない場合があります。
  2. 現在のツアーに属しているが、ツアーの一部ではない隣接エッジを持つ頂点vが存在する限り、vから別のトレイルを開始し、未使用のエッジをたどってvに戻り、このようにして形成されたツアーに参加します。前のツアー。

テストケースを含むJava実装へのリンクは次のとおりです。

http://stones333.blogspot.com/2013/12/find-cycles-in-directed-graph-dag.html


16
DAG(有向非巡回グラフ)にサイクルが存在するにはどうすればよいですか?
sky_coder123

これはすべてのサイクルを見つけるわけではありません。
Vishwa Ratna


0

ジョンソンのアルゴリズムよりも効率的であると思われる次のアルゴリズムを偶然見つけました(少なくとも大きなグラフでは)。ただし、Tarjanのアルゴリズムと比較した場合のパフォーマンスはわかりません。
また、これまでに三角形についてのみチェックアウトしました。興味のある方は、千葉典重氏と西関隆夫氏の「Arboricity and Subgraph Listing Algorithms」をご覧ください(http://dx.doi.org/10.1137/0214017


0

ばらばらのセットのリンクリストを使用したJavaScriptソリューション。実行時間を短縮するために、分離したフォレストにアップグレードできます。

var input = '5\nYYNNN\nYYYNN\nNYYNN\nNNNYN\nNNNNY'
console.log(input);
//above solution should be 3 because the components are
//{0,1,2}, because {0,1} and {1,2} therefore {0,1,2}
//{3}
//{4}

//MIT license, authored by Ling Qing Meng

//'4\nYYNN\nYYYN\nNYYN\nNNNY'

//Read Input, preformatting
var reformat = input.split(/\n/);
var N = reformat[0];
var adjMatrix = [];
for (var i = 1; i < reformat.length; i++) {
    adjMatrix.push(reformat[i]);
}

//for (each person x from 1 to N) CREATE-SET(x)
var sets = [];
for (var i = 0; i < N; i++) {
    var s = new LinkedList();
    s.add(i);
    sets.push(s);
}

//populate friend potentials using combinatorics, then filters
var people =  [];
var friends = [];
for (var i = 0; i < N; i++) {
    people.push(i);
}
var potentialFriends = k_combinations(people,2);
for (var i = 0; i < potentialFriends.length; i++){
    if (isFriend(adjMatrix,potentialFriends[i]) === 'Y'){
        friends.push(potentialFriends[i]);
    }
}


//for (each pair of friends (x y) ) if (FIND-SET(x) != FIND-SET(y)) MERGE-SETS(x, y)
for (var i = 0; i < friends.length; i++) {
    var x = friends[i][0];
    var y = friends[i][1];
    if (FindSet(x) != FindSet(y)) {
        sets.push(MergeSet(x,y));
    }
}


for (var i = 0; i < sets.length; i++) {
    //sets[i].traverse();
}
console.log('How many distinct connected components?',sets.length);



//Linked List data structures neccesary for above to work
function Node(){
    this.data = null;
    this.next = null;
}

function LinkedList(){
    this.head = null;
    this.tail = null;
    this.size = 0;

    // Add node to the end
    this.add = function(data){
        var node = new Node();
        node.data = data;
        if (this.head == null){
            this.head = node;
            this.tail = node;
        } else {
            this.tail.next = node;
            this.tail = node;
        }
        this.size++;
    };


    this.contains = function(data) {
        if (this.head.data === data) 
            return this;
        var next = this.head.next;
        while (next !== null) {
            if (next.data === data) {
                return this;
            }
            next = next.next;
        }
        return null;
    };

    this.traverse = function() {
        var current = this.head;
        var toPrint = '';
        while (current !== null) {
            //callback.call(this, current); put callback as an argument to top function
            toPrint += current.data.toString() + ' ';
            current = current.next; 
        }
        console.log('list data: ',toPrint);
    }

    this.merge = function(list) {
        var current = this.head;
        var next = current.next;
        while (next !== null) {
            current = next;
            next = next.next;
        }
        current.next = list.head;
        this.size += list.size;
        return this;
    };

    this.reverse = function() {
      if (this.head == null) 
        return;
      if (this.head.next == null) 
        return;

      var currentNode = this.head;
      var nextNode = this.head.next;
      var prevNode = this.head;
      this.head.next = null;
      while (nextNode != null) {
        currentNode = nextNode;
        nextNode = currentNode.next;
        currentNode.next = prevNode;
        prevNode = currentNode;
      }
      this.head = currentNode;
      return this;
    }


}


/**
 * GENERAL HELPER FUNCTIONS
 */

function FindSet(x) {
    for (var i = 0; i < sets.length; i++){
        if (sets[i].contains(x) != null) {
            return sets[i].contains(x);
        }
    }
    return null;
}

function MergeSet(x,y) {
    var listA,listB;
    for (var i = 0; i < sets.length; i++){
        if (sets[i].contains(x) != null) {
            listA = sets[i].contains(x);
            sets.splice(i,1);
        }
    }
    for (var i = 0; i < sets.length; i++) {
        if (sets[i].contains(y) != null) {
            listB = sets[i].contains(y);
            sets.splice(i,1);
        }
    }
    var res = MergeLists(listA,listB);
    return res;

}


function MergeLists(listA, listB) {
    var listC = new LinkedList();
    listA.merge(listB);
    listC = listA;
    return listC;
}

//access matrix by i,j -> returns 'Y' or 'N'
function isFriend(matrix, pair){
    return matrix[pair[0]].charAt(pair[1]);
}

function k_combinations(set, k) {
    var i, j, combs, head, tailcombs;
    if (k > set.length || k <= 0) {
        return [];
    }
    if (k == set.length) {
        return [set];
    }
    if (k == 1) {
        combs = [];
        for (i = 0; i < set.length; i++) {
            combs.push([set[i]]);
        }
        return combs;
    }
    // Assert {1 < k < set.length}
    combs = [];
    for (i = 0; i < set.length - k + 1; i++) {
        head = set.slice(i, i+1);
        tailcombs = k_combinations(set.slice(i + 1), k - 1);
        for (j = 0; j < tailcombs.length; j++) {
            combs.push(head.concat(tailcombs[j]));
        }
    }
    return combs;
}

0

開始ノードsからのDFS、走査中にDFSパスを追跡し、ノードvからsへのパスでエッジを見つけた場合にパスを記録します。(v、s)はDFSツリーのバックエッジであり、sを含むサイクルを示します。


良いですが、これはOPが求めているものではありません。すべてのサイクルを見つけてください。
Sean L

0

順列サイクルに関するご質問については、https//www.codechef.com/problems/PCYCLEで詳細を ご覧ください。

あなたはこのコードを試すことができます(サイズと桁数を入力してください):

# include<cstdio>
using namespace std;

int main()
{
    int n;
    scanf("%d",&n);

    int num[1000];
    int visited[1000]={0};
    int vindex[2000];
    for(int i=1;i<=n;i++)
        scanf("%d",&num[i]);

    int t_visited=0;
    int cycles=0;
    int start=0, index;

    while(t_visited < n)
    {
        for(int i=1;i<=n;i++)
        {
            if(visited[i]==0)
            {
                vindex[start]=i;
                visited[i]=1;
                t_visited++;
                index=start;
                break;
            }
        }
        while(true)
        {
            index++;
            vindex[index]=num[vindex[index-1]];

            if(vindex[index]==vindex[start])
                break;
            visited[vindex[index]]=1;
            t_visited++;
        }
        vindex[++index]=0;
        start=index+1;
        cycles++;
    }

    printf("%d\n",cycles,vindex[0]);

    for(int i=0;i<(n+2*cycles);i++)
    {
        if(vindex[i]==0)
            printf("\n");
        else
            printf("%d ",vindex[i]);
    }
}

0

2階の答えの疑似コードのDFS c ++バージョン:

void findCircleUnit(int start, int v, bool* visited, vector<int>& path) {
    if(visited[v]) {
        if(v == start) {
            for(auto c : path)
                cout << c << " ";
            cout << endl;
            return;
        }
        else 
            return;
    }
    visited[v] = true;
    path.push_back(v);
    for(auto i : G[v])
        findCircleUnit(start, i, visited, path);
    visited[v] = false;
    path.pop_back();
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.