Warning: main(menu_algo.php) [function.main]: failed to open stream: No such file or directory in /mnt/106/sda/4/b/nsivaugelas/premiere1/algotableau.php on line 16

Warning: main() [function.include]: Failed opening 'menu_algo.php' for inclusion (include_path='/mnt/106/sda/4/b/nsivaugelas/include:.:/usr/php4/lib/php') in /mnt/106/sda/4/b/nsivaugelas/premiere1/algotableau.php on line 16

Algorithmes utilisant les tableaux

SOMMAIRE

📂 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

🗂 1.3. 1.3 Conclusion :

📂 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

1. 1. Recherche d'une valeur dans un tableau

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 :


if valeur in tableau:
		.................		
			

Mais on ne sait pas ce que Python fait derriÚre cette syntaxe pour trouver l'élément dans le tableau.

1.1. 1.1 Algorithme "naĂŻf"

La méthode "naïve", c'est à dire qui est la plus simple, pour trouver un élément dans un tableau est la suivante :


pour chaque élément successif du tableau :
	si l'élément est la valeur recherchée :
		alors renvoyer VRAI
renvoyer FAUX	
				

→ 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.


from random import randint

def recherche(tableau:list, valeur: int)->bool :
	.......................


			

Lien vers les RÉPONSES

1.2. 1.2. Comparaison avec l'algorithme utilisé par Python

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 :


from random import randint
from timeit import timeit

def recherche_naive(tableau:list, valeur: int)->bool :
	# la fonction ci-dessus
	............

def recherche_python(tableau:list, valeur: int)->bool :
	# la fonction utilisant le test d'appartenance Python
	............	
	
# Programme principal
tab = ...........

t1 = timeit(stmt = 'recherche_naive(tab, 15)', globals=globals(), number = 1000)
print(t1)

t2 = timeit(stmt = 'recherche_python(tab, 15)', globals=globals(), number = 1000)
print(t2)
			

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 ?

Lien vers les RÉPONSES

1.3. 1.3 Conclusion :

1.3.1. Coût en temps d'un algorithme

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.)

1.3.2. Complexité temporelle d'un algorithme

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...

1.3.3. Quelques exemples de complexité...

1.3.4. Lien avec la durée d'exécution d'un programme

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.

2. 2. Extremum ( maximum ou minimum ), moyenne

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.

2.1. 2.1 Recherche du maximum

L'algorithme de recherche d'un maximum est le suivant :


maximum ← 0

pour chaque élément successif du tableau:
	si élément > maximum
		maximum ← Ă©lĂ©ment

renvoyer maximum		
				

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 :


import doctest

def maximum(tableau:list)->int:
    """
        Docstring de la fonction :
            .....................
            
        Tests unitaires :
            
        >>> maximum([1, 10, 8, 17, 0, 3, 6, 12, 14, 18, 15, 4, 7, 16, 13, 2, 9, 19, 5, 11])
        19
            
        >>> maximum([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
        1
            
        >>> maximum([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
        19
            
        >>> maximum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0])
        19
        
        >>> maximum([48, 89, 75, 12, 31, 80, 86, 52, 52, 75, 26, 94, 48, 70, 76, 9, 6, 64, 71, 31])
        94
    """
    
    ................... # code de la fonction
	
# Programme principal

doctest.testmod()	# exécution des test unitaires
			
			

Lien vers les RÉPONSES

2.2. 2.2. Recherche du minimum

  1. écrire l'algorithme permettant de déterminer le minimum dans un tableau
  2. Ă©crire une fonction minimum(tableau) renvoyant le minimum d'un tableau
  3. compléter la fonction avec les tests unitaires sur les tableaux du 2.1.

Lien vers les RÉPONSES

2.3. 2.3. Calcul d'une moyenne

L'algorithme de calcul d'une moyenne est le suivant :


somme ← 0

pour chaque élément successif du tableau
	ajouter élément à somme
	
