web-dev-qa-db-fra.com

Trouver tous les cycles dans un graphe dirigé

Comment puis-je trouver (parcourir) TOUS les cycles dans un graphe dirigé de/vers un nœud donné?

Par exemple, je veux quelque chose comme ça:

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

mais pas: B-> C-> B

187
user7305

J'ai trouvé cette page dans ma recherche et, comme les cycles ne sont pas identiques aux composants fortement connectés, j'ai continué à chercher et, finalement, j'ai trouvé un algorithme efficace qui répertorie tous les cycles (élémentaires) d'un graphe dirigé. Il est de Donald B. Johnson et le papier peut être trouvé dans le lien suivant:

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

Une implémentation de Java peut être trouvée dans:

http://normalisiert.de/code/Java/elementaryCycles.Zip

Une démonstration de Mathematica de l’algorithme de Johnson peut être trouvée ici , l’implémentation peut être téléchargée à partir de la droite ( "Télécharger code de l'auteur " ).

Remarque: En réalité, il existe de nombreux algorithmes pour résoudre ce problème. Certains d'entre eux sont énumérés dans cet article:

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

Selon l'article, l'algorithme de Johnson est le plus rapide.

99
eminsenay

La première recherche en profondeur avec retour en arrière devrait fonctionner ici. Conservez un tableau de valeurs booléennes pour savoir si vous avez déjà visité un nœud. Si vous êtes à court de nouveaux nœuds (sans toucher un nœud que vous avez déjà été), revenez en arrière et essayez une autre branche.

Le DFS est facile à implémenter si vous avez une liste de contiguïté pour représenter le graphique. Par exemple adj [A] = {B, C} indique que B et C sont les enfants de A.

Par exemple, pseudo-code ci-dessous. "start" est le nœud à partir duquel vous partez.

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;

Appelez la fonction ci-dessus avec le noeud de départ:

visited = {}
dfs(adj,start,visited)
32
Himadri Choudhury

Tout d'abord, vous ne voulez pas vraiment essayer de trouver littéralement tous les cycles, car s'il y a 1, il en existe un nombre infini. Par exemple, ABA, ABABA, etc. Il est également possible de réunir deux cycles en un cycle de type 8, etc., etc. ... L’approche utile consiste à rechercher tous les cycles dits simples - ceux qui ne se croisent pas, sauf au début/à la fin. Ensuite, si vous le souhaitez, vous pouvez générer des combinaisons de cycles simples.

L'un des algorithmes de base permettant de rechercher tous les cycles simples dans un graphe dirigé est le suivant: Effectuez une traversée en profondeur en premier de tous les chemins simples (ceux qui ne se croisent pas) dans le graphe. Chaque fois que le noeud actuel a un successeur sur la pile, un cycle simple est découvert. Il se compose des éléments de la pile commençant par le successeur identifié et se terminant par le haut de la pile. La première profondeur de parcours de tous les chemins simples est similaire à la recherche de profondeur d'abord, mais vous ne marquez/enregistrez pas de nœuds visités autres que ceux actuellement sur la pile en tant que points d'arrêt.

L'algorithme de force brute ci-dessus est terriblement inefficace et génère en outre de multiples copies des cycles. C'est cependant le point de départ de plusieurs algorithmes pratiques qui appliquent diverses améliorations afin d'améliorer les performances et d'éviter la duplication de cycle. J'ai été surpris d'apprendre il y a quelque temps que ces algorithmes ne sont pas facilement disponibles dans les manuels et sur le Web. J'ai donc fait des recherches et mis en œuvre 4 algorithmes et 1 algorithme pour les cycles dans les graphes non dirigés dans une bibliothèque open source Java ici: http://code.google.com/p/niographs/ .

BTW, puisque j'ai mentionné les graphes non dirigés: L'algorithme pour ceux-ci est différent. Construisez un arbre recouvrant, puis chaque bord qui ne fait pas partie de l’arbre forme un cycle simple avec quelques arêtes dans l’arbre. Les cycles trouvés de cette façon forment ce qu'on appelle une base de cycle. Tous les cycles simples peuvent ensuite être trouvés en combinant 2 cycles de base distincts ou plus. Pour plus de détails, voir par exemple ceci: http://dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf .

