Yönlendirilmiş bir grafikteki tüm döngüleri bulma


199

Belirli bir düğüme / düğümünden yönlendirilmiş bir grafikteki TÜM döngüleri nasıl bulabilirim (yineleyebilirim)?

Örneğin, böyle bir şey istiyorum:

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

ama değil: B-> C-> B


1
Ödev sanırım? me.utexas.edu/~bard/IP/Handouts/cycles.pdf geçerli bir soru değil :)
ShuggyCoUk

6
Bunun en azından NP Sert olduğunu unutmayın. Muhtemelen PSPACE, bunu düşünmek zorundayım, ama karmaşıklık teorisi B- için sabah çok erken -)
Brian Postow

2
Giriş grafiğinizde v köşeleri ve e kenarları varsa, 2 ^ (e - v +1) -1 farklı döngü vardır (hepsi basit döngüler olmayabilir ). Bu oldukça fazla - hepsini açıkça yazmak istemeyebilirsiniz. Ayrıca, çıktı boyutu üstel olduğundan algoritmanın karmaşıklığı polinom olamaz. Bence bu soruya hala bir cevap yok.
CygnusX1


Yanıtlar:


105

Bu sayfayı aramamda buldum ve döngüler güçlü bir şekilde bağlı bileşenlerle aynı olmadığından, aramaya devam ettim ve son olarak, yönlendirilmiş bir grafiğin tüm (temel) döngülerini listeleyen verimli bir algoritma buldum. Donald B. Johnson'dan ve makale aşağıdaki bağlantıda bulunabilir:

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

Java uygulaması şu adreste bulunabilir:

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

Johnson algoritmasının bir Mathematica gösterimi burada bulunabilir , uygulama sağdan indirilebilir ( "Yazar kodunu indir" ).

Not: Aslında, bu sorun için birçok algoritma vardır. Bazıları bu makalede listelenmiştir:

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

Makaleye göre, Johnson algoritması en hızlı olanıdır.


1
Makaleden bu kadar güçlük çekiyorum ve nihayetinde bu aglorithm hala Tarjan'ın uygulanmasını gerektiriyor. Java kodu da iğrenç. :(
Gleno

7
@Gleno Peki, geri kalanları uygulamak yerine grafikteki tüm döngüleri bulmak için Tarjan'ı kullanabileceğinizi kastediyorsanız, yanılıyorsunuz. Burada , güçlü bir şekilde bağlı bileşenler ve tüm döngüler arasındaki farkı görebilirsiniz (cd ve gh döngüleri Tarjan'ın alg tarafından döndürülmez) alg, bu yüzden karmaşıklığı üstelden daha küçük olabilir). Java-Kodu daha iyi olabilirdi, ama bu makaleyi uygulama çabamı kurtardı.
eminsenay

4
Bu cevap seçilen cevaptan çok daha iyi. Güçlü bir şekilde bağlı bileşenlerden tüm basit döngüleri nasıl elde edeceğimizi anlamaya çalışırken bir süre uğraştım. Bu önemsiz değil. Johnson'ın makalesi harika bir algoritma içeriyor, ancak geçmesi biraz zor. Java uygulamasına baktım ve Matlab'da kendimi devirdim. Kodu gist.github.com/1260153 adresinde bulabilirsiniz .
codehippo

5
@moteutsch: Belki bir şeyleri kaçırıyorum, ama Johnson gazetesine (ve diğer kaynaklara) göre, birden fazla tepe noktası (başlangıç ​​/ bitiş dışında) görünmüyorsa bir döngü temeldir. Bu tanıma göre, A->B->C->Atemel de değil mi?
psmears

9
Bunun için python kullanan herkes için not: Johnson algoritması simple_cyclenetworkx'teki gibi uygulanır.
Joel

35

Geri izleme ile derinlik ilk araması burada çalışmalıdır. Daha önce bir düğümü ziyaret edip etmediğinizi takip etmek için bir dizi boolean değer tutun. Gitmek için yeni düğümleriniz biterse (daha önce bulunduğunuz bir düğüme çarpmadan), sadece geri dönüp farklı bir dal deneyin.

Grafiği temsil eden bir bitişiklik listeniz varsa DFS'nin uygulanması kolaydır. Örneğin adj [A] = {B, C}, B ve C'nin A'nın çocukları olduğunu gösterir.

Örneğin, aşağıdaki sözde kod. "start", başladığınız düğümdür.

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;

