Mme Tortue et M. Lièvre font la course

On s'intéresse dans cet exercice à des listes chaînées. Une cellule d'une liste chaînée est une instance de la classe Cellule ci-dessous.

La classe Cellule
🐍 Script Python
class Cellule():
    def __init__(self):
        self.suivante = None

On fournit un exemple d'utilisation créant une liste chaînée contenant 18 éléments. Étudiez bien ce programme pour comprendre la suite et testez vos modifications pour bien comprendre la structure de la liste.

🐍 Script Python
tete = Cellule()

actuelle = tete
for i in range(1, 18):
    actuelle.suivante = Cellule()
    actuelle = actuelle.suivante

Le plus souvent, la dernière cellule n'a pas de successeur. Dans ce cas, l'attribut suivante de la cellule en question vaut None.

Il arrive toutefois que cet attribut pointe vers une des cellules précédentes, ce qui forme une boucle. Le parcours d'une telle liste chaînée ne se termine jamais.

Une liste chaînée comportant une boucle

graph LR
    0[0]-->1[1]
    1[1]-->2[2]
    2[2]-- ... de 3 à 10 ... -->11[11]
    11[11]-->12[12]
    12[12]-->13[13]
    13[13]-->14[14]
    14[14]-->15[15]
    15[15]-->16[16]
    16[16]-->17[17]
    17[17]-->13[13]

Les numéros correspondent à la position de la cellule dans la liste chaînée.

Cette liste chaînée peut être construite par les instructions suivantes :

🐍 Script Python
tete = Cellule()

actuelle = tete
for i in range(1, 18):
    actuelle.suivante = Cellule()
    actuelle = actuelle.suivante
    if i == 13:
        debut_boucle = actuelle

actuelle.suivante = debut_boucle  # la suivante de la n°17 devient la n°13

Il existe un moyen très simple de détecter une boucle. Cet algorithme est dû à Robert Floyd.

Supposons deux variables lievre et tortue qui prennent comme valeurs des cellules d'une liste chaînée, en commençant par la tete. tortue passe d'une cellule à sa suivante alors que lievre passe directement d'une cellule à la suivante de sa suivante. Autrement dit, lievre fait deux pas quand tortue n'en fait qu'un.

On représente dans le tableau ci-dessous les positions respectives de lievre et tortue au fil du parcours. Le tableau s'arrête lorsque tortue == lievre.

Etape 0 1 2 ... 6 7 8 9 ... 14 15
Position de tortue 0 1 2 ... 6 7 8 9 ... 14 15
Position de lievre 0 2 4 ... 12 14* 16 13 ... 13 15

Lorsque lievre entre dans la boucle (noté avec une astérisque *), il se met à tourner en rond. Au bout d'un moment, tortue le rattrape et se trouve sur la même cellule que lui. Quand la condition lievre == tortue devient vraie, nous savons qu'il y a une boucle.

Si par contre, lievre atteint la fin de la liste chaînée avant d'avoir été « rattrapé » par tortue, la liste ne contient pas de boucle.

Pourquoi ça marche ?

Appelons \(\lambda\) le nombre de cellules dans la boucle. Dans notre exemple, \(\lambda = 5\).

graph LR;
    0[0]-->1[1]
    1[1]-->2[2]
    2[2]-- ... de 3 à 10 ... -->11[11]
    11[11]-->12[12]
    12[12]-->13[13]
    subgraph "Boucle (λ = 5)"
    13[13]-->14[14]
    14[14]-->15[15]
    15[15]-->16[16]
    16[16]-->17[17]
    17[17]-->13[13]
    end

De manière générale, nous savons que lorsque tortue a effectué \(x\) pas, lievre en a effectué \(2x\). De plus, à partir d'une des cellules comprises dans la boucle, si lievre avance d'un multiple de \(\lambda\) cellules, il fait un tour complet et se retrouve sur la même cellule.

