Physique-Chimie & NSI

Cours complets et originaux de Physique-Chimie & NSI

2-01. Parcours de tableau

Dans ce chapitre, nous abordons nos premiers algorithmes classiques sur les listes : calculs de grandeurs globales (somme, moyenne, comptage), recherche séquentielle et détection d'extremum. Nous nous intéresserons également à l’efficacité de ces algorithmes en introduisant la notion de coût temporel.

Rappel : comment parcourir un tableau ?

  • Choisir la boucle adaptée selon que l’on a besoin de l’indice ou de la valeur seule.
  • Ne pas confondre la case (valeur) et son adresse (indice).

Nous avons vu au chapitre 3-02 qu’une boucle for permet de visiter tous les éléments d’un tableau. Deux approches sont possibles :


            notes = [12, 15, 8, 19]

            # 1. Parcours par VALEURS (direct)
            for n in notes:
                print(n)

            # 2. Parcours par INDICES (accès positionnel)
            for i in range(len(notes)):
                print(f"Index {i} : {notes[i]}")
        

Règle de décision simple :

  • On parcourt par valeur quand on a seulement besoin de lire, cumuler ou tester les éléments sans s'intéresser à leur position.
  • On parcourt par indice dès qu'on a besoin de l'emplacement (rang, position d'un extremum) ou qu'on doit modifier la liste sur place.

🖐️ Rappel sur une confusion fréquente :

Dans for i in range(len(tab)):, la variable i est un entier (0, 1, 2…) représentant la position. La valeur contenue dans la case s’obtient avec tab[i].

Algorithmes d’accumulation

  • Calculer la somme et la moyenne des valeurs d’une liste numérique.
  • Compter le nombre d’occurrences respectant un critère précis.
  • Identifier et traiter le cas limite d’une liste vide.

Somme et moyenne

Le principe consiste à utiliser un accumulateur (une variable initialisée à zéro avant la boucle) auquel on ajoute successivement chaque élément du tableau lors de la visite.


        def somme(tab):
            total = 0
            for val in tab:
                total += val
            return total

        def moyenne(tab):
            if len(tab) == 0:
                return None   # Évite la division par zéro si la liste est vide !
            return somme(tab) / len(tab)
    

Avant d’effectuer une division par la taille d’un tableau (len(tab)), il faut toujours s’assurer que le tableau n’est pas vide, sous peine de déclencher une erreur critique ZeroDivisionError.

La fonction somme qu’on vient d’écrire est tellement utile… que c’est une fonction native de Python ! 😊
Elle s’appelle sum. Je vous suggère de retenir son existence.

Comptage conditionnel

On peut combiner l’accumulation avec un test if pour ne comptabiliser que les éléments vérifiant une condition donnée.


        def compter_reçus(notes):
            """Compte le nombre de notes supérieures ou égales à 10."""
            compteur = 0
            for note in notes:
                if note >= 10:
                    compteur += 1
            return compteur
    

Applications sur les calculs d’accumulation

1. Écrire une fonction occurrence(cible, tab) qui prend en paramètre un élément cible et une liste tab, et renvoie le nombre exact de fois où cet élément apparaît dans la liste.

2. Écrire une fonction moyenne_ponderee(notes, coefficients) qui prend deux listes de même longueur et renvoie la moyenne en tenant compte des coefficients associés. Attention, dans cette liste, une note pour laquelle l’élève a été absent est notée "Abs". Il faudra prévoir ce cas de figure ! Toutes les notes sont des entiers (ou bien "Abs")

Pour vérifier que le type d’une variable x est un entier, on peut faire le test type(x) == int.

Recherche séquentielle et sortie anticipée

  • Tester la présence d'une valeur dans un tableau.
  • Trouver l’indice d'une occurrence.
  • Maîtriser le principe de sortie anticipée d’une fonction avec return.

Recherche de présence (booléen)

Pour savoir si un élément figure dans un tableau, on examine les cases l’une après l’autre. Dès qu’on le trouve, il est inutile de continuer : on interrompt immédiatement la fonction avec un return True.


        def existe(cible, tab):
            for element in tab:
                if element == cible:
                    return True   # Trouvé ! On quitte la fonction immédiatement.
            return False          # On a tout examiné sans succès.
    

⚠️ Le piège classique du else intérieur !

L’erreur de débutant consiste à écrire :


            # ❌ CODE ERRONÉ :
            def existe(cible, tab):
                for element in tab:
                    if element == cible:
                        return True
                    else:
                        return False  # la boucle s'arrête dès le premier élément !
        

Avec ce code faux, si la première case ne contient pas la cible, la fonction renvoie False tout de suite sans inspecter le reste du tableau ! Le return False doit impérativement se situer en dehors de la boucle.

Recherche de l’indice d’une valeur

Si l'on veut savoir où se trouve l'élément, on utilise le parcours par indices. On renvoie l’indice dès la première rencontre, ou None (ou -1) si l’élément n’existe pas.


        def trouver_indice(cible, tab):
            for i in range(len(tab)):
                if tab[i] == cible:
                    return i
            return None
    

Applications sur la recherche

1. Écrire une fonction tous_positifs(tab) qui renvoie True si tous les nombres de la liste sont strictement positifs, et False dès qu'un élément est négatif ou nul.

2. Écrire une fonction dernier_indice(cible, tab) qui renvoie l’indice de la dernière apparition de la cible dans la liste, ou None si elle n’y est pas.

Recherche d’un extremum

  • Déterminer la valeur maximale ou minimale d'un tableau.
  • Déterminer l'indice de l'extremum.
  • Comprendre l'impératif de l'initialisation sur le premier élément.

Le principe de recherche d’un maximum

Pour trouver la plus grande valeur d’un tableau :

  1. On considère provisoirement que le maximum est le tout premier élément (tab[0]).
  2. On parcourt les éléments suivants : dès qu’on rencontre une valeur plus grande que notre champion provisoire, elle devient le nouveau champion.
  3. Une fois le tableau terminé, notre variable contient le maximum absolu.

        def valeur_max(tab):
            if len(tab) == 0:
                return None

            maxi = tab[0]  # Initialisation sur le premier élément
            for i in range(1, len(tab)):
                if tab[i] > maxi:
                    maxi = tab[i]
            return maxi
    

🖐️ Ne jamais initialiser un maximum à 0 !

Si le tableau ne contient que des nombres négatifs comme temperatures = [-5, -12, -2, -8], une initialisation à maxi = 0 renverrait 0, qui n’est pourtant même pas présent dans la liste ! L'initialisation sur tab[0] fonctionne avec tous les types de données ordonnées (y compris les nombres négatifs ou les chaînes de caractères par ordre alphabétique).

Là aussi, la recherche d’un extrémum est tellement courante en programmation que Python dispose de deux fonctions natives pour le faire : min et max (tout simplement !)

Rechercher l’indice de l’extremum

Dans de nombreuses situations (trouver le vainqueur d’une course, le jour de la température la plus basse), on cherche non pas la valeur mais sa position dans le tableau.


        def indice_min(tab):
            if len(tab) == 0:
                return None

            i_min = 0  # L'indice du plus petit élément provisoire
            for i in range(1, len(tab)):
                if tab[i] < tab[i_min]:
                    i_min = i
            return i_min
    

Applications sur les extremums

Soit la liste des vitesses mesurées lors d’un contrôle radar : vitesses = [84, 92, 79, 110, 88].

1. Dérouler manuellement l'exécution de indice_min(vitesses) en dressant un tableau des valeurs successives de i et i_min à chaque itération de la boucle.

2. Écrire une fonction etendue(tab) qui calcule l'écart entre la plus grande et la plus petite valeur d’une liste numérique non vide.

Coût d’un algorithme : la complexité linéaire

  • Identifier le nombre d'opérations élémentaires d'un algorithme.
  • Comprendre la notion de complexité linéaire $*\mathcal{O}(n)*$.
  • Distinguer le cas le plus favorable du pire des cas.

Combien d'opérations exécute un ordinateur ?

Le temps d’exécution réel d'un programme dépend de nombreux facteurs extérieurs (puissance du processeur, langage employé, applications en arrière-plan). Pour comparer l'efficacité de deux algorithmes, l'informatique mesure le nombre d'opérations élémentaires (comparaisons, additions, affectations) en fonction du nombre d'éléments $*n*$ contenus dans le tableau.

La complexité linéaire $*\mathcal{O}(n)*$

Dans nos fonctions somme(tab) ou valeur_max(tab), la boucle traite chaque case exactement une fois. Si le tableau contient $*n*$ éléments, la boucle réalise environ $*n*$ itérations.

Quand le nombre d'opérations nécessaires est proportionnel à la taille $*n*$ des données d'entrée, on dit que l'algorithme a un coût linéaire (noté en mathématiques $*\mathcal{O}(n)*$).

Conséquence pratique : si l'on multiplie par 10 le volume de données à traiter, le temps d'exécution sera lui aussi multiplié par 10.

Pire cas et meilleur cas

Pour certains algorithmes, le nombre d'opérations dépend non seulement de la taille du tableau, mais aussi de l'ordre de ses valeurs :

  • Algorithmes d'extremum et de moyenne : ils doivent impérativement lire toutes les cases. Le coût est toujours linéaire, quel que soit le contenu du tableau.
  • Recherche séquentielle avec sortie anticipée :
    • Meilleur cas : la cible est située dans la toute première case (index 0). La fonction s’arrête en 1 seule étape (coût constant $*\mathcal{O}(1)*$).
    • Pire cas : la cible est tout à la fin du tableau ou n'y figure pas du tout. La boucle doit visiter les $*n*$ éléments (coût linéaire $*\mathcal{O}(n)*$).

En informatique, lorsqu'on annonce la complexité d'un algorithme sans préciser, on se place systématiquement dans le pire des cas, car il offre une garantie absolue de performance maximale.

Mini-projet : analyse de relevés météorologiques

L’objectif de ce TP est de construire une boîte à outils de fonctions algorithmiques réutilisables, puis de les appliquer sur une série réelle de températures quotidiennes relevées sur un mois.

Partie 1 : la boîte à outils algorithmique

Créez un fichier tp_parcours.py et écrivez les fonctions suivantes sans utiliser les fonctions prédéfinies de Python (interdiction d'utiliser sum(), min(), max() ou .index()) :

  1. calculer_moyenne(tab) : renvoie la moyenne arithmétique des éléments d'un tableau numérique (renvoie None si la liste est vide).
  2. contient_valeur(cible, tab) : renvoie True si la valeur cible apparaît dans tab, False sinon (avec sortie anticipée).
  3. nombre_valeurs_superieures(seuil, tab) : renvoie le nombre d'éléments strictement supérieurs au seuil.
  4. position_minimum(tab) : renvoie l'indice de la valeur la plus basse du tableau (renvoie None si le tableau est vide).
  5. position_maximum(tab) : renvoie l'indice de la valeur la plus haute du tableau (renvoie None si le tableau est vide).

Partie 2 : analyse d'une station météo

On donne la liste des températures maximales journalières (en °C) relevées au cours du mois de novembre dans une station alpine :


        releves_novembre = [
            8.5, 9.2, 7.0, 5.4, 4.1, 2.0, -0.5, -2.1, 1.2, 3.4,
            4.0, 6.5, 7.8, 8.0, 6.2, 3.5, 1.0, -1.5, -3.0, -0.8,
            0.5, 2.1, 4.3, 5.0, 3.8, 1.5, -0.2, 0.0, 2.4, 4.2
        ]
    

À l'aide des fonctions développées dans la Partie 1, écrivez les instructions nécessaires pour afficher :

  1. La température moyenne enregistrée sur le mois (arrondie au dixième avec round()).
  2. Le nombre de jours de gelée (jours où la température relevée est négative ou nulle).
  3. La température la plus chaude du mois ainsi que le jour exact de son observation (attention : l'indice 0 correspond au 1er jour du mois).
  4. La température la plus froide du mois ainsi que son jour d'observation.
  5. Un message indiquant si la station a enregistré au moins un jour avec une température strictement supérieure à 9°C.

Partie 3 : mise en évidence expérimentale du coût linéaire

Ajoutez ce court code à la fin de votre script pour observer le comportement temporel d'un parcours séquentiel :


        import time
        import random

        # Création de tableaux de tailles différentes
        tailles = [100_000, 1_000_000, 10_000_000]

        for n in tailles:
            donnees = [random.randint(0, 100) for _ in range(n)]
            
            t_debut = time.perf_counter()
            calculer_moyenne(donnees)
            t_fin = time.perf_counter()
            
            duree = t_fin - t_debut
            print(f"Taille : {n:>10} éléments  |  Temps : {duree:.4f} s")
    

Question : Que constatez-vous sur la durée d’exécution lorsque la taille du tableau est multipliée par 10 ?