Concours blanc Informatique Mots de Dyck, coefficients binomiaux

Transcription

Concours blanc Informatique Mots de Dyck, coefficients binomiaux
Lycées Jore et Daudet - MPSI - Informatique
Année 2013-2014
Concours blanc Informatique
Samedi 17 mai 2015 Durée 2H
Consignes générales :
1. Tout code Python devra être précédé d'une brève explication sur son fonctionnement.
2. Il est régulièrement demandé aux candidats d'écrire une fonction. Il est de la responsabilité des candidats, lorsque
c'est nécessaire, d'écrire par eux-mêmes les fonctions auxiliaires qui sont éventuellement nécessaires à une bonne
lisibilité du code. Ces fonctions devront être nommées de façon pertinente.
3. On choisira des noms de variables explicites en cas de besoin.
4. Toute fonction écrite pourra utiliser une fonction d'une question précédente, y compris lorsque cette question
n'aura pas été traitée (mais pas d'une question suivante...).
5. Les correcteurs feront preuve de tolérance vis-à-vis des erreurs de syntaxe Python. En revanche, ils feront preuve
d'exigence vis-à-vis de la lisibilité de votre code et de son indentation.
6. L'utilisation de fonctions de la bibliothèque maths n'est pas autorisée.
Mots de Dyck, coecients binomiaux et nombres de Catalan.
Ce problème est consacré à l'implémentation en Python de trois suites (cn )n∈N , (sn )n∈N et (dn )n∈N .
Partie I.
Dans toute la suite on appellera
On a ainsi, par exemple, c1 =
suite de Catalan
0
P
ck c−k =
c20
l'unique suite (cn )n∈N telle que

 c0 = 1
 ∀n ∈ N, cn =