Yukarıdaki işlevi başlangıç ​​düğümü ile çağırın:

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

2
Teşekkürler. Bu yaklaşımı burada belirtilmiş olanlardan bazılarına tercih ediyorum, çünkü anlaşılması basit (r) ve belki de optimal olmasa da makul zaman karmaşıklığına sahip.
redcalx

1
bu tüm döngüleri nasıl bulur?
beyin fırtınası

3
if (node == start): - node and startilk çağrıda ne var
beyin fırtınası

2
@ user1988876 Bu, belirli bir tepe noktasını içeren tüm döngüleri buluyor gibi görünüyor (ki bu olurdu start). Bu tepe noktasında başlar ve tekrar bu tepe noktasına geri dönene kadar bir DFS yapar, sonra bir döngü bulduğunu bilir. Ama aslında döngülerin çıktısını almaz, sadece bir sayıdır (ancak bunu yapmak için değiştirmek çok zor olmamalıdır).
Bernhard Barker

1
@ user1988876 Sadece, bulunan döngü sayısına eşit sayıda "bir yol buldum" yazdırabilir (bu kolayca bir sayıyla değiştirilebilir). Evet, yalnızca gelen döngüleri algılar start. Her ziyaret edilen bayrak nedeniyle silinecek olduğundan, ziyaret edilen bayrakları temizlemenize gerek yoktur visited[node]=NO;. Ancak, bir döngünüz varsa A->B->C->A, bunların 3'ünde olduğu gibi 3 kez tespit edeceğinizi unutmayın start. Bunu önlemek için bir fikir, bir noktada düğüm olan her düğümün startayarlandığı ve daha sonra bunları tekrar ziyaret etmediğiniz başka bir ziyaret edilen diziye sahip olmaktır .
Bernhard Barker

23

Her şeyden önce - gerçekten tüm döngüleri bulmayı denemek istemezsiniz çünkü 1 varsa, bunlardan sonsuz sayıda vardır. Örneğin ABA, ABABA vb. Veya 2 döngüyü 8 benzeri bir döngüye vb. Birleştirmek mümkün olabilir. başlangıç ​​/ bitiş noktasında. Daha sonra isterseniz basit döngülerin kombinasyonlarını oluşturabilirsiniz.

Yönlendirilmiş bir grafikteki tüm basit döngüleri bulmak için temel algoritmalardan biri şudur: Grafikteki tüm basit yolların (kendiliğinden geçmeyenler) derinlik-ilk geçişini yapın. Geçerli düğümün yığın üzerinde ardılı olduğunda, basit bir döngü keşfedilir. Tanımlanan halefle başlayan ve yığının tepesi ile biten destedeki elemanlardan oluşur. Tüm basit yolların derinlikteki ilk geçişi, önce derinlik aramasına benzer, ancak o anda yığınta bulunanlar dışındaki ziyaret edilen düğümleri durma noktası olarak işaretlemez / kaydetmezsiniz.

Yukarıdaki kaba kuvvet algoritması korkunç derecede verimsizdir ve buna ek olarak döngülerin birden fazla kopyasını oluşturur. Bununla birlikte, performansı artırmak ve döngü çoğaltmasını önlemek için çeşitli geliştirmeler uygulayan çoklu pratik algoritmaların başlangıç ​​noktasıdır. Bir süre önce bu algoritmaların ders kitaplarında ve web'de mevcut olmadığını öğrenmek beni şaşırttı. Bu yüzden bir araştırma yaptım ve burada açık kaynaklı bir Java kütüphanesinde yönlendirilmemiş grafiklerde döngüler için 4 tane algoritma ve 1 algoritma uyguladım: http://code.google.com/p/niographs/ .

BTW, yönlendirilmemiş grafiklerden bahsettiğimden beri: Bunların algoritması farklı. Yayılan bir ağaç oluşturun ve sonra ağacın bir parçası olmayan her kenar, ağaçtaki bazı kenarlarla birlikte basit bir döngü oluşturur. Bu şekilde bulunan döngüler, bir döngü tabanı oluşturur. Tüm basit döngüler daha sonra 2 veya daha fazla farklı temel döngüyü birleştirerek bulunabilir. Daha fazla ayrıntı için bkz . Bu: http://dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf .


Örnek olarak nasıl kullanılır jgraphtİçinde kullanılanıhttp://code.google.com/p/niographs/ örnek olarak github.com/jgrapht/jgrapht/wiki/DirectedGraphDemo
Vishrant

