ntro.ca

        • Contrats de classe
        • Liens utiles
        • Calendrier
        • Calendrier groupe 2
        • Calendrier groupes 1, 3
        • Structure du cours
        • Évaluations
        • Matériel à se procurer
        • Les profs
          • Marc-Olivier Tremblay
          • Mathieu Bergeron
        • Module 1.1: installation + trier des cartes
        • Module 1.2: rappels POO
        • Module 1.3: tableau d'objets
        • Examen 1
        • Module 2.1: données JSON
        • Module 2.2: données en Java
        • Module 2.3: récursivité
        • Examen 2
        • Module 3.1: structure générique
        • Module 3.2: efficacité (1)
        • Module 3.3: efficacité (2)
        • Examen 3
        • Module 4.1: liste naïve
        • Module 4.2: liste par tableau
        • Module 4.3: liste chaînée
        • Examen 4
        • Module 5.1: mappage naïf
        • Module 5.2: mappage par hachage
        • Module 5.3: mappage par arbre
        • Examen 5
        • Équipes
          • Horaire groupe 1
          • Horaire groupe 2
          • Horaire groupe 3
          • Groupe 1
          • Groupe 2
          • Groupe 3
        • Projets vedettes 2022
        • Projets vedettes 2023
        • Projets vedettes 2024
        • Survol
        • Structure
        • Calendrier
        • Calendrier des séances
        • Évaluations
        • Exemples de jeu
        • Exemples de pages
        • Réponses à vos questions
        • Module 1: créer le projet
        • Module 2: concevoir l'application
        • Module 3: vues NtroFx
        • Module 4: modèle et navigation
        • Module 5: ajouter le dorsal, modifier le modèle
        • Module 7: améliorer l'affichage
        • Module 8: jeu en 2d
        • Module 9: client/serveur
        • Module 10: plusieurs instances du même modèle
        • TP1
        • Examen 1
        • TP2
        • Examen 2
        • Projet de fin de session
      • Ajout Format JSON
        • Calendrier
        • Évaluations
        • Structure du cours
        • Contrat de classe
        • Le prof
        • 01: Windows et Word
          • Astuces et raccourcis
        • 02: Word
        • 03: Word
          • Exercice Word: insertion d'éléments spéciaux
          • Exercice Word: tableaux
        • 04: Word
          • Exercice Word: références
          • TP01: Word (15%)
        • 05: PowerPoint
          • TP02: PowerPoint (10%)
        • 06: Examen Word (20%)
        • 07: Excel
        • 08: Excel
        • 09: Excel
          • TP03: Excel (15%)
        • 10: Excel
        • 11: Examen Excel (20%)
        • 12: Access
        • 13: Access
        • 14: Access
        • 15: Examen Access
      • Sondage H2023 (dept. info)
      • Vision H2023 (dept. info)
      • P1) exercices interactifs de lecture
      • P2) transition Excel vers Python
        • Atelier 2: un exemple
      • Index
      • Point de vue sur l'IA
    Théorie 3.3: efficacité (2)
    • Théorie 3.3: efficacité (2)
      • Estimer l’efficacité avec O()
      • Tri naïf: O(n2)
      • Diviser pour régner: O(log(n))
      • Tri fusion: O(log(n)·n)

    Théorie 3.3: efficacité (2) #

    • On peut estimer l’efficacité d’un programme en analysant son pseudo-code

    • Nous allons utiliser les exemples suivants:

      • une recherche naïve: O(n)
      • un tri naïf: O(n2)
      • une recherche de type “diviser pour régner”: O(log(n))
      • le tri fusion: O(log(n)·n)

    Estimer l’efficacité avec O() #

    • Il faut se poser la question:

      • pour chacun des n éléments en entrée:
        • combien de fois est-ce que l’élément sera manipulé?
    • Voici le pseudo-code d’un recherche naïve:

    int trouverIndicePourValeur(liste, valeur):

    • POUR TOUS les éléments el de la liste:
      • SI el est égal à la valeur cherchée:
        • mémoriser l’indice i de l’élément el
    • retourner le dernier indice i mémorisé
    • Clairement, on va manipuler chaque élément une fois:

      • on fait une boucle sur toute la liste
      • on compare chaque élément de la liste à la valeur cherchée
    • Donc, si on a n éléments en entrée, il y aura environs n instructions

    • Donc, l’efficacité (complexité) de l’algorithme est estimée à O(n)

    Tri naïf: O(n2) #

    • Voici le pseudo-code d’un algorithme de tri naïf:

    Liste<C> trier(Liste<C> entree):

    • CRÉER une liste resultat vide
    • TANT QUE la liste entree n’est pas vide
      • appeler valeurMinimale et mémoriser la valeur de retour
      • retirer cette valeur de la liste entree
      • ajouter cette valeur à la fin de la liste resultat
    • retourner la liste resultat
    • Si la liste entree contient n éléments, la boucle va s’exécuter n fois

      • à chaque tour de boucle, on retire un élément
      • après n tours, entree sera vide et on va quitter la boucle
    • On va faire au moins n appels à valeurMinimale()

    • Le nombre d’instructions va être n·VM

      • où VM est le nombre d’instructions pour chaque appel à valeurMinimale()
      • le nombre d’instructions total est estimé à O(n·VM)
    • Regardons le pseudo-code de valeurMinimale():

    Valeur obtenirValeurMinimale(liste):

    • considérer que le premier élément de la liste est la valeur minimale
    • POUR TOUS les éléments el de la liste:
      • SI el est plus petit que la valeur minimale courante:
        • mémoriser el comme la nouvelle valeur minimale courante
    • retourner la valeur minimale
    • On visite chaque élément de la liste

      • donc on va faire n instructions pour chaque appel à valeurMinimale()
      • alors VM == n
    • En résumé:

      • la boucle de trier fait n appels à valeurMinimale()
      • chaque appel à valeurMinimale() fait n instructions
      • on a donc O(n·VM) où VM == n
      • on a donc O(n·n)
      • on a donc O(n2)
    • L’efficacité (complexité) de notre tri naïf est O(n2)

    Diviser pour régner: O(log(n)) #

    • On cherche une valeur dans une liste trié de plus petit au plus grand

    • Est-ce qu’on est obligé de visiter tout le tableau?

    • On peut appliquer une technique appelée “diviser pour régner”:

      • on choisit un élément au milieu de la liste
      • si l’élément est plus petit que la valeur cherchée
        • on peut chercher uniquement “haut dessus” de cet élément
      • si au contraire l’élément est plus grand que la valeur cherchée
        • on peut chercher uniquement “en dessous” de cet élément
    • Et pour chercher dans la moitié de la liste?

      • on applique encore “diviser pour régner”
      • on cherche uniquement dans la moitié de la moitié de la liste
    • Et ainsi de suite jusqu’à ce qu’on trouve

    • À chaque étape dans notre recherche, on divise la liste en deux

    • Pour estimer le nombre d’instructions, on divise n par 2 à chaque étape de recherce:

      • si n=2048, on a:
        • éléments restants à visister après la 1ière étape: 2048
        • éléments restants à visister après la 2ième étape: 1024
        • éléments restants à visister après la 3ième étape: 512
        • éléments restants à visister après la 4ième étape: 256
        • éléments restants à visister après la 5ième étape: 128
        • éléments restants à visister après la 6ième étape: 64
        • éléments restants à visister après la 7ième étape: 32
        • éléments restants à visister après la 8ième étape: 16
        • éléments restants à visister après la 9ième étape: 8
        • éléments restants à visister après la 10ième étape: 4
        • éléments restants à visister après la 11ième étape: 2
        • éléments restants à visister après la 12ième étape: 1
      • on a terminé notre recherche en seulement 12 étapes
      • (pour un programme informatique, c’est ridiculement petit)
    • Il s’agit de O(log(n)), le contraire d’une explosion exponentielle:

    • Les algorithmes O(log(n)) sont souvents les plus efficaces

    • Par exemple, voici des résultats comparant nos deux recherches:

    • Ce n’est pas une erreur si la ligne verte est presque invisible!
      • la recherche efficace est tellement rapide qu’on arrive à peine à la mesurer

    Tri fusion: O(log(n)·n) #

    • Est-ce qu’on peut appliquer l’idée de “diviser pour régner” au tri?

    • Oui, voici par exemple le tri fusion:

    Liste trier(Liste entree):

    • CRÉER une liste resultat vide
    • SI entree contient un seul élément ou moins:
      • copier entree dans resultat
    • SINON:
      • diviser la liste entree en deux sous-listes égales
      • appeler trier() pour trier chaque sous-liste
      • appeler fusionner() pour fusionner les deux sous-listes
      • copier la liste fusionné dans resultat
    • retourner la liste resultat
    • À chaque étape, on fait un nouvel appel à trier

      • donc à chaque étape on divise entree en deux
      • il va donc avoir log(n) étapes
      • on va donc faire log(n) appels à fusionner()
    • Chaque appel à fusionner() demande au moins n instructions

      • chaque élément est manipulé une fois
      • fusionner prend deux listes triées et les combine en une seule liste triée en comparant les éléments un par un, et en ajoutant le plus petit des deux dans la nouvelle liste
      • on continue ensuite en ajoutant les éléments restants des deux listes
    • L’efficacité (complexité) de l’algorithme est donc O(log(n)·n)

      • c’est à dire, on fait log(n) fois une étape qui demande n instructions
    • En terme d’efficacité, les algorithmes O(log(n)·n) sont presques aussi bon que O(n):

    • Par exemple, voici une comparaison entre le tri naïf et le tri fusion:
    • Encore une fois, ce n’est pas une erreur si la ligne verte est presque invisible!
      • en comparaison au tri naïf, le tri fusion est preque instantané
    Creative Commons License Creative Commons Attribution Creative Commons ShareAlike
    • Théorie 3.3: efficacité (2)
      • Estimer l’efficacité avec O()
      • Tri naïf: O(n2)
      • Diviser pour régner: O(log(n))
      • Tri fusion: O(log(n)·n)