n−1
P
ck cn−1−k
.
k=0
= 1.
k=0
1. Calculer c2 .
c2 =
1
P
ck c1−k = c0 c1 + c1 c0 = 1 + 1 = 2.
k=0
2. Écrire une fonction Catalan qui prend comme argument un entier n et rend comme résultat cn .
Le calcul de cn nécessite préalablement le calcul de c0 , c1 , . . ., cn−1 . Une stratégie d'implémentation possible est
donc de stocker successivement en mémoire chaque ci à l'aide d'une variable de type liste. On utilise pour cela
une boucle inconditionnelle. Cette stratégie conduit par exemple à l'implémentation suivante :
def Catalan(n):
c=(n+1)*[1]
for i in range(1,n+1):
c[i]=sum([c[k]*c[i-1-k] for k in range(0,i)])
return(c[n])
Une autre stratégie aurait été d'utiliser la récursivité plutôt qu'une boucle inconditionnelle, mais la récursivité
n'étant pas ociellement au programme en première année (sauf option), cette solution ne sera pas détaillée ici.
3. Faire une étude de la complexité de votre fonction Catalan. On comptera uniquement les additions et les multiplications.
L'initialisation initiale de la liste ne contient ni addition ni multiplication.
L'instruction de la boucle s'exécute n fois.
Lycées Jore et Daudet - MPSI - Informatique
Année 2013-2014
Au premier tour de boucle, on eectue une multiplication et aucune addition.
Au second tour de boucle, on eectue deux multiplication et une addition.
.
..
Au dernier tour de boucle, on eectue n multiplications et n − 1 additions.
n
n
P
P
n(n + 1)
n(n − 1)
Au total on a donc eectué
i=
multiplications et
(i − 1) =
additions.
2
2
i=1
i=1
La complexité est donc quadratique (c'est-à-dire en O n2 ).
Remarquons que la solution récursive enveloppée aurait a priori eu une complexité exponentielle.
Partie II.
Dans une publication de 1761, Johann Andreas von Segner montre que le nombre de décompositions d'un polygone
2n
convexe par les diagonales est sn =
n
n+1
. Dans la suite, on appelera
suite de Segner
la suite (sn )n∈N .
1. Calculer s2 .
4
2
s2 =
3
= 2.
2. Écrire une fonction Segner qui prend comme argument un entier n et rend comme résultat sn .
2n
Le calcul de sn nécessite préalablement celui de 2n
: utiliser la
n
n . Deux stratégies possibles pour calculer
formule avec les factorielles, ou bien utiliser le triangle de Pascal.
Détaillons les deux solutions correspondantes.
Dans le premier cas, on calcule n! en multipliant successivement 1 par tous les entiers compris entre 1 et n : une
n!
boucle inconditionnelle permet cela. Ensuite, on utilisera la division entière pour calculer le quotient
k!(n − k)!
an que le résultat garde un type entier.
def factorielle(n):
resultat=1
for k in range(1,n+1):
resultat=resultat*k
return(resultat)
def binomial(k,n):
return(factorielle(n)//(factorielle(k)*factorielle(n-k)))
Dans le second cas, on calcule nk de proche en proche, à partir des k0 = 1 pour k ∈ N et des
n > 0. On peut par exemple écrire :
n
0
= 0 pour
def binomial(k,n):
L=[[0 for j in range(0,n+1)] for i in range(0,k+1)]
for j in range(0,n+1):
L[0][j]=1
for j in range(1,n+1):
for i in range(1,k+1):
L[i][j]=L[i-1][j-1]+L[i][j-1]
return(L[k][n])
Une fois dénie la fonction binomial, le travail est terminé :
def Segner(k,n):
return(binomial(n,2*n)//(n+1))
Une variante à la première stratégie, mais plus ecace, serait d'utiliser la formule sn =
(n + 2) × (n + 3) × · · · × (2n)
.
1 × 2 × ··· × n
Lycées Jore et Daudet - MPSI - Informatique
Année 2013-2014
3. Faire une étude de la complexité de votre fonction Segner. On comptera uniquement les additions, multiplications
et divisions entières.
(2n)!
nécessite donc
n! × n!
nécessite une unique division supplé-
Première stratégie : le calcul de j! nécessite j multiplications. Le calcul de
2n
n
=
2n + n + n = 4n multiplications et 1 division entière. Le calcul
de sn
mentaire. La complexité est donc linéaire (c'est-à-dire en O n ).
(n + 2) × (n + 3) × · · · × (2n)
.
Seule la constante change avec la variante sn =
1 × 2 × ··· × n
Seconde stratégie : on a été conduit à calculer tous les ji avec i ≤ 2n et j ≤ n, chaque calcul nécessitant
une addition. On a n × (2n) = 2n2 coecients diérents, le nombre total d'addition est donc 2n2 et la complexité
est quadratique. Il se pourrait cependant que cet algorithme soit bien meilleur que le précédent, car aucune multiplication n'a été eectuée et que les multiplications sont en réalité bien plus coûteuses que les additions.
Partie III.
Dans cette partie, on s'intéresse aux mots binaires.
Un mot binaire est une liste dont tous les éléments sont des entiers égaux à 0 ou 1. Par exemple, [0, 1, 0, 0]
est un mot binaire, qu'on s'autorisera à noter 0100.
Comme toute liste, un mot binaire a une longueur : par exemple, le mot 0100 est de longueur 4. On observera qu'il
existe un unique mot binaire de longueur 0, le mot vide [].
Étant donné un entier n, l'ensemble des mots binaires de longueur n est ordonné par l'ordre lexicographique : on
a a0 a1 · · · an−1 strictement plus petit que b0 b1 · · · bn−1 si et seulement si a0 < b0 ou (a0 = b0 et a1 < b1 ), ou ..., ou
(a0 = b0 et ... et an−2 = bn−2 et an−1 < bn−1 ).
Si M et N sont des mots, on s'autorisera à noter M N la concaténation de ces deux mots. Par exemple, si M =
a0 a1 · · · an−1 et N = b0 b1 · · · bm−1 , on a M N = a0 a1 · · · an−1 b0 b1 · · · bm−1 .
1. En général, combien y a-t-il de mots binaires de longueur n ? Donner la liste de tous les mots binaires de longueur 4.
Il y a 2n mots binaires de longueur n. Pour n = 4, on trouve les 16 mots (ordonnés ici avec l'ordre lexicographique)
0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111.
2. Écrire une fonction MotBinaireSuivant qui prend comme arguments un entier n et un mot binaire M de longueur
n, et rend comme résultat le mot binaire suivant M dans l'ordre lexicographique.
Le comportement de la fonction n'est pas spécié si M n'est pas un mot binaire de longueur n ou si M est le
dernier mot binaire de longueur n dans l'ordre lexicographique 11 · · · 1. Le résultat de votre fonction peut être
arbitraire dans ce cas.
On parcourt le mot en partant de la n : dès qu'on trouve un 0, on le remplace par un 1 et on change tous
les éléments suivants du mot en 0. Le balayage permettant de trouver le premier 0 en partant de la n se code
naturellement à l'aide d'une boucle conditionnelle. Pour le remplissage de 0 de la n du mot, on peut utiliser
aussi bien une boucle inconditionnelle qu'une boucle conditionnelle, choix que l'on fait ici.
def MotBinaireSuivant(n,M):
N=[a for a in M]
i=n-1
while N[i]!=0:
i-=1
N[i]=1
i+=1
while i<n:
N[i]=0
i+=1
return(N)
La première ligne consistant à recopier le mot M dans une nouvelle variable N a pour objectif d'éviter l'eet de
bord suivant : sans cette recopie, si l'argument M est une variable, alors le contenu de cette variable est modié
à l'issue de l'exécution. Notons cependant que rien dans l'énoncé n'interdisait cet eet de bord.
Lycées Jore et Daudet - MPSI - Informatique
Année 2013-2014
3. Écrire une fonction MotsBinaires qui prend en argument un entier n et rend comme résultat la liste de tous les
mots binaires de longueur n.
Le sujet est structuré de telle sorte qu'on se sent invité à utiliser la fonction précédente (on pourrait néanmoins
procéder de bien d'autres façons). Par exemple, on peut procéder comme suit : on initialise une liste L à vide,
puis on ajoute successivement à cette liste chaque mot binaire dans l'ordre lexicographique à l'aide de la fonction
précédente. Sachant qu'il y a 2n mots binaires, on peut le faire par exemple avec une boucle inconditionnelle.
def MotsBinaires(n):
L=[]
M=n*[0]
L.append(M)
for i in range(1,2**n):
M=MotBinaireSuivant(n,M)
L.append(M)
return(L)
On aurait pu initialiser L à une liste de longueur 2n et utiliser des aectations plutôt que la méthode append.
Partie IV.
Dans cette partie, on s'intéresse plus particulièrement aux mots de Dyck.
Un mot binaire M = a0 a1 · · · an−1 est appelé un mot de Dyck si et seulement si il a autant de 0 que de 1 et que tout
préxe P = a0 . . . ak−1 de M a au moins autant de 0 que de 1.
On pourra, si on le désire, utiliser sans démontration la caractérisation équivalente suivante : l'ensemble des mots de
Dyck est le plus petit ensemble de mots binaires (pour l'inclusion) vériant les propriétés suivantes :
le mot vide est un mot de Dyck,
si M est un mot de Dyck, alors 0M 1 est un mot de Dyck,
si M et N sont des mots de Dyck, alors M N est un mot de Dyck.
L'ensemble des mots de Dyck de longueur 0 est composé du seul mot vide.
L'ensemble des mots de Dyck de longueur 2 est composé du seul mot 01.
Le mot 0110 n'est pas un mot de Dyck de longueur 4 car son préxe 011 contient strictement plus de 1 que de 0. Autre
argument possible : on ne peut l'écrire ni sous la forme 0M 1 avec M un mot de Dyck (il ne se termine pas par 1), ni
sous la forme M N avec M et N des mots de Dyck (car 10 n'est pas un mot de Dyck).
On notera dn le nombre de mots de Dyck de longueur 2n. On a donc, d'après ce qui précède, d0 = d1 = 1.
1. Parmi les mots binaires de longueur 4 donnés en III.1, lesquels sont des mots de Dyck ? En déduire d2 .
On sélectionne d'abord les six mots qui contiennent autant de 0 que de 1. Parmi ceux-là, seuls 0011 et 0101 sont
bien d'une des deux formes de la dénition. On a donc d2 = 2.
2. Écrire une fonction EstUnMotDeDyck qui prend en argument un mot binaire, et rend comme résultat le booléen
True lorsque l'argument est un mot de Dyck et le booléen False sinon.
Le comportement de la fonction n'est pas spécié si l'argument n'est pas un mot binaire. Le résultat de votre
fonction peut être arbitraire dans ce cas.
En utilisant la caractérisation équivalente, on peut agréablement résoudre le problème à l'aide d'une fonction
récursive.
En utilisant la dénition, on peut éviter la récursivité : on parcourt le mot lettre à lettre en incrémentant un
premier compteur à chaque 0 trouvé et un second compteur à chaque 1 trouvé. Si le second compteur dépasse le
premier en cours de parcours, c'est que le mot n'est pas un mot de Dyck. Si les deux compteur ne sont pas égaux
à l'issue du parcours, c'est que le mot n'est pas un mot de Dyck. Dans tout autre cas, c'est un mot de Dyck. Le
parcours peut se coder à l'aide d'une boucle inconditionnelle.
Lycées Jore et Daudet - MPSI - Informatique
Année 2013-2014
def EstUnMotDeDyck(M):
longueur=len(M)
compteur1=0
compteur2=0
for i in range(0,longueur):
if M[i]==0:
compteur1+=1
else:
compteur2+=1
if compteur1<compteur2:
return(False)
return(compteur1==compteur2)
3. Finalement, écrire une fonction NombreDeMotsDeDyck qui prend un argument un entier n et donne comme résultat
le nombre dn .
Comme dn est le nombre de mots de Dyck de longueur 2n, il s'agit de compter le nombre de True dans la
liste [EstUnMotDeDyck(M) for M in MotsBinaires(8)]. Les fonctions count ou sum font cela très bien, mais
on attend manifestement ici autre chose qu'une simple application d'une fonction Python. On peut écrire notre
propre fonction de décompte à l'aide d'une boucle inconditionnelle et d'un compteur sur le même modèle que
dans l'exercice précédent.
def NombreDeMotsDeDyck(n):
L=MotsBinaires(2*n)
total=0
for M in L:
if EstUnMotDeDyck(M):
total+=1
return(total)
Partie V.
On a écrit la fonction suivante :
def Conjecture(n):
for i in range(0,n+1):
if Catalan(i)!=Segner(i) or NombreDeMotsDeDyck(i)!=Segner(i):
return(False)
return(True)
1. Que calcule la fonction Conjecture ?
Si, pour tous les entiers k inférieurs à n, on a ck = sk = dk , alors la fonction retourne le booléen True. S'il existe
un entier k inférieur à n tel que ck 6= sk ou sk 6= dk , alors la fonction retourne le booléen False.
2. On observe que Conjecture(100) donne pour résultat True.
Qu'est-ce que cela signie et que peut-on conjecturer ?
D'après la réponse précédente, cela signie que tous les entiers k inférieurs à 100, on a ck = sk = dk . On peut
conjecturer que le résultat se maintient pour les entiers k supérieurs, c'est-à-dire que les suites (cn )n∈N , (sn )n∈N
et (dn )n∈N sont égales .
Lycées Jore et Daudet - MPSI - Informatique
Année 2013-2014
3. Démontrer l'une des égalités de la conjecture précédente.
L'égalité facile est (cn )n∈N = (dn )n∈N . Il existe une unique suite qui vérie la relation de récurrence dénissant
(cn )n . L'égalité est donc établie si l'on démontre que (dn )n vérie la relation de récurrence en question.
· On a déjà vu d0 = 1.
· Pour n > 0, comptons les mots de Dyck de longueur 2n. Un tel mot M est soit de la forme 0N 1 avec N
un mot de Dyck, soit de la forme P Q avec P et Q des mots de Dyck. On peut synthétiser ces deux cas en disant
que M est nécessairement de la forme 0R1S avec R et S des mots de Dyck (éventuellement vides). Le mot S peut
alors avoir n'importe quelle longueur paire 2k avec k ∈ {0, . . . , n − 1}. Pour un tel k xé, on a dn−k−1 possibilités
pour R et dk possibilités pour S (par dénition de (di )i dans les deux cas), donc dk dn−k−1 possibilités pour 0R1S .
n−1
P
En sommant sur toutes les valeurs de k possibles, on trouve bien dn =
dk dn−1−k .
k=0