TP — Permutations¶
Travaux pratiques · MPSI (tronc commun) · 2021-2022 · Python
Cours : Récursivité
Ce TP est un TP intermédiaire si vous avez fini intégralement les deux TP précédents sur les tris et le rendu de monnaie.
On considère ici des permutations de \(\range{0}{n-1} = \{0, 1, \dots, n-1\}\),
c'est-à-dire des éléments de \(\mathfrak{S}_n\) (c'est un S en gothique, on
écrira donc Sn dans les commentaires en Python) l'ensemble des bijections de
\(\range{0}{n-1}\) dans lui-même. Remarque : en mathématiques, on utilise en
général des permutations de \(\range{1}{n}\) mais le fait de commencer par 0
permettra d'utiliser directement ces valeurs comme indices.
Pour représenter \(\sigma \in \mathfrak{S}_n\), on va utiliser un tableau \(t_\sigma = [\sigma(0), \sigma(1), \dots, \sigma(n-1)]\), c'est-à-dire que la valeur \(\sigma(i)\) est à l'indice \(i\) dans \(t_\sigma\) :
Le groupe \((\mathfrak{S}_n, \circ)\)¶
Comme les permutations ont même ensemble de départ et d'arrivée, on peut les composer et comme elles sont bijectives, la composée l'est également. Ainsi \(\mathfrak{S}_n\) est stable par \(\circ\). De plus, l'identité est une permutation ainsi que la réciproque d'une permutation.
Question 1
def identite(n):
# renvoie la permutation identité de Sn
Corrigé
def identite(n):
# renvoie la permutation identité de Sn
p = []
for i in range(n):
p.append(i)
return p
Question 2
def compose(sigma, tau):
# renvoie sigma o tau la composée des deux permutations de Sn
# Cette fonction sera toujours appelée avec des permutations portant sur les
# mêmes éléments, il n'y a rien à vérifier
Corrigé
def compose(sigma, tau):
# renvoie sigma o tau la composée des deux permutations de Sn
# Cette fonction sera toujours appelée avec des permutations portant sur les
# mêmes éléments, il n'y a rien à vérifier
n = len(sigma)
p = []
for i in range(n):
p.append(sigma[tau[i]])
return p
Question 3
def inverse(sigma):
# renvoie la permutation inverse de sigma
Corrigé
def inverse(sigma):
# renvoie la permutation inverse de sigma
n = len(sigma)
p = [ 0 ] * n
for i in range(n):
p[sigma[i]] = i
return p
# écrivez vos tests ici pour vous assurer que vos fonctions fonctionnent
Question 4
def iter(sigma, k):
# renvoie la permutation sigma^k= sigma o ... o sigma
# on pourra faire une fonction récursive
Corrigé
def iter(sigma, k):
# renvoie la permutation sigma^k= sigma o ... o sigma
# on pourra faire une fonction récursive
if k == 0:
return identite(len(sigma))
return compose(sigma, iter(sigma, k-1))
# écrivez vos tests ici pour vous assurer que vos fonctions fonctionnent
Pour vérifier si un tableau d'entiers est une permutation, on peut créer un
tableau de n = len(t) booléens valant False et mettre True à l'indice
p quand t[i] = p pour i parcourant les indices. Attention t[i] peut
très bien ne pas être une valeur dans \(\range{0}{n-1}\).
Question 5
def test_permutation(t):
# renvoie un booléen (True ou False) indiquant si le
# tableau t est celui d'une permutation
Corrigé
def test_permutation(t):
# renvoie un booléen (True ou False) indiquant si le
# tableau t est celui d'une permutation
n = len(t)
valid = [ False ] * n
for i in range(n):
p = t[i]
if 0 <= p < n:
valid[p] = True
for b in valid:
if not b:
return False
return True
# écrivez vos tests ici pour vous assurer que vos fonctions fonctionnent
Si \(\sigma\) est une permutation de \(\mathfrak{S}_n\) et \(t\) un tableau à \(n\)
éléments, il est possible de considérer le tableau permuté de \(t\) par
\(\sigma\), noté \(\sigma.t\) par extension et défini par
\(\sigma.t[i] = t[\sigma(i)]\) pour tout indice \(i\). Ainsi si \(\sigma\) est la
permutation correspondant au tableau [3,1,2,0] et t = ['a','b','c','d']
alors \(\sigma.t\) = ['d','b','c','a'].
Question 6
def action(sigma, t):
# renvoie le tableau sigma . t
# on suppose t et sigma de même longueur
Corrigé
def action(sigma, t):
# renvoie le tableau sigma . t
# on suppose t et sigma de même longueur
st = []
n = len(t)
for i in range(n):
st.append(t[sigma[i]])
return st
# écrivez vos tests ici pour vous assurer que vos fonctions fonctionnent
Orbite¶
Soit \(\sigma \in \mathfrak{S}_n\) et \(i \in \range{0}{n-1}\). On note \(\text{Orb}_\sigma(i) = \{i, \sigma(i), \sigma^2(i), \dots\}\). Cet ensemble est nécessairement fini, on peut donc le représenter par un tableau d'entiers.
Par exemple, si on considère \(\sigma\) = [5,2,4,1,3,0] on a
\(\text{Orb}_\sigma(0) = \{0, 5\} = \text{Orb}_\sigma(5)\) et
\(\text{Orb}_\sigma(1) = \{1, 2, 4, 3\}\).
Question 7
def orbite(sigma, i):
# renvoie Orb_sigma(i) comme un tableau sans répétitions
Corrigé
def orbite(sigma, i):
# renvoie Orb_sigma(i) comme un tableau sans répétitions
o = [i]
k = i
while sigma[k] != i:
k = sigma[k]
o.append(k)
return o
# écrivez vos tests ici pour vous assurer que vos fonctions fonctionnent
Les orbites de \(\sigma\) forment une partition de \(\range{0}{n-1}\). Cela signifie que chaque élément est présent dans une orbite et une seule.
Question 8
def orbites(sigma):
# renvoie les orbites de sigma dans un tableau d'orbites sans répétitions
Corrigé
def orbites(sigma):
# renvoie les orbites de sigma dans un tableau d'orbites sans répétitions
n = len(sigma)
traites = [ False ] * n
orbites = []
for i in range(n):
if not traites[i]:
traites[i] = True
o = [i]
k = i
while sigma[k] != i:
k = sigma[k]
traites[k] = True
o.append(k)
orbites.append(o)
return orbites
>>> orbites([5,2,4,1,3,0]) # doit renvoyer par exemple [[0, 5], [1, 2, 4, 3]]
[[0, 5], [1, 2, 4, 3]]
Génération des permutations¶
Il peut être intéressant de générer toutes les permutations pour des problèmes d'exploration exhaustive comme on en verra en deuxième année. Pour le moment, imaginons qu'on souhaite casser un code, une manière bête mais sûre d'y arriver est de tous les tester.
Il existe de nombreuses approches, dont certaines sont très astucieuses pour économiser l'espace. On va voir ici une approche plus simple proche de celle vue dans le TP précédent.
On va prendre un point de vue plus proche de notre représentation en parlant de tableaux sans répétitions. On note \(P(E)\) l'ensemble des tableaux sans répétitions dont les éléments sont dans \(E\). On cherche donc à calculer \(P(\range{0}{n-1})\). Pour cela, on va remarquer que
Ici \(+\) est la concaténation des tableaux. On en déduit une fonction récursive d'énumération. On remarque \(P(\emptyset) = \{[]\}\).
Question 9
def retire(t, e):
# renvoie un NOUVEAU tableau contenant les éléments de t sauf e
Corrigé
def retire(t, e):
# renvoie un NOUVEAU tableau contenant les éléments de t sauf e
tp = []
for x in t:
if x != e:
tp.append( x )
return tp
# écrivez vos tests ici pour vous assurer que vos fonctions fonctionnent
Question 10
def permutations(t):
# renvoie P(t)
Corrigé
def permutations(t):
# renvoie P(t)
if t == []:
return [ [] ]
p = []
for e in t:
for tp in permutations(retire(t, e)):
p.append( [e] + tp )
return p
>>> permutations([0,1,2])
[[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]