moyenne ← somme / n ( n = nombre d'Ă©lĂ©ments du tableau )

renvoyer moyenne		
			
  1. écrire une fonction minimum(tableau) renvoyant la moyenne des éléments d'un tableau.
    Vous n'oublierez pas les annotations ( attention au type du résultat renvoyé ! ) et la docstring
  2. compléter la fonction avec quelques tests unitaires bien choisis
  3. utiliser ensuite la fonction avec les tableaux du 2.1.

Lien vers les RÉPONSES

3. 3. Application : le filtrage numérique

3.1. 3.1. Pourquoi filtrer un signal numĂ©rique ?

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 !!

Signal bruité

3.2. 3.2. Calculer la moyenne glissante sur un ensemble de valeurs

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 :


Pour chaque élément i du tableau :

    ‱ on calcule la moyenne sur un sous-ensemble de n Ă©lĂ©ments du tableau avant et aprĂšs l’élĂ©ment i, celui-ci compris ( ce sous-ensemble est appelĂ© « fenĂȘtre » de calcul ; n est appelĂ©e « largeur » de la fenĂȘtre )
    ‱ on range la valeur moyenne comme Ă©lĂ©ment d'un nouveau tableau
    ‱ on passe Ă  l’élĂ©ment suivant ( i + 1)

On obtient ainsi un nouveau tableau contenant les moyennes successivement calculées
			

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 ) :

Moyenne glissante

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.

  1. Ă©crire une fonction qui prend en paramĂštre un tableau de donnĂ©es, et renvoie le tableau des moyennes glissantes ; pour le calcul des moyennes, vous pourrez rĂ©utiliser la fonction Ă©crite Ă  l’exercice 2.3.
  2. tester le fonctionnement de cette fonction avec le tableau donné dans l'exemple ci-dessus.
  3. faire le mĂȘme travail pour une fenĂȘtre de 2, puis 3 Ă©lĂ©ments.
  4. Ă©crire alors une fonction qui prend en paramĂštre un tableau de donnĂ©es et un entier n reprĂ©sentant la largeur de la fenĂȘtre, et qui renvoie le tableau des moyennes glissantes.
    
    def moyenne_glissante(t : list, n:int)->list:
    	"""
    	Fonction de calcul de la moynne glissante des valeurs d'un tableau
    		t = tableau des valeurs d'origine
    		n = largeur de la fenĂȘtre
    	Renvoie un nouveau tableau contenant les moyennes glissantes.
    	"""					
    	.................			
    					

Quelques indications :

Lien vers les RÉPONSES

3.3. 3.3. Mini-projet : filtrage d'un "vrai" signal

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 :

  1. extraire dans un tableau la sĂ©rie de donnĂ©es « brutes Â» Ă  traiter Ă  partir du fichier texte fourni. Dans ce fichier, il y a une seule donnĂ©e par ligne.
  2. traiter ce tableau de donnĂ©es Ă  l’aide du principe de la moyenne glissante de façon Ă  en faire le filtrage
  3. afficher Ă  l’aide du module Matplotlib la courbe reprĂ©sentant les donnĂ©es brutes et celle des donnĂ©es filtrĂ©es. Si Matplotlib n'est pas installĂ©e chez vous, faites-le !

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.

Lien vers les RÉPONSES

4. 4. Preuve d'algorithme : Terminaison et Correction

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.

4.1. 4.1 Pourquoi une preuve ?

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.

ThéorÚme de Pythagore

Et bien c'est la mĂȘme chose pour les algorithmes; on peut :

Mais il est aussi nécessaire de les démontrer.

4.2. 4.2 comment structurer le raisonnement ?

Nous n'allons pas aujourd’hui dĂ©montrer un algorithme mais uniquement voir les deux choses qu'il faudrait dĂ©montrer...

4.2.1. 4.2.1 Terminaison

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 :


mettre 0 dans maximum

pour tous les éléments du tableau
	si élément > maximum
		mettre élément dans maximum

renvoyer maximum		
				

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 ).

4.2.2. 4.2.2 Correction et invariant de boucle

La deuxiÚme chose à démontrer pour un algorithme c'est sa correction, le fait qu'il est correct, qu'il remplit bien le rÎle pour lequel il a été conçu.
Par exemple :

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 :


pour tout les éléments du tableau
	si l'élément est l'élément recherché
		renvoyer Vrai
renvoyer Faux	
			

Un invariant de boucle pourrait ĂȘtre :


"l'élément cherché est placé entre l'élément courant et la fin du tableau" 
			

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.

4.2.3. 4.2.3. Conclusion

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.

5. 5. QCM

Voici quelques questions pour tester si vous avez bien compris les notions de complexité et de preuve d'un algorithme.