Listes - Implémentation et algorithmes
On utilisera dans toutes les questions de ce contrôle la classe
Maillon
ainsi que l'interface des objets de type Liste
tels que définis dans le cours.
Ainsi, on pourra utiliser sans justification supplémentaire :
- utiliser le constructeur
Maillon(v: int, s: Maillon)
pour créer un nouveau maillon. - accéder aux attributs
valeur
etsuivant
des objetsMaillon
; -
utiliser les fonctions
creer_vide
,est_vide
,est_singleton
,ajoute
,tete
,queue
etaffiche
afin de manipuler des objets de typeListe
.Ces fonctions ne modifient pas les objets qu’elles prennent en argument. On rappelle que la fonction
ajoute
a pour signatureMaillon, int -> Maillon
.
1 2 3 |
|
1 - 2 - 3 - x
Listes chaînées¶
-
On considère le code suivant :
1 2 3 4
m4 = Maillon(4, None) m3 = Maillon(3, m4) m2 = Maillon(2, None) m1 = Maillon(1, m2)
-
Représenter à l’aide d’un schéma l’état de la mémoire de l’ordinateur à l’issue de l’exécution des instructions précédentes. En déduire l’affichage réalisé par le code :
1 2
for m in [m1, m2, m3, m4]: affiche(m)
1 - 2 - x 2 - x 3 - 4 - x 4 - x
-
Quel est l’affichage réalisé par les instructions
affiche(m1)
etaffiche(m2)
après l'exécution de l'instruction :1
m1 = Maillon(5, Maillon(6, m3))
1 2
affiche(m1) affiche(m2)
5 - 6 - 3 - 4 - x 2 - x
Justifier à l’aide d’un schéma.
-
Quel est l’affichage réalisé par les instructions
affiche(m1)
etaffiche(m2)
après l'exécution de l'instruction :1
m2.suivant = m3
1 2
affiche(m1) affiche(m2)
5 - 6 - 3 - 4 - x 2 - 3 - 4 - x
-
Quel est l’affichage réalisé par les instructions
affiche(m1)
etaffiche(m2)
après l'exécution de l'instruction :1
m4.suivant = m2
1 2
affiche(m1) affiche(m2)
5 - 6 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 - 2 - 3 - 4 -
On peut étudier l'effet de ces instructions avec Pythontutor.
-
-
On souhaite écrire le code d’une fonction
concatene
, itérative, qui prend en entrée deux argumentsm1
etm2
de typeMaillon
supposés non vides et qui relie les deux chaînes correspondantes entre elles de la manière suivante : on parcourt la chaîne commençant par le maillonm1
jusqu’au dernier maillon non vide, et affecte à son attributsuivant
le maillonm2
.-
Écrire le code d’une fonction
concatene
correspondant à la description de l’énoncé, sans se soucier d’en écrire la documentation.1 2 3 4 5 6
def concatene(m1, m2): """ Maillon, Maillon -> Nonetype """ maillon_courant = m1 while maillon_courant.suivant is not None: maillon_courant = maillon_courant.suivant maillon_courant.suivant = m2
-
On considère le code suivant :
1 2
a = Maillon(1, Maillon(2, None)) b = Maillon(3, Maillon(4, None))
Justifier vos réponses aux questions suivantes en réalisant un schéma explicatif par question.
-
On exécute l’instruction
concatene(a, b)
.Quel est l’affichage alors réalisé par l’instruction
affiche(a)
?1 2
concatene(a, b) affiche(a)
1 - 2 - 3 - 4 - x
-
On exécute une deuxième fois l’instruction
concatene(a, b)
.Quel est l’affichage alors réalisé par l’instruction
affiche(a)
?1 2 3
concatene(a, b) affiche(a) # ne termine pas # car la liste dont le premier maillon est a est cyclique.
-
On exécute une troisième fois l’instruction
concatene(a, b)
.Que se passe-t-il ?
1 2 3 4 5
concatene(a, b) # ne termine pas # comme la liste dont le premier maillon est a est cyclique, # la condition de la boucle while de la fonction concatene # est toujours vérifiée (on n'atteint jamais None). affiche(a)
-
-
Étude de fonctions récursives¶
-
Soit la fonction
d
suivante :1 2 3 4 5 6 7 8
def d(l): """ Liste -> Liste """ if est_vide(l): return l else: reste = d(queue(l)) new = ajoute(ajoute(reste, tete(l)), tete(l)) return new
Déterminer l'affichage réalisé par
affiche(d(l))
, sil
est la liste \((1, 2, 3)\).Vous justifierez votre réponse par un arbre d'appel, en y représentant les listes de manière "naturelle". Ainsi, le premier nœud de l'arbre sera :
1 2 3 4
l = creer_vide() l = ajoute(ajoute(ajoute(l, 3), 2), 1) affiche(l) affiche(d(l))
1 - 2 - 3 - x 1 - 1 - 2 - 2 - 3 - 3 - x
-
Soit
f
la fonction suivante :1 2 3 4 5 6 7 8 9
def f(l1, l2): """ Liste, Liste -> Liste """ if est_vide(l1): return l2 elif est_vide(l2): return l1 else: intermediaire = f(queue(l1), queue(l2)) return ajoute(ajoute(intermediaire, tete(l2)), tete(l1))
Déterminer l'affichage réalisé par
affiche(f(l1, l2))
, sil1
est la liste(1, 2, 3)
etl2
est la liste(4, 5, 6)
. Vous justifierez vos réponses par un arbre d'appel.1 2 3 4 5 6 7
l1 = creer_vide() l1 = ajoute(ajoute(ajoute(l1, 3), 2), 1) l2 = creer_vide() l2 = ajoute(ajoute(ajoute(l2, 6), 5), 4) affiche(l1) affiche(l2) affiche(f(l1, l2))
1 - 2 - 3 - x 4 - 5 - 6 - x 1 - 4 - 2 - 5 - 3 - 6 - x
Tri par insertion¶
L’objectif de cet exercice est d’étudier une implémentation récursive des algorithmes de tri classique : le tri par insertion et le tri par sélection.
Partie A
On commence par détailler le fonctionnement de l'algorithme du tri par insertion.
Pour ce faire, il est nécessaire d'écrire une fonction auxiliaire, inserer_dans_liste_triée
.
-
La fonction
inserer_dans_liste_triée
prend en argument une listel
supposée triée par ordre croissant et un entiere
quelconque et renvoie la liste composée des éléments del
et dee
également triée par ordre croissant. Par exemple, sil
est la liste(1, 2, 3, 5)
ete
est l'entier4
,inserer_dans_liste_triée(l, e)
renvoie la liste(1, 2, 3, 4, 5)
.-
Déterminer sans justifier votre réponse dans chacun des cas ci-dessous la liste renvoyée par l’instruction :
inserer_dans_liste_triée(l, e)
-
l = ()
,e = 3
Renvoie
(3)
-
l = (4, 5, 6)
,e = 3
Renvoie
(3, 4, 5, 6)
-
l = (11, 14, 18)
,e = 4
Renvoie
(4, 11, 14, 18)
-
l = (19, 20, 23)
,e = 22
.Renvoie
(19, 20, 22, 23)
-
-
En déduire une fonction récursive
inserer_dans_liste_triée
, qui réponde à la question de l’énoncé. On ne se souciera pas d’écrire la documentation de cette fonction. On pourra écrire dans le codei
à la place deinserer_dans_liste_triée
.1 2 3 4 5 6 7 8
def i(l, e): if est_vide(l): return ajoute(creer_vide(), e) else: if e <= tete(l): return ajoute(l, e) inter = i(queue(l), e) return ajoute(inter, tete(l))
-
-
On rappelle l’algorithme du tri par insertion d’une liste
l
:- Si la liste
l
est vide alors elle est triée. -
Sinon :
- On trie récursivement la queue de la liste
l
. On note cette listel’
. - On insère l’élément de tête de la liste
l
"au bon endroit" dansl’
de telle sorte que la liste résultant soit également triée.
- On trie récursivement la queue de la liste
-
Recopier et compléter sur votre copie le schéma ci-dessous :
2. Compléter directement sur l'énoncé le code ci-dessous.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
def tri_insertion(l): """ Liste -> Liste Renvoie la liste des éléments de l triés par ordre croissant. """ if est_vide(l): return l else: # 1 - On trie récursivement la queue de la liste trie_intermediaire = tri_insertion(queue(l)) # Affichage pour analyse affiche(trie_intermediaire) # 2 - On insère dans la queue triée récursivement # l'élément de tête de la liste. La liste résultat # est triée par ordre croissant resultat = i(trie_intermediaire, tete(l)) # Affichage pour analyse print(f"On y insère {tete(l)}") return resultat
-
Déterminer les affichages réalisés par l’instruction
tri_insertion(l)
, quandl
est la liste(8, 9, 1)
.On pourra suivre l’exécution de l’algorithme à l’aide d’un arbre d’appel.
1 2 3
l = ajoute(ajoute(ajoute(creer_vide(), 1), 9), 8) l = tri_insertion(l) affiche(l)
x On y insère 1 1 - x On y insère 9 1 - 9 - x On y insère 8 1 - 8 - 9 - x
- Si la liste
Partie B
On rappelle que l'on a écrit en TP le code des fonctions suivantes :
minimum(l: Liste) -> int
: renvoie le plus petit élément del
;-
supprime(l: Liste, e: int) -> Liste
: renvoie la liste des éléments del
où la première occurrence dee
a été supprimée (e
est supposé présent dans la listel
). -
En utilisant uniquement les fonctions de l'interface du type
Liste
ainsi que les fonctionsminimum
etsupprime
, écrire une fonction récursivetri_selection
qui trie la listel
à l'aide de l'algorithme du tri par sélection dont on rappelle le principe ci-dessous :- Si la liste
l
est vide, alors il n'y a rien à faire. - Sinon :
- On calcule la valeur du minimum
m
de la listel
; - On calcule la liste
l'
obtenue en supprimant de la liste des éléments del
la première occurence dem
; - On ajoute l'élément
m
en tête de la listel'
(que l'on a préalablement triée).
- On calcule la valeur du minimum
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
def minimum(l): """ Liste -> int Renvoie le minimum d'une liste non vide. """ if est_vide(queue(l)): return tete(l) else: return min(tete(l), minimum(queue(l))) def supprime(l, e): """ Liste, int -> Liste Renvoie la liste des éléments de l sans la première occurrence de e. e est supposé appartenir à la liste l. """ if tete(l) == e: return queue(l) else: return ajoute(supprime(queue(l), e), tete(l)) def tri_selection(l): if est_vide(l): return l else: m = minimum(l) lp = supprime(l, m) reste_trie = tri_selection(lp) return ajoute(reste_trie, m) l = ajoute(ajoute(ajoute(creer_vide(), 1), 9), 8) affiche(tri_selection(l))
1 - 8 - 9 - x
- Si la liste
-
Justifier que votre fonction
tri_selection
est récursive.On réalise un appel récursif à la cinquième ligne du code de
tri_selection
:reste_trie = tri_selection(lp)
.