Plus petit ancêtre commun

Dans cet exercice on considère une structure arborescente : un arbre (ici non ordonné) de taille \(n\) dont les nœuds sont étiquetés avec les entiers de \(0\) à \(n-1\). Les étiquettes sont des identifiants uniques des nœuds.

Arbre enraciné non ordonné

Il existe plusieurs définitions équivalentes. En voici une :

Un arbre enraciné non ordonné est un ensemble non vide de nœuds tel que :

  • Un nœud particulier est nommé racine de l'arbre.
  • Chaque nœud, sauf la racine, possède un unique nœud parent.
  • Par cheminements successifs d'un nœud vers son parent, on arrive à la racine de l'arbre.

Le qualificatif « non ordonné » indique qu'il n'y a pas d'ordre pour les sous-arbres. Les deux représentations ci-dessous représentent donc le même arbre non ordonné.

graph TD
    R(0) --> N1(1)
    R    --> N2(2)
    R    --> N3(3)
    N2   --> N4(5)
    N2   --> N5(4)

    R2(0) --> M1(1)
    R2    --> M2(3)
    R2    --> M3(2)
    M3   --> M4(4)
    M3   --> M5(5)

La liste des parents (voir ci-dessous) est ici [None, 0, 0, 0, 2, 2]

Liste des parents

Si l'arbre est de taille \(n\), et que les étiquettes sont les entiers de \(0\) à \(n-1\), on peut donner la liste des étiquettes des parents des nœuds de l'arbre, à l'exception de la racine qui n'a pas de parent.

Par exemple avec

🐍 Script Python
# indices  0  1  2    3   4
parents = [3, 4, 4, None, 3]
  • le parent de \(0\) est \(3\)
  • le parent de \(1\) est \(4\)
  • le parent de \(2\) est \(4\)
  • \(3\) n'a pas de parent
  • le parent de \(4\) est \(3\)

Ce qui donne l'arbre ci-dessous :

graph TD
    R(3) --> N1(4)
    R    --> N2(0)
    N1   --> N3(2)
    N1   --> N5(1)

Un arbre non ordonné est entièrement défini par la liste des parents des nœuds.

Ancêtres d'un nœud

On définit les ancêtres d'un nœud \(N\) comme l'ensemble des nœuds rencontrés depuis le nœud \(N\) jusqu'à la racine en allant successivement de parent en parent.

  • Un nœud est ancêtre de lui-même.
  • La racine est un ancêtre de tout nœud.
  • Les ancêtres d'un nœud \(N\) sont ordonnés du plus petit, le nœud \(N\), au plus grand, la racine.

Plus petit ancêtre commun à deux nœuds

Deux nœuds \(N_1\) et \(N_2\) étant donnés, on considère l'ensemble des ancêtres communs à \(N_1\) et \(N_2\). Cet ensemble est non vide ; la racine étant un ancêtre commun. Il existe un nœud \(N_0\), le plus petit parmi les ancêtres communs à \(N_1\) et \(N_2\), on l'appelle le plus petit ancêtre commun à \(N_1\) et \(N_2\).

Écrire une fonction telle que plus_petit_ancetre_commun(parents, noeud_a, noeud_b) renvoie le plus petit ancêtre commun à noeud_a et noeud_b dans l'arbre non ordonné décrit par sa liste des parents.

Exemples
graph TD
    R(0) --> N1(1)
    R    --> N2(2)
    R    --> N3(3)
    N2   --> N4(4)
    N2   --> N5(5)
    N2   --> N6(6)
    N5   --> N7(7)
    N5   --> N8(8)
    N6   --> N9(9)
    N6   --> N10(10)
    N9   --> N11(11)
    N9   --> N12(12)