22
Nikolay Ognyanov

Le choix le plus simple que j'ai trouvé pour résoudre ce problème utilisait la python lib appelée networkx.

Il implémente l'algorithme de Johnson mentionné dans la meilleure réponse de cette question mais il est assez simple à exécuter.

En bref, vous avez besoin des éléments suivants:

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

Réponse: [['a', 'b', 'd', 'e'], ['a', 'b', 'c ']]

enter image description here

18
fernandosjp

Clarifier:

  1. Les composants fortement connectés trouveront tous les sous-graphes comportant au moins un cycle, pas tous les cycles possibles du graphique. par exemple. Si vous prenez tous les composants fortement connectés et que vous réduisez/regroupez/fusionnez chacun d’eux en un seul nœud (c’est-à-dire un nœud par composant), vous obtenez un arbre sans cycles (un DAG en fait). Chaque composant (qui est fondamentalement un sous-graphe contenant au moins un cycle) peut contenir beaucoup plus de cycles possibles en interne, donc SCC ne trouvera PAS tous les cycles possibles, il trouvera tous les groupes possibles comportant au moins un cycle, et si vous les groupez, le graphique n'aura pas de cycles.

  2. pour trouver tous cycles simples dans un graphe, comme d'autres l'ont mentionné, l'algorithme de Johnson est candidat.

5
Eran Medan

Les variantes basées sur DFS avec bords arrières trouveront effectivement des cycles, mais dans de nombreux cas, ce ne seront PAS des cycles minimaux . En général, DFS vous indique qu’il existe un cycle mais qu’il n’est pas suffisant pour en trouver. Par exemple, imaginez 5 cycles différents partageant deux arêtes. Il n'y a pas de moyen simple d'identifier les cycles en utilisant uniquement DFS (y compris les variantes de retour en arrière).

L'algorithme de Johnson donne en effet tous les cycles simples uniques et présente une bonne complexité dans le temps et dans l'espace.

Mais si vous voulez juste trouver des cycles MINIMAUX (ce qui signifie qu'il peut y avoir plus d'un cycle passant par n'importe quel sommet et que nous souhaitons en trouver des minimaux) ET que votre graphique ne soit pas très grand, vous pouvez essayer d'utiliser la méthode simple ci-dessous. C'est TRÈS simple mais assez lent comparé à celui de Johnson.

Ainsi, l'un des absolument moyen le plus simple de trouver des cycles MINIMAL consiste à utiliser l'algorithme de Floyd pour rechercher des chemins minimaux entre tous les sommets à l'aide de la matrice d'adjacence. Cet algorithme est loin d'être aussi optimal que celui de Johnson, mais il est tellement simple et sa boucle interne est si étroite que pour des graphes plus petits (<= 50-100 nœuds), il est tout à fait judicieux de l'utiliser. La complexité temporelle est O (n ^ 3), la complexité spatiale O (n ^ 2) si vous utilisez le suivi des parents et O(1) si vous ne l'utilisez pas. Tout d'abord, trouvons la réponse à la question s'il y a un cycle. L'algorithme est extrêmement simple. Ci-dessous un extrait de code en 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
      }
    }
  }

A l'origine, cet algorithme fonctionnait sur un graphe à arête pondérée pour trouver tous les chemins les plus courts entre toutes les paires de nœuds (d'où l'argument des pondérations). Pour que cela fonctionne correctement, vous devez fournir 1 s'il existe un Edge dirigé entre les nœuds ou NO_Edge dans le cas contraire. Une fois l'algorithme exécuté, vous pouvez vérifier la diagonale principale. S'il existe des valeurs inférieures à NO_Edge, ce nœud participe à un cycle de longueur égale à la valeur. Tous les autres nœuds du même cycle auront la même valeur (sur la diagonale principale).

Pour reconstruire le cycle lui-même, nous devons utiliser une version légèrement modifiée de l'algorithme avec suivi des parents.

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