19

Bu sorunu çözmek için bulduğum en basit seçim, denilen python lib'i kullanmaktı. networkx .

Bu sorunun en iyi cevabında belirtilen Johnson algoritmasını uygular, ancak yürütülmesi oldukça basittir.

Kısacası aşağıdakilere ihtiyacınız var:

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))

Cevap: [['a', 'b', 'd', 'e'], ['a', 'b', 'c']]

resim açıklamasını buraya girin


1
Ayrıca bir sözlüğü bir networkx grafiğine de çevirebilirsiniz:nx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
Luke Miles

Bir başlangıç ​​tepe noktasını nasıl belirleyebilirim?
nosense

5

Netleştirmek için:

  1. Güçlü Bağlantılı Bileşenler, grafikte olası tüm döngülerde değil, içinde en az bir döngüye sahip tüm altgrafları bulur. Örneğin, güçlü bir şekilde bağlı tüm bileşenleri alıp her birini tek bir düğüme (yani, bileşen başına bir düğüm) daraltır / gruplandırır / birleştirirseniz, döngüsüz bir ağaç alırsınız (aslında bir DAG). Her bileşen (temel olarak içinde en az bir döngü bulunan bir alt grafiktir) dahili olarak daha fazla olası döngü içerebilir, bu nedenle SCC tüm olası döngüleri BULMAZ, en az bir döngüye sahip tüm olası grupları bulur ve grafikte döngüler olmayacaktır.

  2. bir grafikteki tüm basit döngüleri bulmak için , diğerleri de belirtildiği gibi, Johnson algoritması bir adaydır.


3

Bunu bir kez röportaj sorusu olarak aldım, bunun sana geldiğinden şüpheleniyorsun ve yardım için buraya geleceksin. Sorunu üç soruya bölün ve bu daha kolay hale gelir.

  1. bir sonraki geçerli rotayı nasıl belirlersin
  2. bir noktanın kullanılıp kullanılmadığını nasıl belirlersiniz
  3. yine aynı noktadan geçmekten nasıl kaçınırsın

Sorun 1) Rota sonuçlarını yinelemenin bir yolunu sağlamak için yineleyici desenini kullanın. Bir sonraki rotayı almak için mantığı koymak için iyi bir yer muhtemelen yineleyicinizin "moveNext" tir. Geçerli bir rota bulmak için veri yapınıza bağlıdır. Benim için geçerli rota olasılıkları dolu bir sql tablo oldu, bu yüzden bir kaynak verilen geçerli hedefleri almak için bir sorgu oluşturmak zorunda kaldı.

Problem 2) Onları alırken bir koleksiyona bulduğunuz her bir düğümü itin, bu, anında oluşturduğunuz koleksiyonu sorgulayarak bir nokta üzerinde çok kolay "iki katına çıkıp çıkmadığınızı" görebileceğiniz anlamına gelir.

Problem 3) Herhangi bir noktada iki katına çıktığını görürseniz, koleksiyondan bir şeyler çıkarabilir ve "yedekleyebilirsiniz". Sonra bu noktadan sonra tekrar "ilerlemeye" çalışın.

Hack: Sql Server 2008 kullanıyorsanız, verilerinizi bir ağaçta yapılandırırsanız, bunu hızlı bir şekilde çözmek için kullanabileceğiniz bazı yeni "hiyerarşi" şeyler vardır.


3

Arka kenarları olan DFS tabanlı varyantlar gerçekten döngüleri bulacaktır, ancak çoğu durumda minimum DEĞİLDİR döngüler . Genel olarak DFS size bir döngü olduğunu söyler ama döngüleri bulmak için yeterince iyi değildir. Örneğin, iki kenarı paylaşan 5 farklı döngü düşünün. Sadece DFS (geri izleme varyantları dahil) kullanarak döngüleri tanımlamanın basit bir yolu yoktur.

Johnson'un algoritması gerçekten de tüm benzersiz basit döngüleri verir ve iyi zaman ve alan karmaşıklığına sahiptir.

Ancak sadece MINIMAL döngüler bulmak istiyorsanız (yani herhangi bir tepe noktasından daha fazla bir döngü olabilir ve minimum döngüleri bulmak istiyoruz) VE grafiğiniz çok büyük değilse, aşağıdaki basit yöntemi kullanmayı deneyebilirsiniz. Johnson göre ÇOK basit ama oldukça yavaş.

