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 :

graphe 1

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é

graphe non orienté A-B-C

Ici le graphe est orienté, un circuit 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 :

graphe 1

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 :

graphe 5

graphe_6 :

graphe 6

###(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

.128013f6S=dNpg2Bm!C{ 8P5)ku}A_sh0:Fq4yrE./oTxDbc1w937ve[l,ti]n;a(050f0X0#0*0$0Z0z0p0Q0Z0*0z0z0e010#0$0h010406050z0v0l0l0*0H0G040d0L0Z0v0 0L0(050K16181a1c140h04051s1l1v0K1s140f0$0W0@0_0{0}0A0$0i0A0Z1J0A0#12050/0P0Z0X1E0`0|011I1K1M1K0#1S1U1Q0#0H1t0#0A0@1f0z0h0*0(0}0j011W1G010b0;0X0(0*0l0X1Q1?1^1}1Y201U2325120a0p0r0H0L0h0L0z0$1i0(0p0-1;0H0H0X0Q2q1l280(1t0K1/2D1,1.1-1R0f2a0}1M0(222n1Q1B1D0^1X2N0$2P0(0L2T1Q0h2w1t2B2D2+151@2r2V1~2!0H190Z120p0R2A2/132.292;1Y2?2^2`0j2}1^2 2B2M01340*2_040p0U382C143b320}3e3g0p0F3k3a2/3c3q2`0s3u3m3w3o3d0L2@3f2`0c3B302:1F333G353h0V3L3n3O3p3Q3I3h0q3U3D3W3F3H3r0T3$313(3y040R0B3-3N2W3)3R0R2|1m2~3C3.3_3:0R373~39403^2=3Y3g0R3j463l3M3x4b120R3t4f3v414a3*4k3A4n484i4r3;3K4u4h3E433T4A3V424j3;3#4n1w2)1l2T2G0f1.2L3E0Q2#261t4P1u4N2-4L4V0-2*3%3_0u120-0b3u4B3(0S2`4;4G2=0b120X0N0$0z0#0X0y0Q0G0^0X4_4+1~11040+594p33120i0H0*0h0A584L4`1Y5c0!3u0p4=424.0X1@0H0#5f495r120t0C3B0p5K5v5q3p122(0X0-0Q0X0{0X0v0H0z5u5w1~0L120e5!5N015c0o5D3x5y5A5C4n5M5a5F045I5@5#1Y0u0Q120g1j5o2-5+5c0w5J5L5~5O040h215*5_0}5%045)5}67120Y5/4C5;1a5?666i5,120%6a5K6c014-040S1I1U6h5g3p0P122d6r3(5c5e5p6x0(5P6g6U6L6y040t6K5E6j120m6m2+5^6!0l0$123?6Z6)6#5|2+065L6~6/6_6W040z0L180.6(3c6k6-2~705:6e6Y6w6!6k0J6Q5x6e2l7l5b120+6%4u6 6b5+6F0b3G786s040W0L502Y7B3(0L4@047H6n6V5i5k5m652~6D5c6q6^7e74766v7U6o040%6{3 7v7v6D6F0$4:7O6!6S7p5h7D7F2o1k7?6_7K127N6.6D725Q5S5U0z5W5Y7_0}5c7t845+81041^0f7I3_7^7Y7C7E7G7~8h6x6k6,8n2=5P2w885V5X5Z8q6R6p8d3d127!257$397V6z5H6B7-7.7x122w0#5X8u7c7/61040M0H0v7T478V6~7/4}1M7=8v6!728s7}8z1Y8j2!8P2C7d3E8j838%5+868C2x898b8G7h6_5c7+8/8:8W7P6e9b5T8E8c8H8o8J9t8A7{8t8K5c6A7 795(8~6d8N777u9l6 856X6J9w8 127k9Q6d5l0h228m9U6#6T9g7e8|978Q7(8g7,7w6x6F8Z8#9G6E8)0D3f8a5u064v4U0R12030p0M5U0#9f3 9N045j5l5n0y4t8_809F9D3E5-8K0z1{04020E0v0L0#0)0xaratav9A120C7X9%3Eao12ayau0)0kaJaA9!5s9@aHaqasaK0naN0)aB7)5tak3(aSaXaMaUaOaF8I5{aE7%6xa(a+awaXaZa#ah3ca?az0)0Ia_aP6za{98a=apaXaWa@aZaDanb8a@axbbb3a!aRbfa b1bia-9ua/beaIa@a*a aZ0%694F9nac7S0y4za|95ajbHa.5.9!a~aKbhbxbjbdbNbmaKbwaKa`blbua babRbq7qbka$3_bOavbXa,a;7@aCa:9+b7b#bPb2b)5`0%b53994a%bVavb%bYbSb^2C6Db.a^bpb=9h12c193ccc50)0Ob}cg3caQb,1~cdbob(cqalb4b!aTa cocfb_b?5{9@7WbtcCaVcpcGch04cj3hclb{avcwc8b~8ecAct1YcvcOcb7(bTcZ01cdc7b;cPcrcicBaXcEcxc=cz7)bA6|8=8k5V5B9@724~505254569Pc-8pc-72bDaeagcya.cSc3b-cmbQcYdkbr9-c26D7a7bdv7x9_9{8.4g8Xd28ad4c$6dd751535557aZ9$ds9xdh53bGdS5`dmcUcMavdqc;c*6x8f9@dx9@60128+8-3L0K4(0X2D5Q2D4Z2E4R1l2He00*1Td_4O1C2 0K0-0/0;0z04.

Crédits⚓︎

Romain Janvier, Nicolas Revéret


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