Aller au contenu

5) 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"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
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"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
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

.128013A_4:2!-.Sw3B/]7bpPiNq{oF1tl(9 ;=vTm6u8sCfrg}e[hEcD05a,nkydx)050*0T0A0#0t0B0N0E0X0B0#0N0N0G010A0t0r010406050N0L0J0J0#0Q0)040j0x0B0L100x0%050n17191b1d150r04051t1m1w0n1t150*0t0H0^0`0|0~0`0%0R0L0#0R0T0h0r0)0A0V1k0E0V0t0R0V0B1Y0V0A13050:0q0B0T1F0{0}011X1Z1#1Z0A1+1-1)0A0Q1u1T0^1g0N0r0#0%0~0f011/1H010P0=0T0%0#0J0T1)24262b1;2e1-2h2j130a0E0s0Q0x0r0x0N0t1j0%0E0.220Q0Q0T0X2E1m2m0%1u0n1T2R1~201 1*0*2o1I0t0%2g2B1)1C1E0_1:2#2%0%0x2+1)0r2K1u2P2R2{16252F2-2c2;0Q1a0B1)0#1W2K0P0~030c0c0X2=0T1#2:0x0h0Z0h0z130E0z1m0#2|2 142~2n311;333537390T3b013d3f3h3j2(3m0h29040E0f3t3v263x2P2!013C0#361u380V3a3c3e3g0.3M2;3O0l3q0l3U2O3w153Y3A0~3#3%053)3+3I3-3L2$3N3n0d3q0d3_1n3{3y301G3B0x343$3E3*3G3,3K3/483;3n0!3q0!4e2{3|2 3Z404o443J3.3i4u3l3n0K3q0K4A4g3}4j3 4l3D3(3F3H4I473k3O0p3q0p4R3W4C3z4U3!4W4n4Y4p4!464t4%3n0M3q0M4,2Q4.4i2.4;4m41434q454s4K4|0h0D3q0D513X4D3~564X424Z4r4J3:4M3o0Z130z0Z5j534E4=585q5b5s4L3O0z3p045K5A4h5C574G5a4#4{493o3Q0z3T0n3u3`4-5P5m4F4@4H4`5d5W0z3?5M3^5#3V525)4:5+5p4^5r4$5:4b5M4d5^5%5`4T555}594_5c5t5J4x5M4z664f5(69325D5S6d5H5e0z4O5M4Q6k4B5{6a6p5,5T5.6f3n0z4)5M4+6y4S5l5|6C5~5-6e5I6H4~5M506M3W1x2_1m2+2U0*202Z5m4J2*1D1u2^0T2`3w671u4J6@2n0t0*0~3e2P5J3E6~706T6t2a2s0T766s5:1)666n1;0(130.0P6_6A2c0k3q7n7h3 0P130T0+0t0N0A0T3f0)0_0T7s6O5512040C7H4/6a130R0Q0#0r0V7G6l2Q7o1;7K0$6_0E7Z3 7k0T250Q0A7N542c7K0,0e6_157X6|2F7501712 3O3Q5p7~6F6U3P792i7b7 775W835^0E8h7(7t3!136=0.0X0T0|0T0L0Q0N7%7)010x130G8w8k7K0w7:4E7+7-7/7{8j7I7=137^8L8x0(0X130u1k7W2}8D130S7_8G850c723n5=846 8c7d4v0h3?0E7a7c618=8-8g8i8x0%8m2f8C8N1;8z048B8R8!040U8G5*8I1b8K8Z950~7K0o8%7{5P8)8+0h638.8`5V8=4b8^8a9w5/9y7f3u8i8M7O2c7j040k1X1-949J3B0q132r9e4:7K7M9p8k91040r939!9k017?9Q7;96130g992{9I9/0~0J0t135z9*9R9l8P9o9j6}8/80263O6h9v8:8{5u4x9A2j9C6G0haa8~9H8h90130N0x190/9.3Z979?3w9^8H9%9)a49_8y130i9W7P9%2zaJ8O7L0,a36^3Y9r814N74a68d8=4Oag8b865e6v3Uan8 8k9L0P4lav9f040H0x7z2$a=4:0x7q04a{9a9+9$7R7T7VaN7!139d9 aF9$arat9iaS9+9m8Q6zbc0EaUa83n6Jaba(5W4)a$ai87bsama,a-9+9L0t7mb2a09,139ZaEaBa^a`1lbIaFa~13b19@ap9%2K8o8q0N8s8ub8a104aQbSawa 260*a|7JbLb*8la@a_2CbRbX8k979=b?328mb!2Lb$b(8vbn5m7KbbbNa?bf2jbh6!9b0o7@aRck4Dbp0%3O6WbtaZ5u4~bxac9xcx9F3RbCcF8S132K0A8tb~az8S8U040I0Q0L8Y4gbncr3O5g4Y8)8;5u5gczbu8=cZa+cFbDbJ9L4KbHb b313bPb}c396a 2;cj2QaA5mbUb0cM3Wd25|c50Tb#8r8tcacf9Xa27{7`bNcX6H5wc!aYc$5J5wc)cwdscDc.bCbY8nc7ddb)cbdh9cb_9$c`bWbibJ9mc|0~axdPb`chaudj8(aY9s5KaXby6t3pdudr6H5Lc-dy9HdAaDdMbTaHdI137T0r2gb=dFb@7Ld^b{bQb_9-dWcWdYaV5Xd#cA9D5u5Z89ahecajef2R9Gc/aF9LcJcLdS8T130y3$b%7%dkd=boe8bq3o8-38c#ad5J8@8_ei875;cDezcpa576dZ9ueGdqeI6H9zeLc*ee9u7g9+0X5L030E0I8q0AdfcVdleCcs6HaaeWd$5:afe#dve`cDbYb57U7C6jc@bJdRb.cc138Fd~2c0N3Q020v0L0x0A0F0bfkfmfoe48PceeA5mfi13frfn0F0mfBftfgb9047$fc4:fz04fF0F0OfPfu040ofKf9aFfNfPfEflfCfT0efweRfYfjf$fofqf.0FfTfWcN8kfZf;0WfSfHb+fVdSf`fsfQf}dgd f)b_g2fCf:g3fTg0fL55gafof|f;f(f*7Yf_f-g3f#gdf~bKfU8$e6e@eTe96uebe$5Ja#f0d*3oa*e)bJb47Sf60c6xfXaw8AdS8Eg9gqgbg5fxdGg8gugifDg!f+3Z7#g1gYfofRglgu9mf@d78xg)gsf%g@fvgXfAf;gcg~g6aOgfgSfyg:g4g?h6fIg%he0~g)h4fGhhgvg_d1g{hb0Yg+gobj13ho3Rhqh2g3gkgthmg^g/hAfChshdg#g7gVbah1fOf;g=hDhLaOhxd8ghhbhCh5hUfIh8f^9+g)h!hlh$b+hgh.01g)hSh#g,fdfJhGhQg3hJhTh_dG0ogxbmgz8cdZbse|eM6tbwgHeY3obA5$a.d_8r7.dS9$7x7z7B7D7FfTbMh;gNb6f7f?h|fPhkf=g b,dSaxayg`a.cPev0?cU5(e7gAeD0zcuiagE6Vega%f13ocugLeoikb%imggc404ip7A7C0X7E9PiFivi1aKf57VgQiAi-1;hjht7|g-13b-h9a}8AiJhpiL13cRcTco7Y0n6{6#6?6%6:1m0A6*jq2X2S0#1,jn0n6(7`0.0:0=0N04.

Crédits⚓︎

Romain Janvier, Nicolas Revéret


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