Algorithmique

Contexte

Dans cette partie, on s’occupera essentiellement d’algorithmes itératifs bas niveau. On utilisera des types de données simples (des nombres entiers stockés dans des listes, et les structures de contrôle de base (if, while, for).

Les fonctions haut niveau de Python (min(), max(), mean(), sort(), etc. ) sont proscrites.

Environnement de travail

Créer un fichier python-06-listes-algo.py.

Selon la convention de structuration des modules, ce fichier sera structuré en quatre parties :

  1. Imports et définition des variables globales est vide car on ne fait pas appel à des packages externes, ni à des variables globales ;
  2. Définition des fonctions secondaires ;
  3. Définition de la fonction principale contenant l’appel aux fonctions secondaires ;
  4. Appel protégé de la fonction principale.

Objectifs

L’objectif est d’écrire les algorithmes ci dessous en mettant en place une méthodologie de résolution, c’est à dire en les structurant en 4 étapes fondamentales, permettant de préciser :

  1. l’invariant de boucle, c’est à dire la propriété de la liste qui reste vraie tout au long de la résolution du problème ;
  2. les conditions initiales qui garantissent que l’invariant ci dessus est vrai avant l’entrée dans la boucle ;
  3. la condition de continuation, qui permet de réduire la taille du problème à résoudre à chaque itération ;
  4. la progression qui permet de passer d’un problème résolu de taille k à un problème résolu de taille k+1 (la taille de la partie résolue augmente). Le problème non résolu passe lui d’une taille n-k à une taille n-k-1 (la taille de la partie non résolue diminue).

1. Valeur minimum d’une liste

Définir une fonction mini() qui :

Ce premier exercice est entièrement corrigé.

On considère une liste L de n éléments :

Si on souhaite concevoir l’algorithme retournant la valeur minimale d’une liste L, on peut procéder comme suit.

On commence par définir l’invariant de boucle qui consiste à écrire que le problème est résolu au rang k si :

On peut exprimer cet invariant par la propriété suivante. Deux paramètres sont nécessaires pour définir cette propriété (un paramètre est une grandeur susceptible de prendre différentes valeurs au cours de l’exécution de l’algorithme) :

Propriété invariante

La propriété invariante décrit un problème résolu au rang k.

I(i, k) <=> L[i] est la valeur minimum de la liste L[0:k]

L’invariant de boucle doit être vérifié avant l’entrée dans la boucle. On cherche donc les valeurs initiales de i et k qui assurent que I(i, k) est vraie AVANT de commencer à résoudre effectivement le problème.

Condition initiale

Manifestement, I(0, 1) est vraie avant l’entrée dans la boucle. En effet, quelque soit la liste L, L[0] est la valeur minimum de la liste L[0:1]. Donc :

L’étape suivante consiste à définir la condition de continuation.

Condition de continuation

Cette condition concerne le paramètre k qui mesure la taille du problème résolu. Le problème sera complètement résolu lorsque la totalité de la liste aura été parcourue (condition d’arrêt). La condition de continuation est sa négation :

k < n

La dernière étape consiste à définir la progression.

Progression

Cette progression permet de passer d’un problème résolu de taille k à un problème résolu de taille k+1. Sous la condition C(k, n), on examine la valeur de L[k]. Si L[k] est inférieur à L[i], alors L[k] est la nouvelle valeur minimum de la liste L[0:k+1]. On peut donc écrire :

Code Python

On obtient le code Python (naïf) de l’algorithme par transposition directe des étapes 2, 3 et 4 de la structuration du problème.

Vérifier la validité de la solution implémentée en faisant un appel à mini() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : si L = [33, 36, 27, 15, 43, 35, 36, 42, 49, 21], mini(L) retourne 15.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

2. Valeur maximum d’une liste

Décrire les 4 étapes de l’algorithme permettant de retourner la valeur maximale d’une liste d’entiers L.

Écrire la fonction maxi() correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à maxi() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : si L = [33, 36, 27, 15, 43, 35, 36, 42, 49, 21], maxi(L) retourne 49.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

3. Indice du minimum d’une liste

Décrire les 4 étapes de l’algorithme permettant de retourner l’index correspondant à la valeur minimale d’une liste d’entiers L.

Écrire la fonction ind_mini(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à ind_mini() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : si L = [33, 36, 27, 15, 43, 35, 36, 42, 49, 21], ind_mini(L) retourne 3.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

4. Indice du maximum d’une liste

Décrire les 4 étapes de l’algorithme permettant de retourner l’index correspondant à la valeur maximale d’une liste d’entiers L.

Écrire la fonction ind_maxi() correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à ind_maxi() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : si L = [33, 36, 27, 15, 43, 35, 36, 42, 49, 21], ind_maxi(L) retourne 8.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

5. Somme des éléments d’une liste

Décrire les 4 étapes de l’algorithme permettant de retourner la somme des éléments d’une liste d’entiers L.

Écrire la fonction somme() correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à somme() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : si L = [33, 36, 27, 15, 43, 35, 36, 42, 49, 21], somme(L) retourne 337.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

6. Moyenne des éléments d’une liste

Décrire les 4 étapes de l’algorithme permettant de retourner la moyenne des éléments d’une liste d’entiers L.

Écrire la fonction moyenne(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à moyenne() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : si L = [33, 36, 27, 15, 43, 35, 36, 42, 49, 21], moyenne(L) retourne 33.7.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

7. Triée ou pas ?

Décrire les 4 étapes de l’algorithme permettant de retourner retournant 1 si la L est croissante, -1 si L est décroissante, et 0 sinon.

Ecrire la fonction est_trie(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à est_trie() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple 1 : si L = [33, 36, 27, 15, 43, 35, 36, 42, 49, 21], est_trie(L) retourne 0. Exemple 2 : si L = [15, 21, 27, 33, 35, 36, 36, 42, 43, 49], est_trie(L) retourne 1. Exemple 3 : si L = [49, 43, 42, 36, 36, 35, 33, 27, 21, 15] est_trie(L) retourne -1.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

8. Première plus longue sous liste constante

On considère une liste L de n entiers. On veut calculer les indices d et f tels que L[d:f] soit la première plus longue sous liste constante. On veut une solution où la liste ne soit parcourue qu’une seule fois.

Décrire les 4 étapes de l’algorithme permettant de retourner d et f.

Ecrire la fonction pplslc(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à pplslc() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : la première plus longue sous liste constante de L = [2, 1, 1, 3, 4, 4] est L[1:3] = [1, 1]. Dans ce cas, pplslc(L) retourne (1, 3).

Ajouter une docstring et des doctest. Vérifier que les tests passent.

9. Doublons

On considère une liste L de n entiers. Écrire une fonction doublons(L) qui retourne la liste des éléments qui apparaissent au moins deux fois.

Décrire les 4 étapes de l’algorithme permettant de retourner la liste des éléments qui apparaissent au moins deux fois. Jusqu’ici, les exercices ont été résolus en stockant en mémoire uniquement 1 ou 2 entiers. Ici, on peut utiliser des structures de données additionnelles.

Réfléchir au problème si la liste de départ est déjà triée (en ordre croissant par exemple).

Une fois ce problème résolu, réfléchir au problème si la liste de départ n’est pas triée.

Ecrire la fonction doublons(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à doublons() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : on considère la liste L = [2, 1, 1, 3, 4, 4].. doublons(L) retourne [1, 4].

Ajouter une docstring et des doctest. Vérifier que les tests passent.

10. Éléments communs

On considère deux listes L1 et L2 de n et m entiers. Écrire une fonction elts_communs(L1, L2) qui prend les deux listes d’entiers L1 et L2 en entrée et retourne une liste contenant les éléments communs aux deux listes.

Décrire les 4 étapes de l’algorithme permettant de retourner la liste contenant les éléments communs aux deux listes.

Réfléchir au problème si les listes de départ sont déjà triées (en ordre croissant par exemple).

Une fois ce problème résolu, réfléchir au problème si les listes de départ ne sont pas triées.

Ecrire la fonction elts_communs(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à elts_communs() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : on considère les listes L1 = [2, 1, 1, 3, 4, 4] et L2 = [ 5, 1, 6, 4, 1]. elts_communs(L1, L2) retourne [1, 4].

Ajouter une docstring et des doctest. Vérifier que les tests passent.

11. Éléments différents

Écrire une fonction elts_differents(L1, L2) qui prend deux listes d’entiers L1 et L2 en entrée et retourne une liste contenant les éléments qui n’appartiennent qu’à une seule des deux listes.

Décrire les 4 étapes de l’algorithme permettant de retourner la liste contenant les éléments qui n’appartiennent qu’à une seule des deux listes.

Réfléchir au problème si les listes de départ sont déjà triées (en ordre croissant par exemple).

Une fois ce problème résolu, réfléchir au problème si les listes de départ ne sont pas triées.

Ecrire la fonction elts_differents(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à elts_differents() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : on considère les listes L1 = [2, 1, 1, 3, 4, 4] et L2 = [ 5, 1, 6, 4, 1]. elts_differents(L1, L2) retourne [2, 3, 5, 6].

Ajouter une docstring et des doctest. Vérifier que les tests passent.

12. Première plus longue sous liste triée croissante

Écrire une fonction pplsltc(L) qui prend une liste d’entiers L en entrée et retourne les indices d et f de début et de fin de la plus longue sous-séquence de la liste où les éléments sont triés en ordre croissant.

Décrire les 4 étapes de l’algorithme permettant de retourner les indices d et f.

Réfléchir au problème si les listes de départ sont déjà triées (en ordre croissant par exemple).

Une fois ce problème résolu, réfléchir au problème si les listes de départ ne sont pas triées.

Ecrire la fonction pplsltc(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à pplsltc() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : on considère la liste L = [ 33, 36, 27, 15, 43, 35, 36, 42, 49, 21] , pplsltc(L1, L2) retourne (5,9).

Ajouter une docstring et des doctest. Vérifier que les tests passent.

13. Négatifs, positifs

Écrire une fonction np(L) qui prend en argument une liste L de n entiers (négatifs et positifs), calcule une permutation de L telle que L[0:p] ne contient que des valeurs négatives, L[p:n] ne contient que des valeurs positives ou nulles, et retourne l’indice p.

Décrire les 4 étapes de l’algorithme permettant de retourner l’indice p.

Cet algorithme ne met pas en oeuvre de structure de données supplémentaires. Il faut procéder par permutation des éléments.

Ecrire la fonction np(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à np() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : on considère la liste L = [7, -3, 3, 1, 4, -8, 3, -6, -5, 9] , np(L) calcule la permutation [-3, -8, -6, -5, 4, 7, 3, 3, 1, 9 ] et retourne p = 4.

Ajouter une docstring et des doctest. Vérifier que les tests passent.

14. Première sous liste de somme maximum

Écrire une fonction pslsm(L) qui prend en argument une liste L de n entiers (négatifs et positifs), et retourne les indices d et f de début et de fin de la première sous liste de somme maximum, ainsi que la somme s des éléments de L[d:f].

Décrire les 4 étapes de l’algorithme permettant de retourner les indices d et f et la somme s.

On peut penser à définir l’invariant I(d, f, s, dp, i, sp) tel que : L[d:f] de somme s est la première sous liste de somme maximum de L[0:i] ; L[dp:i] de somme sp est le suffixe de somme maximum.

Ecrire la fonction pslsm(L) correspondante.

Vérifier la validité de la solution implémentée en faisant un appel à pslsm() sur quelques listes à partir de main() et en affichant/vérifiant la valeur de retour correspondante.

Exemple : on considère la liste L = [-1, 2, 1, -4, 3, 4, -6, 2, 3, 2, -3,1 ] , pslsm(L) retourne d = 4, f = 10 et s = 8.

Ajouter une docstring et des doctest. Vérifier que les tests passent.