Listes et algorithmes récursifs
Présentation¶
Stocker les données est un problème fondamental en informatique. Étant donné une collection finie de données \(a\), \(b\), \(c\), \(\ldots\), on décide de les stocker de manière séquentielle : le premier élément de la liste est \(a\), le second \(b\), etc. On utilise pour cela la structure de liste.
On peut donner une définition récursive à la structure de liste. En effet, une liste c'est : soit la liste vide ; soit un élément (la tête) suivit d'une liste (la queue).
Dans ce TP, on n'utilisera pas le type list définit par python. On définit le type Liste dans un fichier annexe et on utilisera uniquement les fonctions suivantes pour manipuler les objets de type Liste. Aucune des fonctions de cette interface ne modifient les listes auxquelles elles s'appliquent : les listes sont non mutables ici.
| Fonction | Description |
|---|---|
creer_vide() |
Renvoie une liste vide |
est_vide(l) |
Renvoie True si et seulement si la liste est vide |
tete(l) |
Renvoie le premier élément de la liste \(l\) (l'élément dit de tête) |
queue(l) |
Renvoie la liste constituée de tous les éléments de \(l\) à l'exception du premier |
ajoute(l, e) |
Renvoie la liste constituée de l'élément \(e\), suivi des éléments de \(l\) |
affiche(l) |
Affiche la liste des éléments de \(l\) sur la sortie standard, séparés par le caractère -. |
La liste vide est représentée par x. |
On suppose instanciée une variable l de type Liste représentant la liste \(l = (1, 8, 5)\).
- Que renvoie
est_vide(l)? - Que renvoie
tete(l)? - Que renvoie
queue(l)? Quelle est le type dequeue(l)? - Que renvoie
ajoute(l, 93)? - Que fait l'instruction
affiche(l)?
Interface¶
Exécuter le bloc ci-dessous pour charger l'interface du type Liste.
#--- HDR ---#bksl-nlclass Liste:bksl-nl def py-undpy-undinitpy-undpy-und(self, c, n):bksl-nl assert isinstance(c, int), f"Les éléments d'une Liste sont de type int"bksl-nl assert isinstance(n, Liste) or n is None, f"La queue d'une Liste doit être une Liste"bksl-nl self.c = cbksl-nl self.n = nbksl-nlbksl-nldef creerpy-undvide() -> Liste: return Nonebksl-nldef estpy-undvide(l: Liste) -> bool: return l is Nonebksl-nldef ajoute(l: Liste, e: int) -> Liste: return Liste(e, l)bksl-nldef tete(l: Liste) -> int: return l.cbksl-nldef queue(l: Liste) -> Liste: return l.nbksl-nldef affiche(l: Liste) -> None: print("x") if estpy-undvide(l) else (print(tete(l), end = " - "), affiche(queue(l)))bksl-nldef element(l: Liste, i: int) -> int: return tete(l) if i == 0 else element(queue(l), i - 1)bksl-nl#--- HDR ---#bksl-nlbksl-nl# Exécuter ce bloc AVANT de traiter les exercices.bksl-nl# Charge les fonctions suivantes :bksl-nl# creerpy-undvide : () -> Listebksl-nl# estpy-undvide : Liste -> boolbksl-nl# ajoute : Liste, int -> Listebksl-nl# tete : Liste -> intbksl-nl# queue : Liste -> Listebksl-nl# affiche : Liste -> NoneTypebksl-nlbksl-nll = creerpy-undvide()bksl-nlaffiche(l)bksl-nll = ajoute(l, 0)bksl-nlaffiche(l)bksl-nll = ajoute(l, 1)bksl-nlajoute(l, 2)bksl-nlaffiche(l)bksl-nlbksl-nl
D'après l'affichage, à quoi voit-on que la structure de liste proposée est non mutable ?
Premier exemple¶
Écrire les instructions python permettant d'instancier les variables l1, l2, l3, et l4, de type Liste représentant les listes :
- \(l_1 = ()\)
- \(l_2 = (-1)\)
- \(l_3 = (5, 6, 9, -1)\)
- \(l_4 = (-5, 9, 4, 9, -5, 9)\).
Vous utiliserez pour cela uniquement les fonctions creer_vide, ajoute.
# l1 = creerpy-undvide()bksl-nl# ...bksl-nlbksl-nl
Exercices¶
Vous n'avez pas le droit d'utiliser de boucle for, ou de boucle while.
Liste singleton¶
Écrire une fonction est_singleton qui prend en argument une liste l et qui renvoie True si la liste est constituée d'un seul élément, False dans tous les autres cas.
def estpy-undsingleton(l):bksl-nl """ Liste -> boolbksl-nl Détermine si la liste est constituée d'un seul élément. """bksl-nl passbksl-nlbksl-nl
Création de liste I¶
Écrire une fonction singleton qui prend en argument un entier n et qui renvoie la liste ayant pour seul élément le nombre n.
def singleton(n):bksl-nl """ int -> Listebksl-nl Renvoie la liste (n) """bksl-nl passbksl-nlbksl-nl
Création de liste II¶
On souhaite écrire une fonction récursive nombres qui prend en argument un entier n supérieur à 1 et qui renvoie la liste des entiers de n (inclu) à 1 (inclu) dans l'ordre décroissant.
- Pour quelle valeur du paramètre
nparle-t-on du cas de base dans ce contexte ? Que doit renvoyer l'instructionnombre(n)dans le cas de base ? - Dans le cas général, lorsque le paramètre
nest strictement supérieur à1:- Quelle instruction doit-on écrire pour affecter à la variable
interle résultat de l'appel récursif ? - Quelle liste la variable
interreprésente-t-elle ?
- Quelle instruction doit-on écrire pour affecter à la variable
- En déduire le code de la fonction
nombres.
def nombres(n):bksl-nl """ int -> Listebksl-nl Renvoie la liste (n, n-1, n-2, ..., 3, 2, 1) """bksl-nl passbksl-nlbksl-nl
Création de liste III¶
On se propose dans cet exercice d'écrire une fonction nombresII qui prend en argument un entier n supérieur à 1 et qui renvoie la liste des entiers de 1 (inclu) à n (inclu) dans l'ordre croissant.
-
Écrire une fonction récursive
nombresII_auxqui étant donné deux entiersneti, renvoie la liste constituée des entiers dei(inclu) àn(inclu) dans l'ordre croissant.On donne les indications suivantes :
- le cas de base de cette fonction est lorsque
netisont égaux. - l'appel récursif est
nombresII_aux(n, i + 1) - En déduire une fonction
nombresII(celle-ci n'est pas récursive) qui renvoie la liste dans l'ordre croissant des entiers de1àn. Vous appellerez pour cela la fonctionnombresII_auxavec des arguments bien choisis.
- le cas de base de cette fonction est lorsque
def nombresIIpy-undaux(n, i):bksl-nl """ int, int -> Listebksl-nl Renvoie la liste de nombres (i, i + 1, ..., n-1, n) """bksl-nl passbksl-nlbksl-nldef nombresII(n):bksl-nl """ int, int -> Listebksl-nl Renvoie la liste de nombres (1, 2, ..., n-1, n) """bksl-nl passbksl-nlbksl-nl
Longueur d'une liste¶
On définit la longueur d'une liste comme étant le nombre d'éléments constituant cette liste. Par exemple, si \(l = (0, 1, 5)\), la longueur de \(l\) est 3. Par convention, la longueur de la liste vide est 0.
Écrire une fonction python récursive longueur qui étant donné une liste, en renvoie la longueur.
- Pour quelle valeur du paramètre
lparle-t-on du cas de base dans ce contexte ? Que doit renvoyer l'instructionlongueur(l)dans ce cas ? -
- Dans le cas général, que renvoie l'appel récursif
longueur(queue(l))? - En déduire le code de la fonction
longueur.
- Dans le cas général, que renvoie l'appel récursif
def longueur(l):bksl-nl """ Liste -> intbksl-nl Renvoie la longueur de la liste l """bksl-nl passbksl-nlbksl-nl
Somme des éléments d'une liste¶
Écrire une fonction python récursive somme qui étant donnée une liste l d'entiers non vide en renvoie la somme.
def somme(l):bksl-nl """ Liste -> intbksl-nl Calcule la somme des éléments de la liste l """bksl-nl passbksl-nlbksl-nl
Appartenance à une liste¶
Écrire une fonction python récursive appartient qui étant donné une liste l et un élément e, renvoie True si et seulement si l'élément e est un des éléments de la liste l.
Indication. On utilisera l'appel récursif appartient(queue(l), e).
def appartient(l, e):bksl-nl """ Liste, int -> boolbksl-nl Détermine si l'élément e fait partie de la liste l """bksl-nl passbksl-nlbksl-nl
Nombre d'occurrences¶
Écrire une fonction python récursive nombre_occurrences qui étant donné une liste l et un élément e détermine le nombre d'occurrences de l'élément e dans la liste l.
def nombrepy-undoccurrences(l, e):bksl-nl """ Liste, int -> intbksl-nl Compte le nombre d'occurrences de e dans l """bksl-nl passbksl-nlbksl-nl
Maximum des éléments d'une liste¶
On souhaite écrire une fonction python récursive maximum qui étant donnée une liste l d'entiers non vide en renvoie le plus grand.
- Écrire une fonction
maximum2qui étant donné deux entiersaetbrenvoie le plus grand entier parmiaetb. -
- Pour quelle(s) valeur(s) du paramètre
lparle-t-on du cas de base dans ce contexte ? Que doit renvoyermaximum(l)dans ce cas ? -
Sans justifier votre réponse, compléter le tableau ci-dessous :
Liste lmaximum(queue(l))maximum(l)\((3, 5, 6, 1, 4)\) \(6\) \((8, 5, 6, 1, 4)\) \((9, 3, 2, 12, 4, 1)\) \((1, 2, 3, 4, 5, 6, 7, 8)\)
- Pour quelle(s) valeur(s) du paramètre
-
En déduire le code d'une fonction python récursive
maximumqui renvoie le plus grand élément de la listel.
def maximum2(a, b):bksl-nl """ int, int -> intbksl-nl Calcule l'élément maximum parmis a et bbksl-nl """bksl-nl passbksl-nlbksl-nldef maximum(l):bksl-nl """ Liste -> intbksl-nl Renvoie le plus grand élément de l """bksl-nl passbksl-nlbksl-nl
Pour aller plus loin¶
Suppression d'un élément¶
Écrire une fonction supprime qui prend en argument une liste l et un entier e, et renvoie la liste des éléments de l dans laquelle la première occurrence de e a été supprimée. On suppose que l'élément e appartient à la liste l. La liste initiale ne sera pas modifiée.
def supprime(l, e):bksl-nl """ Liste, int -> Listebksl-nl Supprime la première occurrence de e la liste l """bksl-nl passbksl-nlbksl-nl
Concaténation de deux listes¶
Si \(l_1\) et \(l_2\) sont deux listes, on appelle concaténation de \(l_{1}\) avec \(l_{2}\) la liste constituée des éléments de \(l_1\) suivis des éléments de \(l_2\). Par exemple, la concaténation des listes \(l_1 = (0, 1, 6)\) et \(l_2 = (9, 4, 1)\) est la liste \((0, 1, 6, 9, 4, 1)\) ; par contre la concaténation des listes \(l_{2}\) et \(l_1\) (dans cet ordre) est la liste \((9, 4, 1, 0, 1, 6)\). La concaténation de la liste vide avec la liste \(l_1\) est la liste \((0, 1, 6)\).
-
Compléter le tableau ci-dessous.
2. Écrire une fonction python récursivel1l2concatene(l1, l2)\((3, 5, 7)\) \((2, 4, 6)\) \((2, 4, 6)\) \((3, 5, 7)\) \((4, 6)\) \((3, 5, 7)\) La liste vide \((3, 5, 7)\) concatenequi étant donné deux listesl1etl2renvoie la concaténation de ces deux listes. Les listesl1etl2ne seront pas modifiées. Vous utiliserez comme cas de base le cas oùl1est la liste vide, et l'appel récursif aura la formeconcatene(queue(l1), l2).
def concatene(l1, l2):bksl-nl """ Liste, Liste -> Listebksl-nl Concatène les deux listes """bksl-nl passbksl-nlbksl-nl
Division de listes¶
- Écrire une fonction
est_2tonqui prend en argument une listelet qui renvoieTruesi et seulement si la listelest constituée d'exactement deux éléments. -
On souhaite écrire une fonction
divisequi divise en deux parties "à peu près égales" une listel. Les longueurs des deux listes résultantes doivent différer au plus d'un, et tous les éléments de la listeldoivent se retrouver dans l'une uniquement des deux listes renvoyées. Ainsi sidivise(l), renvoie les listesl1etl2, on a :concatene(l1, l2)qui contient les mêmes éléments (avec répétition, mais pas forcément le même ordre) quel.-
lest la liste \((1, 2, 3, 4, 5)\). Parmi les propositions ci-dessous, déterminer celle(s) qui peuve(nt) être renvoyées par l'instructiondivise(l):l1 = (1, 2, 3)etl2 = (3, 4, 5)l1 = (1, 3, 5)etl2 = (2, 4)l1 = (1, 2, 3)etl2 = (5, 4)l1 = (1, 3, 4, 5)etl2 = (2)
Justifier votre réponse.
-
En déduire une fonction divise qui réponde au problème posé. Pour le cas général, on remarquera que si une liste
lest constituée d'au moins trois éléments, on peut la diviser en deux de la manière suivante :- on met à part les deux premiers éléments
x1etx2de la listel; - on divise récursivement en deux listes
l1etl2les éléments restants ; - on ajoute à
l1l'élémentx1et àl2l'élémentx2.
- on met à part les deux premiers éléments
-
def divise(l):bksl-nl """ Liste -> Liste, Listebksl-nl Divise la liste l en deux listes """bksl-nl passbksl-nlbksl-nl
Pour les plus fous¶
L'objectif ici est de déterminer l'ensemble des parties d'un ensemble \(E\) à \(n\) éléments, que l'on note \(\mathcal{P}(E)\). Par exemple, si l'ensemble \(E\) est \(\{1, 2, 3\}\), l'ensemble des parties de \(E\) est :
On remarquera plusieurs choses :
- les éléments de \(\mathcal{P}(E)\) sont des ensembles ;
- \(\emptyset \in \mathcal{P}(E)\) (on note parfois l'ensemble vide \(\{\}\) ou \(\emptyset\)) et \(E \in \mathcal{P}(E)\) ;
- Si \(E = \emptyset\), que vaut \(\mathcal{P}(E)\) ?
-
Soit \(E = \{1, 2, 3\}\), et soit \(e = 3\).
-
Déterminer la liste de tous les sous-ensembles de \(E\) qui ne contiennent pas \(e\).
On note cette liste \(E'\). 2. Déterminer la liste de tous les sous-ensembles de \(E\) qui contiennent \(e\).
On note cette liste \(E''\). 3. Écrire \(E\) en fonction de \(E'\) et de \(E''\). Vous utiliserez pour cela les opérations ensemblistes \(\cup\) et \(\cap\). 3. Déduire des questions précédentes une fonction python récursive
liste_sous_ensemblesqui étant donné un ensemble \(E\), renvoie la liste de tous ses sous-ensembles.
On utilisera le type
listpour représenter un ensemble. On rappelle que siEest une liste :E.pop()renvoie le premier élément deEen le supprimant de celle-ci ;E.append(sE)ajoute l'élémentsEà la fin de la listeE;- si
E1etE2sont deux listes, alorsE1 + E2est une liste constituée de la concaténation des éléments deE1et deE2; E.copy()renvoie une liste dont les éléments sont les mêmes que ceux deE.
-
-
Si \(E\) possède \(n\) éléments, combien d'éléments possède \(\mathcal{P}(E)\) ?
def listepy-undsouspy-undensembles(E):bksl-nl """ list -> listbksl-nl Renvoie la liste de tous les sous-ensembles de E """bksl-nl passbksl-nlbksl-nl