Yani, biri kesinlikle MİNİMAL döngülerini bulmanın en kolay yolu bitişiklik matrisi kullanan tüm köşeler arasında asgari yolları bulmak için Floyd'un algoritmasını kullanmaktır. Bu algoritma Johnson kadar ideal değildir, ancak o kadar basittir ve iç halkası o kadar sıkıdır ki daha küçük grafikler için (<= 50-100 düğüm) kesinlikle kullanmak mantıklıdır. Zaman karmaşıklığı O (n ^ 3), ebeveyn izleme kullanıyorsanız alan karmaşıklığı O (n ^ 2) ve kullanmıyorsanız O (1). Her şeyden önce, bir döngü olup olmadığı sorusuna cevap bulalım. Algoritma son derece basittir. Scala'da snippet aşağıdadır.

  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
      }
    }
  }

Başlangıçta bu algoritma, tüm düğüm çiftleri arasındaki en kısa yolları bulmak için ağırlıklı kenar grafiğinde çalışır (dolayısıyla ağırlıklar argümanı). Doğru çalışması için düğümler arasında yönlendirilmiş bir kenar varsa 1 veya aksi takdirde NO_EDGE sağlamanız gerekir. Algoritma yürütüldükten sonra, NO_EDGE değerinden daha düşük değerler varsa bu düğümün değere eşit bir uzunluk döngüsüne katıldığından ana diyagonali kontrol edebilirsiniz. Aynı döngünün diğer tüm düğümleri aynı değere sahip olacaktır (ana diyagonalde).

Döngüyü yeniden yapılandırmak için, üst izleme ile algoritmanın biraz değiştirilmiş sürümünü kullanmamız gerekir.

  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
      }
    }
  }

Ana noktalar matrisi başlangıçta, köşeler arasında bir kenar ve -1 ise bir kenar hücresinde kaynak tepe noktası dizini içermelidir. İşlev döndükten sonra, her kenar için en kısa yol ağacındaki üst düğüme başvuruda bulunacaksınız. Ve sonra gerçek döngüleri kurtarmak kolaydır.

Sonuçta, tüm minimum döngüleri bulmak için aşağıdaki programa sahibiz

  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
  }

ve sonucu test etmek için küçük bir ana yöntem

  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")
  }

ve çıktı

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

2

Yönlendirilmemiş grafik durumunda, yakın zamanda yayınlanan bir makale ( Yönlendirilmemiş grafiklerde döngülerin ve st-yolların optimum listesi ) asimptotik olarak en uygun çözümü sunar. Burada okuyabilirsiniz http://arxiv.org/abs/1205.2766 veya burada http://dl.acm.org/citation.cfm?id=2627951 Sorunuza cevap vermediğini biliyorum, ancak sorunuz yön belirtmiyor, yine de Google araması için yararlı olabilir


1

X düğümünden başlayın ve tüm alt düğümleri kontrol edin (yönlendirilmemişse üst ve alt düğümler eşdeğerdir). Bu alt düğümleri X'in çocukları olarak işaretleyin. Bu tür herhangi bir alt düğümden, X'in 2 adım uzakta olduğu işaretlenen A, X '' in çocukları olduğunu işaretleyin.). Daha sonra X'e basar ve bunu X'in alt öğesi olarak işaretlerseniz, bu X'in 3 düğümlü bir döngüde olduğu anlamına gelir. Üst öğeye geri izlemek kolaydır (olduğu gibi, algoritmanın bunun için desteği yoktur, bu nedenle hangi üst öğenin X 'olduğunu bulursunuz).

Not: Grafik yönlendirilmemişse veya çift yönlü kenarları varsa, bir döngü için aynı kenarı iki kez geçmek istemediğinizi varsayarsak, bu algoritma daha karmaşık hale gelir.


1

İstediğiniz bir grafikteki tüm temel devreleri bulmaksa, 1970'ten beri bir kağıtta bulunan JAMES C. TIERNAN tarafından EC algoritmasını kullanabilirsiniz.

Çok orijinal I (umut hiçbir hata aşağıda gösterilmiştir orada toplamak vardır) php uygulamak başardı olarak AK algoritması. Varsa döngüler de bulabilir. Bu uygulamadaki (orijinali klonlamaya çalışan) devreler sıfır olmayan elemanlardır. Burada sıfır, var olmama anlamına gelir (bildiğimiz gibi null).

Aşağıdakilerin dışında, algoritmaya daha bağımsız bir uygulama sağlayan başka bir uygulama da izlenir, bu da düğümlerin negatif sayılardan bile, örneğin -4, -3, -2, vb.