La matrice parent doit initialement contenir un index de sommet source dans une cellule Edge s'il existe un bord entre les sommets et -1 sinon. Après le retour de la fonction, vous aurez une référence au noeud parent dans l’arborescence de chemin le plus court. Et puis, il est facile de récupérer les cycles réels.

Dans l'ensemble, nous avons le programme suivant pour trouver tous les cycles minimaux

  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
  }

et une petite méthode principale juste pour tester le résultat

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

et la sortie est

The following minimal cycle found:
012
Total: 1 cycle found
3
Kirill Frolov

On m'a posé cette question une fois lors d'une interview, je suppose que cela vous est arrivé et que vous venez ici pour demander de l'aide. Divisez le problème en trois questions et cela devient plus facile.

  1. comment déterminez-vous le prochain itinéraire valide?
  2. comment déterminez-vous si un point a été utilisé
  3. comment éviter de traverser à nouveau le même point

Problème 1) Utilisez le modèle d'itérateur pour fournir un moyen d'itérer les résultats de l'itinéraire. Le "moveNext" de votre itérateur est probablement un bon endroit pour mettre en logique le prochain itinéraire. Pour trouver un itinéraire valide, cela dépend de la structure de vos données. Pour moi, il s’agissait d’une table SQL remplie de possibilités de routes valides; j’ai donc dû créer une requête pour obtenir les destinations valides à partir d’une source.

Problème 2) Poussez chaque nœud que vous trouvez dans une collection au fur et à mesure que vous les récupérez. Cela signifie que vous pouvez voir si vous "redoublez" très facilement sur un point en interrogeant la collection que vous construisez à la volée.

Problème 3) Si, à un moment quelconque, vous constatez que vous redoublez, vous pouvez extraire des éléments de la collection et les "sauvegarder". Ensuite, à partir de ce moment, essayez à nouveau d’avancer.

Hack: si vous utilisez SQL Server 2008, vous pouvez utiliser de nouvelles "hiérarchies" pour résoudre rapidement ce problème si vous structurez vos données dans une arborescence.

3
slf

Dans le cas de graphe non orienté , un article récemment publié (Liste optimale des cycles et des st-trajets dans les graphes non orientés]) offre une solution asymptotiquement optimale. Vous pouvez le lire ici http://arxiv.org/abs/1205.2766 ou ici http://dl.acm.org/citation.cfm?id=2627951 I sachez que cela ne répond pas à votre question, mais puisque le titre de votre question ne mentionne pas la direction, il peut toujours être utile pour la recherche Google

2
daureg

Si vous voulez trouver tous les circuits élémentaires d’un graphique, vous pouvez utiliser l’algorithme EC de JAMES C. TIERNAN, trouvé sur un papier depuis 1970.

L'algorithme EC très original , car j'ai réussi à l'implémenter en php (j'espère qu'il n'y a pas d'erreur). Il peut aussi trouver des boucles s'il y en a. Les circuits de cette implémentation (qui tente de cloner l'original) sont des éléments non nuls. Zéro ici signifie non-existence (nul comme nous le connaissons).

Hormis ce qui suit, suit une autre implémentation qui donne à l'algorithme plus d'indépendance, cela signifie que les nœuds peuvent démarrer n'importe où, même à partir de nombres négatifs, par exemple -4, -3, -2, etc.

Dans les deux cas, il est nécessaire que les nœuds soient séquentiels.

Vous devrez peut-être étudier le document original, Algorithme de circuit élémentaire 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);
?>

alors c’est l’autre implémentation, plus indépendante du graphe, sans goto et sans valeurs de tableau, mais elle utilise les clés de tableau, le chemin, le graphique et les circuits sont stockés sous forme de clés de tableau (utilisez les valeurs de tableau si vous le souhaitez, il suffit de modifier le paramètre requis. lignes). L'exemple de graphique commence à partir de -4 pour indiquer son indépendance.

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

?>

J'ai analysé et documenté la CE mais malheureusement la documentation est en grec.

1
Melsi

