Aller au contenu

Circuits et cycles

Les graphes orientés

On parle de circuits pour les graphes orientés

Exemple de graphe orienté avec circuit

circuit

Exemple de graphe orienté sans circuit

pas circuit

Les graphes non orientés

On parle de cycles pour les graphes non orientés

Exemple de graphe non orienté avec cycle

cycle

Exemple de graphe non orienté sans cycle

pas cycle

graphes acycliques

  • Un graphe non orienté acyclique est un graphe qui ne contient pas de cycle.
  • Un graphe orienté acyclique est un graphe qui ne contient pas de circuit.
Premiers essais

Nous nous plaçons dans le cas d'un graphe connexe.
Nous désirons savoir si ce graphe contient au moins un cycle, ou un circuit.
L'idée est la suivante : on parcourt le graphe en profondeur (on avance le plus loins possible vers la profondeur) à partir d'un sommet choisi arbitrairement. A chaque fois que l'on rajoute un sommet à notre parcours, nous regardons ses voisins. Si parmi ses voisins il y a un sommet que l'on a déjà parcouru, c'est que le parcours se referme, il existe donc un cycle. Si on a terminé le parcours du graphe sans jamais avoir été dans cette situation, on en déduit que le graphe ne contient pas de cycle.

graphe_1 :

graph LR
    A --- B

graphe_2 :

graph LR
    A --> B

Nous reprenons donc l'algorithme du parcours en profondeur itératif, auquel on a ajouté les lignes 12 et 13 pour détecter l'existence d'un cycle ou d'un circuit.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Ctrl+Clic pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Que s'est-il passé?

Lorsque l'on parcourt graphe_1 en partant de "A", on parcourt "A", puis on arrive en "B". "A" fait partie des voisins de "B" et donc un cycle est détecté. Par contre, dans graphe_2, "A" ne fait pas partie des voisins de "B", et il n'y a pas de cycle détecté à ce stade.

graphe_1 aurait pu être représenté ainsi si on le considérait comme un graphe orienté :

graph LR
    A --> B
    B --> A
Ainsi, avec le script précédent, on détecte l'existence d'un cycle, ce qui est normal si on considère ce cycle comme orienté.

Graphe orienté ou pas?

Nous constatons donc que l'algorithme doit être différent suivant que l'on travaille sur un cycle orienté ou pas.

Ici le graphe est non orienté, aucun cycle ne doit être détecté

graph LR
    A --- B
    B --- C

Ici le graphe est orienté, un cycle doit être détecté

graph LR
    A --> B
    B --> A
    B --> C

Cas des graphes non orientés

👉 Nous allons nous limiter à la recherche de présence de cycle dans un graphe non orienté connexe1.

Comment détecter la présence d'un cycle dans un graphe non orienté

Reprenons l'exemple de graphe_1

graphe_1 :

graph LR
    A --- B
Nous parcourons ce graphe en profondeur en partant de A. Lorsqu'on arrive en B, on découvre A comme voisin, mais le graphe n'étant pas orienté, il ne faut pas considérer ce nouveau voisin qui est le résultat de l'arc de retour.
Il suffit donc de reconnaître que A est le prédécesseur de B, pour ne pas considérer que A - B - A est un cycle.

👉 Comme nous avons déjà dû le faire pour la recherche de plus court chemin, nous allons donc construire le dictionnaire des prédécesseurs de chaque sommet.

Nous nous plaçons dans le cas d'un graphe non orienté connexe. On parcourt le graphe à partir d'un sommet choisi arbitrairement. A chaque fois que l'on rajoute un sommet à notre parcours, nous regardons ses voisins. Si parmi ses voisins il y a un sommet que l'on a déjà parcouru, et qui évidemment n'est pas celui dont on vient, c'est qu'il existe un cycle. Si on a terminé le parcours du graphe sans jamais avoir été dans cette situation, on en déduit que le graphe ne contient pas de cycle.

L'algorithme

Nous utilisons un parcours en profondeur. Au fur et à mesure de la progression, nous construisons le dictionnaire predecesseurs, qui prend pour clés les sommets visités, et pour valeur associée à chaque clé, le sommet d'où l'on vient. On arrete le parcours, et renvoie True si le voisin d'un sommet a déjà été parcouru, et si ce n'est pas celui d'où l'on vient.

Si le parcours entier a été effectué, la fonction renvoie False

Il n'est pas nécessaire de conserver la liste des sommets parcourus, comme nous l'avions fait dans le parcours en profondeur.

Nous allons utiliser le parcours en profondeur itératif.

À vous de jouer

Compléter le script ci-dessous

Les graphes utilisés pour les tests sont :

graphe_5 :

graph LR
    A --- B
    B --- E
    A --- C

graphe_6 :

graph LR
    A --- B
    A --- C
    C --- D
    C --- E
    E --- D

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Ctrl+Clic pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5