🐍 Console Python
>>> parents = [None, 0, 0, 0, 2, 2, 2, 5, 5, 6, 6, 9, 9]
>>> plus_petit_ancetre_commun(parents, 8, 11)
2
>>> plus_petit_ancetre_commun(parents, 1, 10)
0
>>> plus_petit_ancetre_commun(parents, 12, 6)
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 : 10/10
.128013,:ag)1ikn9N/S=vmsuhb.84y7e[62o-dt c0(!w]r5_P3plf050G0A0H0d0h0V0r0I0J0V0d0r0r0o010H0h0U010406050r0s0q0q0d0P0y040n0E0V0s0;0E0j050m0{0}0 110_0U041a1h051k0m1k1m1h0_0G0h0p0)0+0-0/0t0h0e0t0V1A0t0H0@050!0u0V0A1v0,0.011z1B1D1B0H1J1L1H0H0u0E0G111I0P1i0H0t0)140r0U0d0j0/0D011N1x010W0$0A0j0d0q0A1H1/1;1_1P1|1L1 210@0a0I0S0P0E0U0E0r0h170j0I0Y1-0P0P0A0J2m1a240j1i0m1+2z0H1)1(1*0G260/1D0j1~2j1H1s1u0*1O2J0h2L0j1#1t1H0U2s1i2x2z2%0`1:2n2R1`2W0P0~0V0@0I0g2w2+0^2*252-1P2/2;2?0D2_1;2{2x2I01300d2=040I0T342y0_372~0/3a3c0I0x3g362+383m2?0Q3q3i3s3k390E2:3b2?0C3x2|2,1w2 3C313d0z3H3j3K3l3M3E3d0w3Q3z3S3B3D3n0k3Y2}3!3u040g0K3)3J2S3#3N0g2^1b2`3y3*3=3,0g333`353|3;2.3U3c0g3f423h3I3t470@0g3p4b3r3}463$4g3w4j444e4n3-3G4q4d3A3 3P4w3R3~4f3-3X4j1j2#1a2P2C0G2G380J1#221i4L1l4J2)4H4Q0Y2$3Z3=0i0@0Y0W3q4x3!0N2?4,4C2.0W0@1;0J0Z2s0r4;4$1`0?040L4~4l2 0@1:2s0j0H4}4H4=1P510b3q0I4-3~0@2W0A0s0G54455f0@0f0c3x0I5y5j5e3l4^0j4`2D0A0r0R5n5p5i5k1`0E0@0o5M5B01510B5r3t5m0E5o5q5d4 5t040O5x5z5N1P4(040N1z1L5S5(5C045K5$2%5A5_010E4/040h5c5~5.0/625Z0H5^550/0i0J0@0l180A5X3A515w4q5z6r5 6e395Z5#6d5s695Q6y5Y04581~5b6m3!5V6I5l5{5!5L5%6u515+6q6s5y686v044_4{5H5J6O5}2`6X5P040v6L2.4^0U0U1~6)356X51536Q6z6Y5|6/5)0f5,6W5T5:2s0H0s0P194j6t6~0j5D5F4|6%6x4q064r3A5:4*710/635j6}3t4@6E155I6?0;0H0R6!5G0R4Q0}0s7c2)5T6{7s6Y6F5a666*7O0@5h7d6X7g6N5#7F7Q5g6C4y6w5p0R0u7)5u6p2%066r7!7h6#5I0d7+3!6,5R7Z5T7#7G4|7;527Q7#7S6H7w6n7X7 6M5|7(8e6J5u747e6D866$7:8360818h6:6Z5E7|886|7N608b0 6G7U6_7W047Y67847-0G7/88737m7_760@5=1}8x5)8D3{6V75607P8l6M8r7}8#6A040M828O8+0@0B0O8U8_6u6a8z6^2y8p8f898-8y8/8S8u900@8@8;5U8{8}9g911;933d6`0@8%8K8F7{5G8:985)5W9x8=0F7Q0q0h4g886T8 6~818^2`953+9u7j8t8E6R8{7Q6,9C9A019E9G9Z6S8~8(5-8L5v8o8W9t6E1:0J9k6B9c7f9Q6$7~9Z6,6.9Z8b2h8C9)438)9O8.8A9v9b9T9K0@a0ad6D2i0Ua48o6X770Z7a7M9N7`9;0d9?4w0m4Z0A2z2!aA4K1t4M2C2E2A1!1$2C0d1KaD0m4L0_aQ0Z0#0%04.
Indice 1

On peut construire une pile avec les ancêtres de noeud_a, une autre pile pour ceux de noeud_b. Ceci peut se faire via une fonction qui renvoie les ancêtres d'un nœud.

Indice 2

Le sommet de chaque pile sera commun : la racine de l'arbre.

Indice 3

On peut les dépiler tant que le sommet est identique.