在有向图中找到所有循环


198

我如何找到(迭代)有向图中给定节点的所有循环?

例如,我想要这样的东西:

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

但不是:B-> C-> B


1
我要做功课吗?me.utexas.edu/~bard/IP/Handouts/cycles.pdf并非不是一个有效的问题:)
ShuggyCoUk 2009年

5
请注意,这至少是NP Hard。可能是PSPACE,我必须考虑一下,但是对于复杂性理论B-来说,还为时过早
布莱恩·波斯托

2
如果输入图形为V顶点和e边则有2 ^(E - v +1)-1周期不同(尽管不是所有的可能是简单的周期)。这很多-您可能不想显式地编写所有代码。另外,由于输出大小是指数的,因此算法的复杂度不能为多项式。我认为这个问题仍然没有答案。
CygnusX1 2011年


Answers:


105

我在搜索中找到了该页面,由于循环与强连接的组件不同,因此我一直进行搜索,最后,我找到了一种有效的算法,该算法列出了有向图的所有(基本)循环。它来自唐纳德·B·约翰逊(Donald B. Johnson),可以在以下链接中找到该论文:

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

Java实现可在以下位置找到:

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

一个数学约翰逊的算法演示可以发现这里实现,可以从右边(下载“下载作者码”)。

注意:实际上,有很多算法可以解决此问题。本文中列出了其中一些:

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

根据文章,约翰逊算法是最快的算法。


1
从本文中我发现实施起来很麻烦,最终这种算法仍然需要实施Tarjan。Java代码也很丑陋。:(
Gleno

7
@Gleno好吧,如果您的意思是可以使用Tarjan在图中查找所有循环而不是执行其余循环,那是错误的。在这里,您可以看到强连接的组件与所有循环之间的差异(Tarjan的alg不会返回cd和gh循环)(@ batbrat)您的困惑的答案也隐藏在这里:Tarjan的不返回所有可能的循环alg,因此其复杂度可能小于指数)。Java代码可能会更好,但是它节省了我在本文中实现的工作量。
eminsenay 2011年

4
该答案比所选答案好得多。我努力了很长时间,试图弄清楚如何从强连接的组件中获取所有简单的周期。事实证明,这是不平凡的。Johnson的论文包含了一个很好的算法,但是有点困难。我查看了Java实现,并在Matlab中进行了介绍。该代码可在gist.github.com/1260153上找到
codehippo 2011年

5
@moteutsch:也许我缺少了一些东西,但是根据Johnson论文(和其他来源),如果没有一个顶点(除了起点/终点)出现一次以上,则循环是基本的。按照这个定义,不是A->B->C->A基本的吗?
psmears 2014年

9
对于使用python的任何人请注意:Johnson算法的实现方式simple_cycle与networkx相同。
乔尔

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

1
如何找到所有周期?
头脑风暴

3
if (node == start): - node and start首次通话是什么
头脑风暴

2
@ user1988876这似乎是查找涉及给定顶点(为start)的所有循环。它从该顶点开始并执行DFS,直到再次回到该顶点,然后知道已找到一个循环。但是它实际上并没有输出周期,只是输出了一些周期(但是修改它来做到这一点应该不会太困难)。
2013年

1
@ user1988876好吧,它只是打印“找到路径”的次数等于找到的循环数(可以很容易地用计数代替)。是的,它将仅检测来自的周期start。您不必真正清除已访问的标志,因为每个已访问的标志都会由于清除visited[node]=NO;。但是请记住,如果您有一个周期A->B->C->A,您将检测到3次,就像其中start任何3 次一样。防止出现这种情况的一个方法是,要设置另一个访问数组,在该数组中start设置在某个时刻成为节点的每个节点,然后就不必重新访问它们了。
Bernhard Barker 2013年

23

首先-您真的不想尝试从字面上看所有周期,因为如果有1,那么就有无限多个周期。例如ABA,ABABA等。或者也可以将2个循环连接成一个类似8的循环,以此类推...等等。有意义的方法是寻找所有所谓的简单循环-除自身以外不交叉的循环在起点/终点。然后,如果您愿意,可以生成简单循环的组合。

在有向图中查找所有简单循环的基线算法之一是:对图中的所有简单路径(不交叉的路径)进行深度优先遍历。每当当前节点在堆栈上具有后继者时,都会发现一个简单的周期。它由堆栈中的元素组成,这些元素从已标识的后继者开始,到堆栈顶部为止。所有简单路径的深度优先遍历与深度优先搜索相似,但是除了当前堆栈中的那些节点以外,您不会标记/记录已访问的节点作为停止点。

上面的蛮力算法效率极低,此外还产生多个循环副本。然而,这是多种实用算法的起点,这些实用算法应用了各种增强功能以​​提高性能并避免循环重复。令我惊讶的是,不久前发现教科书和网络上还没有这些算法。因此,我进行了一些研究,并在http://code.google.com/p/niographs/的开放源Java库中实现了4种此类算法和1种针对无向图循环的算法。

顺便说一句,因为我提到了无向图:那些算法是不同的。生成一棵生成树,然后不属于该树的每个边缘与该树中的某些边缘一起形成一个简单的循环。以这种方式发现的循环形成了所谓的循环基础。然后可以通过组合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库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图:nx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
Luke Miles

如何指定起始顶点?
nosense

5