.128013CyB1{]/wi_}qPr 7F9fg;2h)(EpNnD0ldT,4Ake!bmc:35a=o[.8t6sSuxv050H0N0#0V0j0G0%0p0R0G0V0%0%0W010#0j0B010406050%0)0Q0Q0V0o0c040(0X0G0)0 0X0D050h16181a1c140B04051s1l1v0h1s140H0j0+0@0_0{0}0x0j0u0x0G1J0x0#12050/0P0G0N1E0`0|011I1K1M1K0#1S1U1Q0#0o1t0#0x0@1f0%0B0V0D0}0w011W1G010t0;0N0D0V0Q0N1Q1?1^1}1Y201U2325120a0p0n0o0X0B0X0%0j1i0D0p0-1;0o0o0N0R2q1l280D1t0h1/2D1,1.1-1R0H2a0}1M0D222n1Q1B1D0^1X2N0j2P0D0X2T1Q0B2w1t2B2D2+151@2r2V1~2!0o190G120p0e2A2/132.292;1Y2?2^2`0w2}1^2 2B2M01340V2_040p0T382C143b320}3e3g0p0K3k3a2/3c3q2`0U3u3m3w3o3d0X2@3f2`0$3B302:1F333G353h0q3L3n3O3p3Q3I3h0!3U3D3W3F3H3r0s3$313(3y040e0F3-3N2W3)3R0e2|1m2~3C3.3_3:0e373~39403^2=3Y3g0e3j463l3M3x4b120e3t4f3v414a3*4k3A4n484i4r3;3K4u4h3E433T4A3V424j3;3#4n1w2)1l2T2G0H1.2L3E0R2#261t4P1u4N2-4L4V0-2*3%3_0M120-0t3u0p4B3/0t120N0*0j0%0#0N0k0R0c0^0N3u4?3_11040z554G2=120u0o0V0B0x544L5c1Y580J4;565d040-1@0o0#5b4+1~580y0S3B0p5F4=5m3p122(0N0-0R0N0{0N0)0o0%5q5I010X120W5V5z5n120f5y4p334.0N5v5x4n5H5$0}585D5;5r1Y0M0R120C1j5k2-5W580l5E5G5{5J040B215#5+0}5Y045!5`64120Y5*495,5t5.1a5:635?01580g675F69014-040i1I1U6e6p3p0P122d6o3c585a5l6w0D5K6d6T6f6x120y6J3c6h0O6j2+5=6Z0Q0j123?6Y6K6!045_2+065G6}6-6@6V040%0X180.6%3E6h6+2~6 3x6W6I6?6(120Z6P4C5K2l7k3(6R6$4u6~685W6E0t3G773/120+0X4|2Y7z3_0X0i127F6k6U5e5g5i622~6C586n7g7l7274256u7S6l040g6`3 7t7t6C6E0j4:7M6Z6R7o427B7D2o1k7=6@7I7K7}6,6C715L5N5P0%5R5T7^5A6#7G1~80041^0H8f5%598c6q7C7E827b6C6)7a397c7X862x888a5U7W7p6m8o6a73757#397T120g5C6A7,7-7v122w0#5S8s8x7.5~040I0o0)7R478T6}7.4_1M7;835W718q7|8l6g7J042!8M2C8y3(8h7L8@7N6b2w875Q5S8E6v7?127*8-8.8U988A5O9c8b8F578H9r5s8`967$6w6y8|5X5Z9B718K7!8S9k6B8^7e8,2C8u7i8I3d125h0B228k9u8m6S9f707`8r9R5B8S8:048X8Z9B5}120r3f894;064v4U0e12030p0I5P0#9e3 847O5h5j0k4t976Z799B585)9Y0}0%1{04020m0)0X0#0v0Lanapar9)9h7V9#3cak12auaq0v0daEawai6^5p7~aBalaI0v0baQax7(aMac6@aCamaoaFaHa#aJaA3E5^az9y6ZaZaQata(0vaUaW8t5Wa:a?0AaTaK6ya_8#a{aPa?aSa?aU0Sa-8Nb4aDa?a=ava@b08Pb2926Ca|bha~b8bj6_bb9Obda!bha%bhaU0g664F985fa84 4zaX7h6iaf5(9RboaFbgaFb9bu4*a/b5bya a*8G04bl3hbnbYaFb7bAbs0gb(933_bQarbzbTbsbabPb+arbSa)a.6@b19Bb@aRb!c26Qayb}bebhc0bib#9sb%c5b~0v0Ec8bc9z12b;b*cdaFbqb.ch8daVckcuarcnbrcy8m9ibm7%bVctbxb,cobvcqcjaN3Ec6cwb`cG5@bkcBcNarcWc1cp9gbtccc$c7cFc9a+crc#aQcEcxc;b$bC3@3c6E1X0N5w9E4_4{4}4 5153aU9!c{7_04bG7Qaaa^c@bfcPbWc38ecT945Z8wcJ6w9;049?0=9N4o6@d05Qd3dqde4`4|4~50527fcY6^dcc*9$df7Pa9bJddczcsbwa;dm8O047rbK78ds9:8%8)8+3L0h4(0N2D5L2D4Z2E4R1l2Hd~0V1Td@4O1C2 0h0-0/0;0%04.

Crédits⚓︎

Romain Janvier, Nicolas Revéret


  1. Voir : Les graphes - Structure au paragraphe 1.2.4 : Structures de graphes