- Correctitude : La solution résout-elle le problème ?
- Efficacité : Complexité temporelle et spatiale
- Clarté : Lisibilité et maintenabilité du code
- Robustesse : Gestion des cas limites et erreurs
Problème : Évaluer la complexité d'un algorithme de recherche linéaire
- Identifier les opérations élémentaires
- Compter le nombre d'opérations en fonction de la taille
- Déterminer la classe de complexité
- Comparer avec d'autres solutions possibles
FONCTION RechercheLineaire(tableau, element) : Entier
POUR i DE 0 A LONGUEUR(tableau) - 1 FAIRE
SI tableau[i] = element ALORS
RETOURNER i
FIN SI
FIN POUR
RETOURNER -1
FIN
• Initialisation de i : 1 opération
• Comparaison de i avec la taille : n+1 opérations
• Incrémentation de i : n opérations
• Comparaison tableau[i] = element : n opérations (au maximum)
• Retour de la valeur : 1 opération (au maximum)
Opérations totales : 1 + (n+1) + n + n = 3n + 2
Donc complexité : O(n) - Linéaire
Algorithme correct mais inefficace pour de grandes données. Meilleure complexité possible : O(log n) avec recherche dichotomique sur tableau trié.
• Compter les opérations dominantes : Boucles, comparaisons
• Identifier le pire cas : Situation où l'algorithme met le plus de temps
• Simplifier l'expression : Garder le terme dominant
Problème : Valider un programme de tri par sélection avec des tests
FONCTION TriSelection(tableau) : Tableau
POUR i DE 0 A LONGUEUR(tableau) - 2 FAIRE
min_index ← i
POUR j DE i+1 A LONGUEUR(tableau) - 1 FAIRE
SI tableau[j] < tableau[min_index] ALORS
min_index ← j
FIN SI
FIN POUR
ECHANGER tableau[i] ET tableau[min_index]
FIN POUR
RETOURNER tableau
FIN
• Test 1 : [] → [] (tableau vide)
• Test 2 : [1, 2, 3] → [1, 2, 3] (déjà trié)
• Test 3 : [3, 2, 1] → [1, 2, 3] (inversé)
• Test 4 : [2, 1, 2, 3, 1] → [1, 1, 2, 2, 3] (duplicata)
• Gestion des tableaux vides : OK
• Gestion des doublons : OK
• Gestion des tableaux déjà triés : OK (pas de modification inutile)
• Complexité : O(n²) - Acceptable pour petits tableaux
Algorithme correct mais complexité quadratique. Bonne robustesse mais inefficace pour de grandes données.
• Tester les cas limites : Vide, singleton, valeurs extrêmes
• Tester les cas normaux : Données typiques
• Tester les cas exceptionnels : Erreurs possibles
Problème : Tester un algorithme de recherche dichotomique
FONCTION RechercheDichotomique(tableau, element) : Entier
gauche ← 0
droite ← LONGUEUR(tableau) - 1
TANT QUE gauche ≤ droite FAIRE
milieu ← (gauche + droite) DIV 2
SI tableau[milieu] = element ALORS
RETOURNER milieu
SINON SI tableau[milieu] < element ALORS
gauche ← milieu + 1
SINON
droite ← milieu - 1
FIN SI
FIN TANT QUE
RETOURNER -1
FIN
• Test 1 : [1, 3, 5, 7, 9], 5 → 2 (élément présent)
• Test 2 : [1, 3, 5, 7, 9], 4 → -1 (élément absent)
• Test 3 : [5], 5 → 0 (singleton)
• Test 4 : [], 5 → -1 (tableau vide)
Pour un tableau de 1000 éléments :
• Recherche linéaire : jusqu'à 1000 comparaisons
• Recherche dichotomique : maximum 10 comparaisons (log₂(1000) ≈ 10)
Beaucoup plus efficace que la recherche linéaire (O(log n) vs O(n)), mais nécessite un tableau trié.
• Utiliser la structure des données : Triez si possible
• Choisir l'algorithme adapté : Selon la taille et la structure
• Tester les performances : Comparer avec d'autres solutions
Problème : Comparer les tris par insertion et fusion
FONCTION TriInsertion(tableau) : Tableau
POUR i DE 1 A LONGUEUR(tableau) - 1 FAIRE
cle ← tableau[i]
j ← i - 1
TANT QUE j ≥ 0 ET tableau[j] > cle FAIRE
tableau[j + 1] ← tableau[j]
j ← j - 1
FIN TANT QUE
tableau[j + 1] ← cle
FIN POUR
RETOURNER tableau
FIN
FONCTION Fusion(gauche, droite) : Tableau
resultat ← []
i ← 0
j ← 0
TANT QUE i < LONGUEUR(gauche) ET j < LONGUEUR(droite) FAIRE
SI gauche[i] ≤ droite[j] ALORS
AJOUTER resultat, gauche[i]
i ← i + 1
SINON
AJOUTER resultat, droite[j]
j ← j + 1
FIN SI
FIN TANT QUE
// Ajouter les éléments restants
TANT QUE i < LONGUEUR(gauche) FAIRE
AJOUTER resultat, gauche[i]
i ← i + 1
FIN TANT QUE
TANT QUE j < LONGUEUR(droite) FAIRE
AJOUTER resultat, droite[j]
j ← j + 1
FIN TANT QUE
RETOURNER resultat
FIN
FONCTION TriFusion(tableau) : Tableau
SI LONGUEUR(tableau) ≤ 1 ALORS
RETOURNER tableau
FIN SI
milieu ← LONGUEUR(tableau) DIV 2
gauche ← SOUSTABLEAU(tableau, 0, milieu)
droite ← SOUSTABLEAU(tableau, milieu, LONGUEUR(tableau))
gauche_trie ← TriFusion(gauche)
droite_trie ← TriFusion(droite)
RETOURNER Fusion(gauche_trie, droite_trie)
FIN
| Aspect | Tri insertion | Tri fusion |
|---|---|---|
| Complexité moyenne | O(n²) | O(n log n) |
| Complexité pire cas | O(n²) | O(n log n) |
| Stabilité | Stable | Stable |
| Complexité spatiale | O(1) | O(n) |
| Facilité d'implémentation | Simple | Complexe |
Tri fusion meilleur pour grandes données, tri insertion plus simple pour petits tableaux.
• Évaluer selon le contexte : Taille des données, contraintes mémoire
• Considérer plusieurs critères : Temps, espace, stabilité, complexité
• Tester avec des jeux de données réalistes : Simuler conditions réelles
Problème : Optimiser un programme de calcul de factorielle
FONCTION FactorielleRecursive(n) : Entier
SI n = 0 OU n = 1 ALORS
RETOURNER 1
SINON
RETOURNER n * FactorielleRecursive(n - 1)
FIN SI
FIN
Complexité : O(n), Espace : O(n) à cause de la pile d'appels
FONCTION FactorielleIterative(n) : Entier
resultat ← 1
POUR i DE 2 A n FAIRE
resultat ← resultat * i
FIN POUR
RETOURNER resultat
FIN
Complexité : O(n), Espace : O(1) - Beaucoup plus efficace
• Cas n < 0 : Retourner erreur ou 0
• Cas n = 0 ou n = 1 : Retourner 1
• Cas n très grand : Risque de dépassement de capacité
FONCTION Factorielle(n) : Entier
SI n < 0 ALORS
ERREUR "Factorielle non définie pour négatif"
RETOURNER -1
FIN SI
resultat ← 1
POUR i DE 2 A n FAIRE
resultat ← resultat * i
FIN POUR
RETOURNER resultat
FIN
Version itérative plus efficace que récursive. Même complexité mais meilleure utilisation de la mémoire.
• Éviter la récursion si possible : Risque de débordement de pile
• Minimiser l'utilisation de la mémoire : Privilégier les versions itératives
• Gérer les cas limites : Assurer la robustesse du programme
- Analyse de complexité : Compter les opérations élémentaires
- Tests unitaires : Vérifier chaque fonction individuellement
- Tests fonctionnels : Vérifier le comportement global
- Profiling : Mesurer les performances réelles
- La complexité théorique ne reflète pas toujours les performances réelles
- Un algorithme optimal en temps peut utiliser trop de mémoire
- La lisibilité du code influence la maintenabilité
- Les tests doivent couvrir les cas normaux et les exceptions
- Équilibre : Chercher le bon compromis entre efficacité, lisibilité et robustesse
- Contexte : Adapter l'évaluation au contexte d'utilisation
- Mesure objective : Utiliser des critères mesurables pour comparer les solutions
- Amélioration continue : Réévaluer régulièrement les solutions existantes