Her iki durumda da düğümlerin sıralı olması gerekir.

Orijinal makaleyi incelemeniz gerekebilir, James C. Tiernan Temel Devre Algoritması

<?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);
?>

o zaman bu diğer uygulama, grafikten daha bağımsız, goto olmadan ve dizi değerleri olmadan, bunun yerine dizi anahtarları, yol, grafik ve devreler dizi anahtarları olarak saklanır (isterseniz dizi değerlerini kullanın, sadece gerekli olanları değiştirin çizgiler). Örnek grafik bağımsızlığını göstermek için -4'ten başlar.

<?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

?>

AT'yi analiz ettim ve belgeledim ama maalesef belgeler Yunanca.


1

Bir DAG'daki tüm döngüleri bulmada iki adım (algoritma) vardır.

İlk adım, güçlü bir şekilde bağlı bileşenler kümesini bulmak için Tarjan'ın algoritmasını kullanmaktır.

  1. Herhangi bir keyfi tepe noktasından başlayın.
  2. Bu köşe noktasından DFS. Her bir x düğümü için, dfs_index [x] ve dfs_lowval [x] olmak üzere iki sayı tutun. dfs_index [x] bu düğüm ziyaret edildiğinde depolanırken dfs_lowval [x] = dk (dfs_low [k]) burada k, dfs yayılan ağaçtaki x'in doğrudan üst öğesi olmayan x'in tüm çocuklarıdır.
  3. Aynı dfs_lowval [x] 'a sahip tüm düğümler aynı güçlü şekilde bağlı bileşendedir.

İkinci adım, bağlı bileşenler içindeki döngüleri (yolları) bulmaktır. Benim önerim Hierholzer algoritmasının değiştirilmiş bir versiyonunu kullanmak.

Fikir:

  1. Herhangi bir başlangıç ​​köşesi v'yi seçin ve v noktasına dönene kadar o köşeden bir kenar izi takip edin. V dışında herhangi bir köşeye sıkışmak mümkün değildir, çünkü tüm köşelerin eşit derecesi, iz başka bir girişe girdiğinde köşe w w kullanılmayan bir kenar olmalıdır. Bu şekilde oluşturulan tur kapalı bir turdur, ancak ilk grafiğin tüm köşelerini ve kenarlarını kapsamaz.
  2. Geçerli tura ait olan ancak turun bir parçası olmayan bitişik kenarları olan bir v noktası olduğu sürece, v'den dönene kadar kullanılmayan kenarları takip ederek v'den başka bir yol başlatın ve bu şekilde oluşan tura katılın önceki tur.

İşte bir test senaryosuna sahip bir Java uygulamasına bağlantı:

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


16
DAG'da (Yönlü Asiklik Grafik) bir döngü nasıl olabilir?
16:47

Bu, tüm döngüleri bulamaz.
Vishwa Ratna


0

Johnson'un algoritmasından (en azından daha büyük grafikler için) daha verimli görünen aşağıdaki algoritmaya rastladım. Ancak Tarjan'ın algoritmasına kıyasla performansından emin değilim.
Ayrıca, şimdiye kadar sadece üçgenler için kontrol ettim. İlgileniyorsanız, lütfen Norishige Chiba ve Takao Nishizeki'nin "Arboricity ve Alt Çizgi Listeleme Algoritmaları" na bakın ( http://dx.doi.org/10.1137/0214017 )


0

Ayrık küme bağlantılı listeleri kullanarak Javascript çözümü. Daha hızlı çalışma süreleri için ayrık ayarlanmış ormanlara yükseltilebilir.

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

Başlangıç ​​düğümünden DFS, geçiş sırasında DFS yolunu takip edin ve s yolundan v düğümünden bir kenar bulursanız yolu kaydedin. (v, s) DFS ağacında bir arka kenardır ve bu nedenle s içeren bir döngüyü gösterir.


İyi, ama OP'nin aradığı bu değil: tüm döngüyü bul, muhtemelen minimum.
Sean L

0

Permütasyon Döngüsü ile ilgili sorunuzla ilgili daha fazla bilgiyi buradan edinebilirsiniz: https://www.codechef.com/problems/PCYCLE

Bu kodu deneyebilirsiniz (boyut ve rakam numarasını girin):

# 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

İkinci katın cevabında sözde kod için DFS c ++ sürümü:

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();
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.