Lorsque tortue arrive sur une cellule de la boucle dont le numéro est un multiple de \(\lambda\)1 (dans l'exemple la cellule à la position \(15 = 3\times 5\)) elle se trouve sur à une position de la forme \(k\lambda\).

Dans ce cas, lievre a effectué \(2 k \lambda = k \lambda + k \lambda\) pas. Autrement dit, il a effectué \(k\) tours complets depuis la cellule numéro \(k \lambda\) où se trouve actuellement la tortue.

tortue et lievre sont donc à la même position.

Écrivez la fonction qui détermine la présence d'une boucle dans la liste chaînée tete passée en argument. On garantit que la liste est non vide.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
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

.128013(lbsSet.ph4rd5f189uma"ovTw7g_F/3=in 6k:)y 2PcN030h0a0b0p0C06080K0N060p08080B0q0b0C0d0q020E03080n0o0o0p0g0J02090r060n0(0r0D030z0/0;0?0^0-0d020318111b0z180-0h0C0s0X0Z0#0%0e0C0w0e061p0e0b0+030S07060a1k0!0$0q1o1q1s1q0b1y1A1w0b0g190b0e0X0{080d0p0D0%0L0q1C1m0q0j0U0a0D0p0o0a1w1V1X1$1E1(1A1+1-0+040K0M0g0r0d0r080C0~0D0K0Q1T0g0g0a0N25111:0D190z1R2i1O1Q1P1x0h1=0%1s0D1*221w1h1j0Y1D2s0C2u0D0r2y1w0d2b192g2i2M0.1W262A1%2F0g0=060+0k2f2Q0,2P1;2S1E2U2W0+0L2!1X2$2g2r0q2*0p2X020A2.2h0-2;2(0%2@2_0f2|2:2Q2=320+0i352~37302?0r2V2^0+0F3c2%2R1l2)3h2+020v3m2 3p313r3j020l3v3e3x3g3i2_0m351c2K112y2l0h1Q2q3f0N2G1.193O1a3M2O122#033U0Q2L3E2B0q0G0+0Q0j350K3n380j0+3U0D0(1*0b0x070r0n0Y0a3K3w3,0*0205463+2T0+0b0R453$2/3@3f490I0H3c0K4r3?474e020b3h0b0n4i2M4t4d1E0r0+0B3=4l3F0D4f4h4q4s4K3,4M021s0a0s2b4J4u4F4H4Z4E314N4g4P4r4R1%3.020u1o1A4%2'4L0+4V4X4B2#4D4^3,0r0u0+0C084@3o5153022F0b572=0G0N0+0O0 4}2/4 581%520+1X0h5e3f4T4{4Y4j2h4-4#020c4c504v0/1i1X4*5z025n2=5q02555u3F5Q5c5T3,5g5i5k5F5o1E494p5M0E4s5,5O5v4f4y4A5X5p4$5M5.4_4w5;5l5A4!0%4G5D5$380+5I0s5K5~0,5-4Q602?4`0C4W5y4C5B615^6k6e5w6h4|643f625E5M6l6f0267696t5U0+6w2O6p660n5J3|6a5+6c5`5Y543;5_6y6q6i6a6Q5@020B4I6U6I5|0g4z6a6y5(4+6P4,6e4/2b4z0g106'4'3-5h020t0g5=5*5,6y6?0R0n6_5?1E5Z020y2^086N113(0a2i2J7k3N1i3P2l2o2j0p1z7n0z3O0-7x0R0T0V02.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
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

.128013(lbsSet.ph4rd5f189uma"ovTw7g_F/3=in 6k:)y 2PcN030h0a0b0p0C06080K0N060p08080B0q0b0C0d0q020E03080n0o0o0p0g0J02090r060n0(0r0D030z0/0;0?0^0-0d020318111b0z180-0h0C0s0X0Z0#0%0e0C0w0e061p0e0b0+030S07060a1k0!0$0q1o1q1s1q0b1y1A1w0b0g190b0e0X0{080d0p0D0%0L0q1C1m0q0j0U0a0D0p0o0a1w1V1X1$1E1(1A1+1-0+040K0M0g0r0d0r080C0~0D0K0Q1T0g0g0a0N25111:0D190z1R2i1O1Q1P1x0h1=0%1s0D1*221w1h1j0Y1D2s0C2u0D0r2y1w0d2b192g2i2M0.1W262A1%2F0g0=060+0k2f2Q0,2P1;2S1E2U2W0+0L2!1X2$2g2r0q2*0p2X020A2.2h0-2;2(0%2@2_0f2|2:2Q2=320+0i352~37302?0r2V2^0+0F3c2%2R1l2)3h2+020v3m2 3p313r3j020l3v3e3x3g3i2_0m351c2K112y2l0h1Q2q3f0N2G1.193O1a3M2O122#033U0Q2L3E2B0q0G0+0Q0j350K3n380j0+3U0D0(1*0b0x070r0n0Y0a3K3w3,0*0205463+2T0+0b0R453$2/3@3f490I0H3c0K4r3?474e020b3h0b0n4i2M4t4d1E0r0+0B3=4l3F0D4f4h4q4s4K3,4M021s0a0s2b4J4u4F4H4Z4E314N4g4P4r4R1%3.020u1o1A4%2'4L0+4V4X4B2#4D4^3,0r0u0+0C084@3o5153022F0b572=0G0N0+0O0 4}2/4 581%520+1X0h5e3f4T4{4Y4j2h4-4#020c4c504v0/1i1X4*5z025n2=5q02555u3F5Q5c5T3,5g5i5k5F5o1E494p5M0E4s5,5O5v4f4y4A5X5p4$5M5.4_4w5;5l5A4!0%4G5D5$380+5I0s5K5~0,5-4Q602?4`0C4W5y4C5B615^6k6e5w6h4|643f625E5M6l6f0267696t5U0+6w2O6p660n5J3|6a5+6c5`5Y543;5_6y6q6i6a6Q5@020B4I6U6I5|0g4z6a6y5(4+6P4,6e4/2b4z0g106'4'3-5h020t0g5=5*5,6y6?0R0n6_5?1E5Z020y2^086N113(0a2i2J7k3N1i3P2l2o2j0p1z7n0z3O0-7x0R0T0V02.

  1. On peut en effet montrer qu'une boucle de longueur \(\lambda\) contient toujours une cellule dont la position est un multiple de \(\lambda\)