澄清:

  1. 高度连接的组件将查找其中具有至少一个循环的所有子图,而不是图中的所有可能的循环。例如,如果您将所有牢固连接的组件都折叠起来/分组/合并到一个节点(即每个组件一个节点),则将得到一棵没有循环的树(实际上是DAG)。每个组件(基本上是一个至少包含一个循环的子图)在内部可以包含更多可能的循环,因此SCC不会找到所有可能的循环,它将找到具有至少一个循环的所有可能的组,并且如果您进行分组它们,那么图形将没有周期。

  2. 为了找到图中的所有简单循环,正如其他人提到的那样,约翰逊算法是一种候选方法。


3

曾经有人问我这是面试问题,我怀疑这已经发生在您身上,您正在这里寻求帮助。将问题分为三个问题,将变得更加容易。

  1. 您如何确定下一条有效路线
  2. 您如何确定一个点是否已被使用
  3. 如何避免再次越过同一点

问题1)使用迭代器模式提供一种迭代路由结果的方法。放置逻辑以获取下一条路线的好地方可能是迭代器的“ moveNext”。要找到有效的路由,这取决于您的数据结构。对我来说,这是一个充满有效路由可能性的sql表,因此我必须构建查询以获取给定源的有效目的地。

问题2)将找到的每个节点推入到集合中,这意味着您可以通过查询正在构建的集合来非常轻松地查看是否在某个点上“加倍返回”。

问题3)如果在任何时候看到您要加倍,您可以从集合中弹出内容并“备份”。然后从这一点开始尝试再次“前进”。

哈克:如果您使用的是Sql Server 2008,则可以使用树中的数据来构造一些新的“层次结构”,以快速解决此问题。


3

具有后边缘的基于DFS的变体确实可以找到循环,但是在许多情况下,它不是最小的循环。通常,DFS会为您提供一个标记,表明存在一个循环,但不足以实际找到循环。例如,假设5个不同的周期共享两个边。没有简单的方法仅使用DFS(包括回溯变体)来识别周期。

Johnson的算法确实给出了所有独特的简单循环,并且具有良好的时间和空间复杂性。

但是,如果您只想找到最小周期(意味着可能有一个以上的周期遍历任何顶点,而我们有兴趣寻找最小顶点)并且您的图形不是很大,则可以尝试使用以下简单方法。与Johnson's相比,它非常简单,但速度却很慢。

因此,找到最小周期的绝对最简单的方法之一是使用弗洛伊德算法使用邻接矩阵在所有顶点之间找到最小路径。该算法远不及Johnson算法最佳,但它是如此简单,并且其内部循环非常紧密,以至于对于较小的图形(<= 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
      }
    }
  }

最初,该算法在加权边图上运行,以找到所有节点对之间的所有最短路径(因此,使用weights参数)。为了使其正常工作,如果节点之间有定向边,则需要提供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


1

从节点X开始,并检查所有子节点(如果未定向,则父节点和子节点等效)。将这些子节点标记为X的子节点。在任何此类子节点A中,将其标记为A的子节点X',其中X'被标记为距离2步。)如果您以后点击X并将其标记为X''的子代,则意味着X处于3个节点的周期中。回溯到它的父对象很容易(按原样,该算法不支持此方法,因此您会找到具有X'的任何父对象)。

注意:如果图形是无向的或具有任何双向边,则假设您不想在一个循环中遍历同一条边两次,则此算法会变得更加复杂。


1

如果要在图形中查找所有基本电路,可以使用JAMES C. TIERNAN的EC算法,该算法自1970年以来在纸上发现。

非常原始的 EC算法我设法实现它在PHP(希望没有错误如下图所示)。如果有循环,它也可以找到循环。此实现中的电路(试图克隆原始电路)是非零元素。这里的零代表不存在(我们知道它为null)。

除了下面的描述之外,还有另一个实现使算法更加独立,它意味着节点可以从任何地方开始,甚至可以从负数开始,例如-4,-3,-2等。

在这两种情况下,都要求节点是顺序的。

您可能需要研究原始论文James C. Tiernan基本电路算法

<?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中查找所有循环涉及两个步骤(算法)。

第一步是使用Tarjan的算法来查找一组强连接的组件。

  1. 从任意顶点开始。
  2. 从该顶点开始的DFS。对于每个节点x,保留两个数字dfs_index [x]和dfs_lowval [x]。dfs_index [x]存储访问该节点的时间,而dfs_lowval [x] = min(dfs_low [k]),其中k是x的所有子代,它们不是dfs生成树中x的直接父代。
  3. 具有相同dfs_lowval [x]的所有节点都在同一强连接组件中。

第二步是在连接的组件中查找循环(​​路径)。我的建议是使用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 '16

这不会找到所有周期。
Vishwa Ratna


0

我偶然发现了以下算法,该算法似乎比约翰逊算法更有效(至少对于较大的图形而言)。但是,与Tarjan的算法相比,我不确定其性能。
此外,到目前为止,我只检查了三角形。如果有兴趣,请参阅Norishige Chiba和Takao Nishizeki的“树状图和子图列出算法”(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路径,如果在到s的路径中找到节点v的边缘,则记录该路径。(v,s)是DFS树中的后端,因此指示包含s的循环。


很好,但这不是OP想要的:找到所有周期,可能很小。
肖恩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

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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.