Commencez au nœud X et recherchez tous les nœuds enfants (les nœuds parent et enfant sont équivalents s'ils ne sont pas dirigés). Marquez ces nœuds enfants comme étant des enfants de X. A partir de tout nœud enfant A, indiquez que ses enfants sont des enfants de A, X ', X' étant marqué comme étant à 2 pas.). Si vous appuyez plus tard sur X et le marquez comme étant un enfant de X '', cela signifie que X est dans un cycle de 3 nœuds. Il est facile de revenir à son parent (tel quel, l'algorithme ne prend pas en charge cette opération, vous pouvez donc trouver le parent qui a X ').

Remarque: Si le graphe n'est pas dirigé ou comporte des arêtes bidirectionnelles, cet algorithme devient plus compliqué si vous ne voulez pas traverser le même bord deux fois pendant un cycle.

1
Brian

ne pouvez-vous pas créer une petite fonction récursive pour parcourir les nœuds?

readDiGraph( string pathSoFar, Node x)
{

    if(NoChildren) MasterList.add( pathsofar + Node.name ) ; 

    foreach( child ) 
    {
       readDiGraph( pathsofar + "->" + this.name, child) 
    }
}

si vous avez une tonne de nœuds, vous allez manquer de pile

1
astronought

Il existe deux étapes (algorithmes) impliquées dans la recherche de tous les cycles dans un DAG.

La première étape consiste à utiliser l'algorithme de Tarjan pour trouver l'ensemble des composants fortement connectés.

  1. Commencez par n'importe quel sommet arbitraire.
  2. DFS de ce sommet. Pour chaque nœud x, conservez deux nombres, dfs_index [x] et dfs_lowval [x]. dfs_index [x] stocke le moment où ce nœud est visité, tandis que dfs_lowval [x] = min (dfs_low [k]) où k est tous les enfants de x qui n'est pas directement parent de x dans l'arbre dfs-spanning.
  3. Tous les nœuds avec le même dfs_lowval [x] sont dans le même composant fortement connecté.

La deuxième étape consiste à trouver des cycles (chemins) dans les composants connectés. Ma suggestion est d'utiliser une version modifiée de l'algorithme de Hierholzer.

L'idée est:

  1. Choisissez n’importe quel sommet de départ v et suivez un tracé des arêtes de ce sommet jusqu’à ce que vous reveniez à v. Il n’est pas possible de rester bloqué sur un sommet autre que v, car le degré pair de tous les sommets garantit que, lorsque le tracé entre dans un autre. sommet w il doit y avoir un bord non utilisé laissant w. Le tour formé de cette manière est un tour fermé, mais ne couvre pas tous les sommets et les arêtes du graphe initial.
  2. Tant qu’il existe un sommet v appartenant au tour en cours mais dont les arêtes adjacentes ne font pas partie du tour, commencez un autre tracé à partir de v, en suivant les arêtes inutilisées jusqu’à ce que vous reveniez à v, et rejoignez le tour ainsi formé au tour précédent.

Voici le lien vers une implémentation Java avec un scénario de test:

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

1
stones333

DFS à partir du noeud de démarrage s, gardez une trace du chemin DFS lors de la traversée et enregistrez le chemin si vous trouvez un Edge à partir du noeud v dans le chemin d'accès à s. (v, s) est un bord arrière de l'arborescence DFS et indique donc un cycle contenant s.

0
Xceptional

Je suis tombé sur l'algorithme suivant, qui semble être plus efficace que l'algorithme de Johnson (au moins pour les graphes plus volumineux). Je ne suis toutefois pas sûr de ses performances par rapport à l'algorithme de Tarjan.
En outre, je n’ai jusqu’à présent vérifié que les triangles. Si cela vous intéresse, veuillez consulter "Algorithmes de listage d'arboricité et de sous-graphes" de Norishige Chiba et Takao Nishizeki ( http://dx.doi.org/10.1137/0214017 )

0
Shadow

En ce qui concerne votre question sur le cycle de permutation , lisez-en davantage ici: https://www.codechef.com/problems/PCYCLE

Vous pouvez essayer ce code (entrez la taille et le nombre de chiffres):

# 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
Mohamed Amine Phys

Solution Javascript utilisant des listes chaînées disjointes. Peut être mis à niveau pour séparer les forêts d'ensembles pour des temps d'exécution plus rapides.

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
Ling Qing Meng