đ 1. 1. Recherche d'une valeur dans un tableau
đ 1.1. 1.1 Algorithme "naĂŻf"
đ 1.2. 1.2. Comparaison avec l'algorithme utilisĂ© par Python
đ 2. 2. Extremum ( maximum ou minimum ), moyenne
đ 2.1. 2.1 Recherche du maximum
đ 2.2. 2.2. Recherche du minimum
đ 2.3. 2.3. Calcul d'une moyenne
đ 3. 3. Application : le filtrage numĂ©rique
đ 3.1. 3.1. Pourquoi filtrer un signal numĂ©rique ?
đ 3.2. 3.2. Calculer la moyenne glissante sur un ensemble de valeurs
đ 3.3. 3.3. Mini-projet : filtrage d'un "vrai" signal
đ 4. 4. Preuve d'algorithme : Terminaison et Correction
đ 4.1. 4.1 Pourquoi une preuve ?
đ 4.2. 4.2 comment structurer le raisonnement ?
đ 5. 5. QCM
Nous allons nous intéresser à un algorithme simple : celui de la recherche d'un élément de valeur donnée dans un tableau.
Un tableau au sens algorithmique est implémenté en Python avec un type construit appelé liste Python, que vous avez déjà étudié en détails ici.
En particulier, vous connaissez donc la syntaxe du test d'appartenance d'une valeur Ă un tableau :
Mais on ne sait pas ce que Python fait derriÚre cette syntaxe pour trouver l'élément dans le tableau.
La méthode "naïve", c'est à dire qui est la plus simple, pour trouver un élément dans un tableau est la suivante :
â on parcourt donc le tableau Ă©lĂ©ment aprĂšs Ă©lĂ©ment; si on trouve la valeur, on sort du parcours et de la fonction en renvoyant VRAI; si on arrive Ă la fin du parcours et que l'on n'a pas trouvĂ© la valeur, on sort de la fonction en renvoyant FAUX.
Traduisez cet algorithme en codant une fonction recherche(tableau,valeur)
.
Cette fonction prendra en argument un tableau et une valeur Ă chercher, et renverra True
ou False
selon que la valeur est présente ou non dans le tableau.
Vous testerez votre recherche sur un tableau de 50 valeurs entiÚres entre 1 et 100 générées aléatoirement.
Est-ce l'algorithme utilisé par Python "en interne" pour tester l'appartenance d'une valeur dans un tableau ? Impossible de le savoir directement...mais nous allons essayer de comparer son temps d'exécution avec celui de notre algorithme "naïf".
Pour cela, nous allons utiliser le module timeit
qui permet de comparer le temps d'exécution ( ou de plusieurs exécutions ) d'un script Python :
t1
contiendra la durée moyenne d'exécution de la recherche "naïve", et t2
celle de la recherche "Python".Compléter le code ci-dessus avec le code des deux fonctions. Lancer le script plusieurs fois de suite, et comparer les valeurs de durée obtenues à chaque fois.
Que peut-on conclure ?
A conditions d'exĂ©cution identiques ( mĂȘme ordinateur, mĂȘme taille de donnĂ©es Ă traiter,...) tous les algorithmes ne se valent donc pas : certains sont plus efficaces que d'autre, c'est Ă dire qu'ils rĂ©soudront plus rapidement le problĂšme pour lequel ils sont prĂ©vus.
On dit que leur coût en temps (ou coût temporel) est différent : un algorithme efficace aura un coût en temps plus faible qu'un autre algorithme moins efficace.
Dans le cas de notre fonction de recherche, l'algorithme "naïf" a un coût en temps plus grand que celui utilisé en interne par Python, il est donc moins efficace que ce dernier.
( Remarque : on peut aussi comparer les algorithmes en fonction de leur coût en espace, c'est à dire la plus ou moins grande quantité de mémoire dont ils ont besoin pour fonctionner; cependant nous ne nous y intéresserons pas dans ce chapitre et les suivants.)
L'algorithme "naĂŻf" est extrĂȘmement simple, mais la structure du tableau ne permet pas de l'amĂ©liorer.
Essayons d'analyser le coût temporel cet cet algorithme, et déterminons ce que l'on appellera sa complexité temporelle.
Nous ne pouvons pas prĂ©voir quand cet algorithme sâarrĂȘte, mais nous pouvons dire que:
La complexité temporelle d'un algorithme correspond à l'évaluation du coût en temps d'un algorithme quand la quantité de données qu'il traite augmente.
Elle dépend souvent de la quantité de données notée n.
Généralement, on s'intéressera à la complexité temporelle dans le pire des cas.
Dans notre cas, la complexité de la recherche dans un tableau augmentera proportionnellement avec la taille du tableau : si le tableau contient 30 000 noms, le pire cas demandera
30 000 Ă©tapes; avec 60 000 noms, il y aura 60 000 Ă©tapes, etc....
On dira que la complexité d'une recherche linéaire dans un tableau est "en O(n)"
La notation "O(n)" ( "grand O de n" ou "O de n") est une notation mathématique qui résume la définition précédente et nous évite de dire à chaque fois : "dans le pire des cas", "proportionnel à ", ...
L'Ă©tude de la complexitĂ© d'un algorithme prend du sens quand n devient trĂšs grand; pour n = 10 ou n = 20, cette notion n'a en effet pas beaucoup d'intĂ©rĂȘt la plupart du temps...
Mais nous ne parlons ici que de la complexité temporelle d'un ALGORITHME !
Si nous voulons savoir quelle sera la durée d'exécution du programme c'est une autre question :
La complexité temporelle d'un algorithme nous donne des indications sur le temps d'exécution d'un programme, MAIS si la complexité est théorique et ne dépend que de l'algorithme, le temps d'exécution lui est une notion pratique et dépend :
En conclusion la complexité temporelle N'EST PAS le temps d'exécution d'un programme, elle ne permet de comparer que l'efficacité d'ALGORITHMES entre eux.
Vous trouverez sur cette page un peu plus de détail sur cette notion de complexité des algorithmes.
L'algorithme de recherche d'une valeur dans un tableau est basé sur le parcours du tableau et la comparaison de chaque élément à la valeur recherchée.
Sur cette base nous pouvons imaginer des traitement différents des éléments du tableau.
Tests du résultat d'une fonction : les tests unitaires
La notion de tests est fondamentale en informatique : certains systĂšmes ne peuvent se contenter d'un fonctionnement approximatif mais doivent au contraire ĂȘtre robustes, c'est Ă dire fonctionner correctement dans toutes les situations possibles.
Pour prouver qu'une fonction fait toujours correctement le travail pour lequel elle est prévue, il faudrait théoriquement la tester avec tous les arguments possibles et imaginables; c'est bien entendu impossible...
On peut cependant se contenter de tester son bon fonctionnement sur quelques arguments bien choisis : on parle alors de tests unitaires
En Python, on peut utiliser le module doctest
pour réaliser ces tests unitaires; il permet d'indiquer dans la docstring de la fonction des tests à réaliser et le résultat attendu si elle fonctionne bien.
Si ce n'est pas le cas, un message est alors affiché signalant qu'il faut corriger son code !
Pour les fonctions suivantes, vous utiliserez ce module pour vérifier "automatiquement" que votre code fournit le bon résultat pour quelques tests unitaires donnés.
L'algorithme de recherche d'un maximum est le suivant :
Vous devez coder la fonction maximum(tableau)
qui renvoie le maximum d'un tableau.
Vous n'oublierez pas les annotations et la docstring
Vous utiliserez le code ci-dessous, qui permet de réaliser "automatiquement" des tests de bon fonctionnement de votre fonction à l'aide du module doctest
:
minimum(tableau)
renvoyant le minimum d'un tableauL'algorithme de calcul d'une moyenne est le suivant :
minimum(tableau)
renvoyant la moyenne des Ă©lĂ©ments d'un tableau.Les signaux numĂ©riques ( par exemple la musique numĂ©risĂ©e ) mĂȘme si ils ont Ă©tĂ© enregistrĂ©s dans de bonnes conditions ( en studio par exemple ), ne sont pas exempts de dĂ©fauts.
On « voit » par exemple que le signal ci-contre est en rĂ©alitĂ© une sinusoĂŻde, mais que celle-ci est « parasitĂ©e » par de nombreux « pics » vers le bas ou le haut ; lâorigine de ces parasites
peut ĂȘtre diverse ( bruit de fond du secteur Ă 50 Hz, parasites alĂ©atoires, interfĂ©rences,...).
DÚs lors, comment fait-on pour extraire uniquement le signal « utile » dans cet ensemble de données, à savoir récupérer une ( plus ou moins ) « belle » sinusoïde ?
Vous allez voir, ce n'est pas compliquĂ© : un signal numĂ©rique, ce nâest rien dâautre quâune succession de valeurs distinctes stockĂ©es dans un tableau; il suffit pour les filtrer de faire des calculs de moyenne sur ces valeurs, et ça, vous savez faire !!
Mais pour filtrer un signal, nous nâallons pas faire la moyenne de toutes les valeurs constituant le signal comme on l'a fait au 2.3 ( on obtiendrait alors zĂ©ro, le signal contenant Ă peu prĂšs autant de valeurs positives que nĂ©gatives...) !
LâidĂ©e est en fait de calculer la valeur moyenne "Ă un instant donnĂ©" des valeurs, câest Ă dire calculĂ©e sur quelques valeurs successives dans le tableau.
Le principe en est le suivant :
Voila un exemple avec une fenĂȘtre de calcul de 1 Ă©lĂ©ment ( 1 avant / 1 aprĂšs, soit une moyenne glissante sur 3 Ă©lĂ©ments ) :
La fenĂȘtre de calcul se "dĂ©plaçant" ainsi sur lâensemble des valeurs semble "glisser" sur cet ensemble, dâoĂč le nom de moyenne glissante utilisĂ© pour ce calcul.
( Remarque : on constate que les premiers et les derniers Ă©lĂ©ments ne peuvent logiquement pas ĂȘtre traitĂ©s avec cette mĂ©thode...)
On va pour commencer considĂ©rer une fenĂȘtre de largeur 1 Ă©lĂ©ment.
Quelques indications :
moyenne()
un "sous-tableau" extrait du tableau principal, et contenant les Ă©lĂ©ments sur lequel le calcul de la moyenne doit se faire.Vous allez maintenant appliquer le principe du filtrage par moyenne glissante Ă une sĂ©rie « rĂ©elle » de donnĂ©es issues dâun signal numĂ©rique.
Vous disposez pour cela du fichier signal_bruit.txt qui contient la suite des valeurs dâun signal « bruitĂ© » quâil va falloir traiter...
Il va falloir vous documenter pour :
Dans l'onglet Outils du site, vous trouverez quelques éléments d'aide pour l'utilisation des fichiers sous Python et sur l'utilisation du module Matplotlib; n'hésitez pas à consulter d'autres ressources pour compléter ces informations.
Nous allons dans cette partie commencer à nous poser une question que nous reverrons en algorithmique cette année : la question de la preuve d'un algorithme.
Vous connaissez le théorÚme ci-contre et vous savez trÚs bien dessiner un triangle rectangle quelconque et vérifier que ce théorÚme est correct.
Mais vous savez aussi que ce théorÚme a été démontré et qu'au delà de tous les tests que l'on pourrait faire, il a été vérifié théoriquement.
Et bien c'est la mĂȘme chose pour les algorithmes; on peut :
Mais il est aussi nécessaire de les démontrer.
Nous n'allons pas aujourdâhui dĂ©montrer un algorithme mais uniquement voir les deux choses qu'il faudrait dĂ©montrer...
La premiĂšre chose Ă dĂ©montrer c'est sa terminaison, c'est Ă dire le fait qu'il va s'arrĂȘter, en dehors de toute autre considĂ©ration.
En effet un algorithme, la plupart du temps, effectue un grand nombre d'actions et utilise donc une boucle. Il est important de savoir si l'algorithme va s'arrĂȘter ( sans chercher Ă savoir s'il remplit bien son rĂŽle ).
Par exemple dans l'algorithme de recherche de maximum que nous venons de voir :
On peut constater que :
Comme le tableau a nĂ©cessairement une longueur finie ( contrairement aux mathĂ©matiques, rien ne peut ĂȘtre infini en informatique, sauf certaines boucles mal codĂ©es...) l'algorithme va nĂ©cessairement se terminer.
Nous venons de prouver la terminaison de l'algorithme ( sans, encore une fois, avoir prouvé que celui ci nous donnait bien le maximum du tableau ).
Et cette démonstration va s'appuyer sur une notion importante : l'invariant de boucle.
En effet un algorithme, la plupart du temps, effectue un grand nombre d'actions et utilise donc une boucle.
Pour appuyer la démonstration il faut donc trouver une proposition vraie tout au long du déroulement de l'algorithme.
Par exemple dans l'algorithme de recherche que nous venons de voir :
Un invariant de boucle pourrait ĂȘtre :
Ces notions de démonstration et d'invariant de boucle ne sont pas faciles a aborder, car, la plupart du temps, la compréhension de l'algorithme semble nous rendre inutile sa démonstration... Mais sur des algorithmes plus complexes c'est une démarche mathématique importante.
Quand on a démontré que :
On a alors réalisé une preuve mathématique de l'algorithme. On est sur de son bon fonctionnement dans n'importe quelle situation.
Il reste alors Ă le coder et un "bon" algorithme peut ĂȘtre "mal" codĂ©, il faut donc bien faire attention, dans la phase d'implĂ©mentation, Ă respecter l'algorithme tel qu'il a Ă©tĂ© Ă©crit.
Voici quelques questions pour tester si vous avez bien compris les notions de complexité et de preuve d'un algorithme.